Java内存区域与垃圾回收
一、JAVA虚拟机运行时的数据区域
JAVA虚拟机运行时的数据区域主要包括:方法区、虚拟机栈、本地方法栈、堆、程序计数器等。这些区域的作用以及作用范围如下图所示:
二、垃圾回收算法
2.1 如何判断对象是否需要回收
引用计数器
每个对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加一;当引用失效时,计数器的值就减一。如果一个对象的引用计数器值为0,那么这个对象就不可能再被使用了。
可达性分析
如果仅仅只依靠引用计数器并不足以标识所有需要垃圾回收的对象。比如下面这个代码
public static void test(){
Test objA = new Test();
Test objB = new Test();
objA.obj = ObjB;
ObjB.obj = objA;
objA = null;
objB = null;
}
对于上面这个方法,除了 objA.obj = ObjB以及ObjB.obj = objA;之外再也没有其他的对象引用objA和objB,实际上这两个对象也不再可能再被使用、。但是如果只依靠引用计数器的话,由于他们两个都互相引用着,因此两个对象的计数器都不为零。因此,许多语言都通过可达性分析算法来判断对象是否存活。
可达性分析算法是通过一系列被称为“GC Roots”的根对象为起点,从这些节点开始,根据引用关系向下搜索,搜索的过程所走过的路径被称为引用链。如果一个对象到“GC Roots”没有任何一个引用链时,也就意味着这个对象不可达。则证明这个对象不可能再被使用了。
GC Roots
在java语言中,可以作为GC Roots的对象主要有一下七种
- 在虚拟机栈(局部变量表)中引入的对象
- 在方法区中类的静态属性引用的对象
- 在方法区中常量引用的对象
- 在本地方法栈中JNI(即Native方法)引入的对象
- Java虚拟机内部的引用,如基本类型对应的Class对象,一些常驻异常类、系统类加载器等
- 所有被同步锁(synchronizd关键字)持有的对象
- 反应Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等。
2.2 引用
强引用
即传统意义上的引用,是指在程序代码中普遍存在的引用赋值。只要强引用关系存在,垃圾回收器就不会回收掉被引用的对象。
软引用
用来描述一些还有用,但是非必须的对象。被软引用关联着的对象,在系统将要发生内存溢出异常前会被回收。如果回收之后还是没有足够的内存,才会抛出内存溢出异常
弱引用
弱引用也是用来描述非必须的对象,但是要比软引用更加弱一些。被弱引用关联的对象,下次垃圾回收时无论是否有足够的内存都会被回收。ThreadLocal中的KEY值便是使用的这种引用来实现的。
虚引用
虚引用也被称为“幽灵引用”或者“幻影引用”。是最弱的一种引用关系。一个对象是否有虚引用完全不影响其生存时间。同时,也无法通过虚引用来取得对象实例。其主要作用是在这个对象被回收时收到一个系统通知。
2.3 finalize方法
如果一个对象被可达性分析标记为不可达,并不一定会被回收。对象被回收之前,至少要经历两次标记:如果对象经过可达性分析后发现并没有与GC Roots相连接的引用链,那它将会被第一次标记,随后会进行一次筛选,而筛选的条件是该对象是否有必要执行finalize方法。如果对象没有finalize方法或者finalize方法已经被调用过了,那样会被判定没必要执行。
如果对象需要执行finalize方法,那么虚拟机会将该对象放入一个叫F-Queue的队列之中。并在稍后由一个低优先级的线程去执行他们的finalize方法。这里的“执行”只能保证该方法被调用,并不保证一定会执行完成。因为如果某个对象的finalize方法如果运行缓慢或者陷入死循环,可能会导致F-Queue队列中其他对象永久处于等待状态,甚至可能导致回收子系统崩溃。
回到刚才的话题:如果对象在finalize方法中重新与其他对象生成关联关系,那么这个对象就可以完成一个“自我的救赎”。但是,由于finalize方法只执行一次,因此使用这种方法只可以逃脱一次回收,如果后续连接断开再次被标记,该对象则会被判断为finalize方法已经被调用过,不需要再次调用。
注:这个方法不建议使用,如果有需要最后处理的逻辑,如数据库连接断开等,尽量使用try-finally
2.4 垃圾回收算法
分代收集理论
- 弱分代假说:绝大多数对象都是朝生夕灭的
- 强分代假说:熬过越多次垃圾收集过程的对象就越难以消亡
- 跨代引用假说:跨代引用相对于同代引用来说仅占极少数
现代的垃圾收集器大多数是按照分代收集理论来进行设计的。根据分代收集理论在Java堆中划分了不同的区域。现代的Java虚拟机通常将Java堆分成至少两个区域,即新生代(Young Generation)和老年代(Old Generation)。顾名思义,对象通常在新生代创建,每次垃圾收集新生代中都会有大量的对象被回收。而没被回收的对象则逐步进入老年代中存放。