GC回收机制方便了开发者,但是这种方便也是有代价的。
代价:
一旦这种自动化机制出错,我们又不得不去深入理解GC回收机制。甚至需要对这些“自动化”的技术实施必要的监控和调节。
Java虚拟机中使用“可达性分析”的算法来决定对象是否可以回收。
可达性分析图可达性分析
对象之间的应用看作是一条引用链,从GC Root对象开始,追寻到对象引用的一条完整路径,则该对象不可回收。但如果该对象从GC Root开始,没有形成完整的引用路径,则该对象可以回收。
而我在这简单理解为,当JVM虚拟机在回收扫描到某个对象时,把它作为GC Root对象,进行引用追踪。如果追踪到该对象还在处于被关联引用的状态,则就是不可回收的。否则,则是可回收对象。
GC Root对象
在Java中,有一下几种对象可以作为GC Root:
1.Java虚拟机栈(局部变量表)中的引用对象(方法中的局部对象引用)
2.方法区中静态引用指向的对象
3.然处于存活状态中的线程对象
4.Native中JNI所引用的对象
GC回收算法
GC回收算法有多种策略,而面对不同情况采用不同的算法,从而达到高效回收。并且各家厂商,细节也会不一致。
标记清除算法
对“GC Roots”集合进行遍历,保留所有可以被GC Roots直接或间接引用到的对象,而剩下的都作为回收对象进行回收。
过程分为两步:
1.Mark标记阶段:将直接或间接引用到的对象进行标记,标记为存活对象,其他的标记为回收对象。
2.回收阶段:遍历完后,将回收对象清除。
标记清除算法优点是实现简单,不需要将对象进行移动。缺点是需要终端进程其他组件的执行,并且可能产生内存碎片。
标记清除算法复制算法
将现有的内存分为两块,每次只使用其中一块。
在回收时,将存活对象复制到未被使用的那块内存中,之后清除正在使用的内存,从而完成回收。
过程分为两步:
1.Mark标记阶段:将直接或间接引用到的对象进行标记,标记为存活对象,其他的标记为回收对象。
2.复制阶段:将标记存活的对象复制到未使用的内存中,并设置当前的内存为使用内存。
3.回收阶段:回收另一块内存。
赋值算法优点是按照顺序分配内存即可,实现简单,运行高效,没有内存碎片。缺点是可用的内存大小缩小到原来一般,对象存活率高时会频繁进行复制操作。
复制算法复制前 完成复制后清除标记-压缩算法
先从根节点开始对所有可达对象进行一次标记,之后将所有存活对象压缩到内存的一端,最后清理一端边界外所有的空间。
过程分为两步:
1.Mark标记阶段:将直接或间接引用到的对象进行标记,标记为存活对象,其他的标记为回收对象。
2.压缩阶段:将剩余存活对象按顺序压缩到内存某一端
3.回收阶段:回收边界外内存。
标记-压缩算法优点是既避免了碎片又不需要分离成两块内存控件,缺点是压缩操作需要进行布局对象移动,会降低一定效率。
标记-压缩图例JVM分代回收策略
Java虚拟机会根据对象存活的周期不同,把堆内存分为新生代、老年代。
对于新创建的对象在新生代中进行分配内存,此区域对象生命周期一般比较短。如果经过多次回收仍然存活下来,则转移到老年代中。
新生代
新生成的对象优先存放到新生代中,存活率很低。
新生代中,常规应用进行一次收集一般可回收70%~95%的空间,回收率很高。所以一般采用复制算法。
新生代又可细分为3部分:Eden、Survivor0、Survivor1。
新生代存放顺序为:Eden > Survivor0 > Survivor1。
新生代分配情况老年代
当一个新生代存活时间够长而没有被清理掉,则会被复制到老年代。
老年代内存大小一般比新生代大,可以存放更多对象。
如果对象比较大,并且新生代剩余控件不够,则这个大对象会直接分配到老年代上。
因为对象生命周期比较长,不需要过多的复制操作,所以一般采用标记压缩回收算法。
引用
对象是否存活,是通过引用关系来进行判断。而引用关系又可以根据引用强度进行分类。
JVM根据引用强度的由强到弱,它们分别是:强引用、软引用、弱引用、虚引用
四种引用对比
网友评论