Java垃圾收集算法
Java怎么判断一个对象是否可以回收?当一个对象没有被使用的时候,这个对象就可以回收了,那么怎么判断一个对象有没有被使用?
引用计数法
给每一个对象添加一个引用计数器,每当这个对象被引用的时候,就给这个计数器加1,当一个对象的引用计数器为0的时候,这个对象就没有被引用。
引用计数法存在一个问题,如果两个对象相互引用,即对象A引用了对象B,而对象B又引用了对象A,除此之外这两个对象再没有其他引用。这两个对象无法被使用了,但由于引用计数器不为零,他们的内存空间并不会被回收。
根搜索算法
把一系列GC root作为起点,沿引用链向下遍历,如果可以到达某个对象,则该对象是可用的。如果无法到达某个对象,则这个对象是不可用的,要进行垃圾回收。
并不是非死不可
GC收集也不是绝对的。也就是说JVM进行GC收集时并不会回收所有的垃圾对象。如果内存空间很充足,JVM仍可以保留一些不再使用的对象,如果在进行垃圾回收之后内存空间仍旧不够用,JVM就会进行二次回收,此时JVM就会回收第一次清理时保留的对象。那么,JVM是怎么区分这些对象的呢?
JVM定义了强,软,弱,虚四种引用类型
- 强引用。最常见的引用,Object obj=new Object() obj就是一个强引用,只要强引用还存在,JVM就不会回收掉强引用的对象。
- 软引用。当内存空间不足导致JVM进行二次垃圾清理的时候,JVM会清理掉弱引用对象。
- 弱引用。这一类对象是根搜索算法无法到达的对象,即JVM垃圾。JVM会在GC收集的时候回收这些对象的内存。
- 虚引用。虚引用不会影响GC收集时执行的操作。给一个对象添加虚引用的作用就是让JVM在销毁这个对象的时候发送一个系统通知。
不可用对象的自救过程(了解即可)
通过根搜索算法查找出不可达的对象,对这些对象进行第一次标记,说明这些对象即将回收。
然后对这些对象进行筛选,判断它们是否有必要执行finalize方法,把要进行finalize方法的对象存入到F-queue中,
稍后由虚拟机自动建立一个低优先级的一个finalizer线程对这些对象进行finalize操作,对象没有实现finalize方法发或者finalize方法已经被虚拟机调用过都被视为没有必要执行finalize方法,但要注意的是finalizer线程只负责出发对象的finalize方法,并不会等待方法成功执行。
成功执行了finalize操作的对象会与引用链重新建立连接。稍后GC会对F-queue中的对象进行第二次标记,这一过程中会移除成功与引用连重新建立连接的对象。
回收方法区
GC在这个区域的回收效率较低。主要涉及到字面量,类的回收
字面量的回收:当没有任何一个string类型是"abc"的,常量"abc"会被JVM请出常量池。
类的回收:GC收集会回收无用的类
满足一下三点的类是无用的类:
1.该类的类加载器ClassLoader被回收。
2.该类的所有实例都已被回收。
3.该类的所有反射方法没有被使用。
垃圾收集算法
标记清除算法
标记出需要清理的对象,然后回收这些对象。由于这个内存回收算法是以对象为单位进行回收,所以这个算法会造成大量的内存碎片,而且效率不高
复制算法
把内存分为相等的区块,进行内存回收时,把一个区块中还存会的对象复制到另一个未使用的区块中,然后清理掉这个内存区块。清理的速度快,但是内存空间利用效率低。
复制算法改进
在JVM的堆中,新生代和老年代会发生GC回收。其中,新生代中对象的寿命都很短,每次执行MinorGC后存活的对象都很少,执行Minor GC时需要保留的对象很少,因此只需要在新生代中画出一小部分内存空间用来保存Minor GC时存活的对象就可以了。
按照上面的思路,Jvm把新生代划分为Eden区和两个Survivor区,三者的大小比例是8:1:1,平常用Eden和一个From Survivor区用来存储对象,剩下的10/1大小的一个To Survior用来存放执行MinorGC后存活的对象。
改进的复制算法保证了清理速度的同时提高了内存空间的利用效率。新生代区采用的是这种算法。
标记整理算法
标记出需要清理的对象,进行垃圾回收时,让所有被标记的对象向着一端移动,然后回收这些对象的内存。这个算法虽然是以对象为单位进行清理,但是不会产生内存碎片,速度也更快。
分代收集算法
核心思想:根据对象存活周期不同采用不同的算法进行回收处理
新生代GC(minor GC):对象的寿命很短,每次垃圾回收时都有大批对象需要回收,回收动作很频繁,速度也较快
老年代GC(Major GC):对象存活率高,没有担保空间。通常会伴随一次Minor GC。Major GC的速度通常是Minor GC的10倍以上。通常使用标记整理和标记清理算法。
垃圾收集器
Serial
每次回收垃圾时都需要"Stop the World",即停止其他的一切线程。导致在垃圾清理时,其他线程无法工作。就像一台计算机每过一个小时就需要暂停响应5分钟,是让人难以接受的。
但是在有的情况下,例如在JVM的Client模式下,使用Serial垃圾收集器只需要暂停几十毫秒。因此JVM新生代区域的垃圾收集器仍是Serial收集器。
优点:相对于其他垃圾收集器来说Serial简单高效,没有线程切换的损失。
Parallel
Concurrent Mark Sweep
Parallel Scavenge收集器
CMS收集器
初始标记
并发标记
重新校正
标记清除
内存分配与回收策略
对象优先在Eden区分配
对象优先在新生代的Eden区中分配。当Survivor不能分担任务,空间不充足的时候,会触发分配担保机制,把Eden区中的对象提前移动到老年区中去,给新创建的对象足够的空间。
大对象直接进入老年代
占用大量连续内存空间的对象直接进入老年代,不需要先在新生代中分配空间。
长期存活对象进入老年代
存活时间长的对象直接进入老年代。
动态对象年龄判定
如果Survivor空间中某个年龄的对象占用空间总和超过Survivor空间的一般,则用这个年龄作为新生代进入老年代的门槛年龄,而不需要等到规定的年龄(即MaxTenuringThreshold中要求的年龄)。
空间分配担保
Minor GC的时候,会判断之前晋升到老年代的对象的平均大小是否大于老年代现在的剩余空间。如果大于,则要进行一次Full GC,如果小于,则要看一下是否允许担保失败,如果不允许担保失败,还是要进行Full GC。
但这种策略仍是一种概率手段,不能处理极端情况。
别人写的GC收集器相关的文章
https://www.cnblogs.com/duke2016/p/6250766.html
网友评论