Open rooobot opened 4 years ago
既然是垃圾回收,那首先要明白,什么是垃圾回收?
垃圾回收是指将已经分配了,但却不再使用的内存回收回来,以便能够再次分配的操作。
那么,有哪些垃圾回收的算法呢?
GC Roots
)来标记存活的对象集合stop-the-world
:基于安全点的机制,停止其他非垃圾回收线程的工作,直到完成垃圾回收具体的回收方式主要有以下三种:
from
和to
来维护这两块内存区域,并且只使用from
指针指向的内存区域来分配内存,在进行垃圾回收时,把存活的对象复制到to
指针所指向的内存区域中,然后交换from
指针和to
指针的指向;这种方式能有效的解决碎片化的问题,但堆空间的使用效率非常低Java
对象的生命周期有一定的特点:大部分Java
对象只存活一小段时间,则存活下来的对象则会存活很长一段时间。
基于这样的特点,于是就有了分代回收的思想:将堆空间划分为两代,分别为新生代和老年代。新生代用来存储新建的对象,当对象存活的时间够长时,则将其移动到老年代。
对新生代的垃圾回收我们称之为Minor GC
,老年代被触发垃圾回收时,表示堆空间已经耗尽了,所以,Java
虚拟机需要做一次全堆扫描,也就是Full GC
。
新生代又有进一步的划分:Eden
区和Survivor
区,其中Survivor
区还会进一步划分为两个大小相同的区,分别为from
区和to
区,这里的to
区始终是空的。
在new
对象时,会从Eden
区中划出一块用来存储对象的内存,当Eden
区的空间耗尽时,会触发Minor GC
,存活下来的对象则会被送到Survivor
区。Minor GC
时,Eden
区和from
区中的对象会被复制到to
区,然后交换from
和to
两个指针,这样就可以保证下一次Minor GC
时,to
指向的Survivor
区还是空的。
这Minor GC
的过程中,虚拟机会记录Survivor
区中的对象被来回复制的次数,当某个对象被来回复制超过一次的次数(默认为15
次)时,则该对象会被晋升至老年代。
同时,如果单个Survivor
区的占用比例超过了一定的百分比(默认为50%
),那么复制次数较高的对象也会被晋升至老年代。
由此可以看出,这里使用的是标记-复制算法。理想的情况下,Eden
区中的对象基本都死亡了,要复制的数据将非常少,因此采用这种标记-复制算法的效果也就非常的好。
同时,到这里可以看到Minor GC
不需要对整个堆进行垃圾回收。
如果老年代中的对象引用了新生代的对象时,Minor GC
会不会对老年代中的对象进行扫描呢?
答案是不会。
虚拟机通过卡表的方案来解决这个问题。卡表就是将整个堆划分为一个个大小为512
字节的卡,并且维护一个卡表,用来存储每张卡的标识位,这个标识位代表对应的卡是否可能存在指向新生代对象的引用,如果可能存在,那么就认为这张卡是脏的。
这样,在Minor GC
时,就不用扫描老年代中的对象了,只需要在卡表中寻找脏卡,然后将脏卡中的对象加入到Minor GC
的GC Roots
中即可。当完成所有脏卡的扫描之后,虚拟机会将脏卡的标识位清零。
这样新生代对象的垃圾回收就解决了。
老年代触发垃圾回收时,说明堆内存不够用了,此时就会触发Full GC
。
最后,对于新生代和老年代都有不同的垃圾回收器可供选择,各自有不同的特点。
新生代垃圾回收器,都是采用标记-复制算法:
Serial
:单线程回收Parallel
:和Serial
类似,但是是多线程Parallel Scavenge
:多线程回收,吞吐率更好,但是不能和CMS
一起使用老年代垃圾回收器:
Serial Old
:单线程回收,采用标记-压缩算法Parallel Old
:多线程回收,采用标记-压缩算法CMS
:采用标记-清除算法,并发回收,并发回收失败时,会采用上面两个回收器回收一次G1
:横跨新生代和老年代,直接将堆分成很多个区域,每个区域都可充当Eden
区、Survivor
区或老年区中的某一个。采用标记-压缩算法,能并发回收,在选择回收区域时,会优先回收死亡对象较多的区域ZGC
:号称在支持的堆内存大小范围内,暂停时间不会超过10ms
问题一
请简述
JVM
垃圾回收原理。