Java虚拟机第二章 垃圾收集与内存分配 第一部分-对象的生死
对象的生死
判断对象的生死主要采用两种方法,一种是计数算法,一种是可达性分析算法,对于计数算法,它的原理是为对象添加一个计数器,每当引用被使用的时候+1,引用失效的时候-1,0的时候即不可以再使用,例如python就是采用计数算法,但是它也有自己的缺点。例如在相互引用的时候就会产生,应该被回收的对象并没有被回收的例子。
Refferrence objectA=new Refferrence();//在这里引用被创建
Refferrence objectB=new Refferrence();
objectA.object=objectB;//+1
objectB.object=objectA;//+1
objectA=null;//-1 可达性分析算法中在这里断开连接。
objectB=null;
由于ObjectA的计数器为1,该对象并不会被回收。
而在Java虚拟机中采用的是第二种,可达性分析算法。
可达性分析类似树的遍历,对于根节点(GC root)进行遍历,遍历不到的对象则认为是要回收的对象。
而可以作为根节点的对象有虚拟机栈引用的对象(直接创建的对象,new出来的),方法区的静态对象(static对象),方法区的常量引用对象,native方法中引用的对象。这时我们再返回去看一开始的代码例子。在objectA=null的时候,它就已经与GC roots 断开连接了,也就是说不可达GC roots了,它就可以被成功回收。
可达性分析算法扩展
首先要标记GC Roots。两种方法。
1.首先进行遍历(例如在栈区遍历),判断是否为GC roots,如果是就进行一次标记。缺点:太慢了。同时你只能判断是否是指针,是不是无效引用,也就是null引用是不知道的。
2.采用一个Map进行存储GC roots,在类加载过程完成后,JIT编译后,我们可以得知每一个数据的类型,因此就可以往Map中进行添加引用对象,也就是说GC roots 是在java生命周期中被维护的。这个Map被称为OopMap。
在遍历之前,需要OopMap是精准的,而OopMap并不是一直在被更新的,而是在安全点处才会被更新。因此,所有线程都达到安全点时,GC开始。
HotSpot会在所有方法的临返回之前,以及所有非counted loop(非可数循环?)的循环的回跳之前放置安全点。
那么如何保证所有线程都到达安全点?
主动式中断:设置一个全局标志位,线程在执行时会查看标志位变化,如果为真(需要中断),就执行到最近的safepoint挂起(暂停),一般在saftpoint,分配内存(如创建对象)前进行查看标志位,防止没有足够内存。那么就有一个问题,这个“查看”非常频繁,会不会效率变低。事实上,HotSpot将该指令精简到了只有一条汇编指令的地步。
抢断式中断:使所有线程中断,未在安全点的重新运行到安全点,几乎弃用。
安全点机制保证了程序执行时,在不太长的时间内就会遇到可进入垃圾收集 过程的安全点。但是,程序“不执行”的时候呢?
所谓的程序不执行就是没有分配处理器时间,典型的 场景便是用户线程处于Sleep状态或者Blocked状态,这时候线程无法响应虚拟机的中断请求,不能再走到安全的地方去中断挂起自己,虚拟机也显然不可能持续等待线程重新被激活分配处理器时间。
对于 这种情况,就必须引入*安全区域*(Safe Region)来解决。
安全区域
***安全区域***是指能够确保在某一段代码片段之中,引用关系不会发生变化,因此,在这个区域中任意地方开始垃圾收集都是安全的。我们也可以把安全区域看作被扩展拉伸了的安全点。
当用户线程执行到安全区域里面的代码时,首先会标识自己已经进入了安全区域,那样当这段时间里虚拟机要发起垃圾收集时就不必去管这些已声明自己在安全区域内的线程了。当线程要离开安全区域时,它要检查虚拟机是否已经完成了根节点枚举(或者垃圾收集过程中其他需要暂停用户线程的 阶段),如果完成了,那线程就当作没事发生过,继续执行;否则它就必须一直等待,直到收到可以 离开安全区域的信号为止。