作为Java开发者,一般可能关注最多的就是堆内存(heap)和栈内存(stack),实际可分为以下几个区域:
方法区(Method Area):与Java堆一样,是各个线程共享内存区域,它存储已被虚拟机加载的类信息 、常量、静态变量、即时编译器编译后的代码等数据。运行时常量池是方法区的一部分。
虚拟机栈(VM Stack): 线程私有的,生命周期与线程相同。每个方法执行的同时都会创建栈帧(Stack Fragme),用于存储局部变量表,操作数栈,动态链接,方法出口等信息。每个方法执行完成的过程对应着栈帧在虚拟机栈中入栈到出栈的过程。
本地方法栈(Native Method Stack):与虚拟机栈作用相似,只不过对应的是Nativie方法。
堆(Heap): 是虚拟机管理的内存最大的一块,被所有线程共享。唯一目的就是存放对象实例,几乎所有的对象都在这里分配内存。Java堆是GC的主要区域。
程序计数器(Progress Counter Register):一块较小的内存空间,线程私有,当前线程所执行的字节码的行号指示器。
备注:直接内存(Direct Memory)虽不是虚拟机运行时数据的一部分,也不是虚拟机规范的内存区域,但是也会经常使用,就是我们 I/O操作。
内存溢出(OutOfMemoryError)从虚拟机角度分析可概括为以下几类:
- 堆溢出
- 栈溢出(虚拟机栈和本地方法栈)
- 方法区和运行时常量池溢出
- 直接内存溢出
只要GC Roots 可达,那么这些对象就不会被回收。那么可作为GC Roots的对象主要是以下几种:
- 虚拟机栈(栈帧中的本地变量表)中引用的对象
- 方法区中类静态引用的对象和常量应用对象
- 本地方法栈中JNI(即一般说的Native 方法)引用的对象
那么在开发中常见的内存泄漏常见就可以对应起来:
- 单例对象持有 Activity 的 Context 引用 => 方法区内存溢出
- 注册和反注册。如果没有反注册,由于一般注册用集合保持引用,没有反注册 => 堆内存溢出
- Bitmap对象,引起的内存泄漏 => 方法区内存溢出
- I/O操作,db操作,未关闭 => 直接内存溢出
等等其他一些方式引起的内存泄漏都可以根据以上规则进行匹配判断