深入理解JAVA虚拟机(二)垃圾收集与内存分配策略

一.JAVA虚拟机垃圾回收机制

1.判断一个对象是否存活

1.1 引用计数算法:给对象中添加一个引用计数器,每当有引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用。

1.2 可达性分析算法:在Java中,都是通过可达性分析来对象是否存活的(如果对象是死的,那么它所占用的内存就是需要回收的)。可达性分析算法的基本思想就是通过一系列被称为“GC Roots”的对象开始,从这些节点向下搜索,搜索走过的路线称为引用链。当一个对象没有在任何引用链上出现,则这个对象会被判定为不可用的(死的,可回收的)。

       在被可达性分析算法判定为不可用的对象,也并非是一定就是会被回收的,它们还会经历一次筛选的过程,筛选的条件就是此对象是不是要执行finalize()方法,如果对象没有覆盖finalize()方法或它的finalize()方法在上一次垃圾回收器工作时已经执行过了,则被判定为不用执行finalize()方法(对象会在这次回收中被回收),若判定为需要执行finalize()方法,则这个对象会被放置在一个F-Queue队列中,稍后虚拟机会建立一个finalizer线程(低优先级)来触发这个方法,但虚拟机不承诺会等它执行完这个方法。(也就是这个对象可能在执行finalize()方法时被回收了),如果在finalize()方法中,对象加入了任何一个引用链中,则这个对象在这次回收器工作时就不会被回收了。

       在Java中,有几种可作为GC Roots对象:虚拟机栈(栈帧中的本地变量表)中引用对象。方法区中类静态属性和常量引用的对象和本地方法栈中JNI引用的对象;

二.垃圾回收算法

1.标记-清除(Mark-Sweep)算法

首先会利用前面的可达性分析算法标记出需要回收的对象,在标记完成后就统一回收所有被标记的对象,这个算法的缺点主要有:
•效率问题,在标记和清除两个过程中效率都不高;
•空间问题,标记清除之后会产生大量的内存碎片,碎片太多,可能导致在下次为大对象分配内存时,提前触发一次垃圾回收动作;

2.复制算法(Coping)

将可用的内存分为两块,每次只使用其中的一块,这样每次只需要顺序分配内存就可以,当一块的内存用完后,就把还存活的对象复制到另一块内存中去,然后对使用过的内存空间进行回收就可以了。(一般不会采用平均分成两块的方式,现代虚拟机一般会将内存分成一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden空间和一块Survivor空间,回收时,将Eden空间和Survivors空间里还存活的对象复制到另一块没有使用的Survivor空间中,然后清理掉用过的空间),一般会这种算法回收新生代的内存空间。

3.标记-整理(Mark-Compact)算法

先利用可达性分析算法标记需要回收的对象,然后就让还存活的对象(出现在任何引用链中的对象)都向一端移动,然后清理掉端边界外的内存。(一般用来回收老年代的对象)。

三.垃圾回收的时间

大多数情况下,对象优先在Eden区中分配(大对象直接在老年代分配),当Eden没有足够空间时,JVM就会发起一次Minor GC。在进行Minor GC 之前,JVM会检查老年代最大可用的空间是否大于新生代所有对象的空间,如果成立,则Minor GC是安全的,否则,JVM就会去检查HandlePromotionFailure设置值是否允许担保失败。如果允许担保失败,则会继续检查老年代最大可用空间是否大于历次晋升到老年代对象的平均大小,如果是,则会尝试进行Minor GC(若失败,就会进行一次Full GC);否则就会改为进行一次Full GC。

各类垃圾收集器:


垃圾收集器:
    新生代:Serial收集器,ParNew收集器,Parallel Scavenge收集器
    老年代:CMS, Seral Old(MSC), Parallel Old收集器
    GI收集器

猜你喜欢

转载自blog.csdn.net/qq_31278903/article/details/72431210