垃圾回收机制知识点:
JVM中的年代
JVM
中分为年轻代(
Young generation
)和老年代
(Tenured generation)
。
HotSpot JVM
把年轻代分为了三部分:
1
个
Eden
区和
2
个
Survivor
区(分别叫
from
和to
)。默认比例为
8
:
1
,为啥默认会是这个比例,接下来我们会聊到。
一般情况下,新创建的对象都会被分配到
Eden
区
(
一些大对象特殊处理
)
,这些对象
经过第一次
Minor GC
后,如果仍然存活,将会被移到
Survivor
区。对象在
Survivor
区中每熬过一次
Minor GC
,年龄就会增加
1
岁,当它的年龄增加到一定程度时,就
会被移动到年老代中。 因为年轻代中的对象基本都是朝生夕死的
(80%
以上
)
,所以
在年轻代的垃圾回收算法使用的是复制算法,复制算法的基本思想就是将内存分为
两块,每次只用其中一块,当这一块内存用完,就将还活着的对象复制到另外一块
上面。复制算法不会产生内存碎片。
在
GC
开始的时候,对象只会存在于
Eden
区和名为
“From”
的
Survivor
区,
Survivor区
“To”
是空的。紧接着进行
GC
,
Eden
区中所有存活的对象都会被复制到
“To”
,而
在
“From”
区中,仍存活的对象会根据他们的年龄值来决定去向。年龄达到一定值
(
年
龄阈值,可以通过
-XX:MaxTenuringThreshold
来设置
)
的对象会被移动到年老代
中,没有达到阈值的对象会被复制到
“To”
区域。经过这次
GC
后,
Eden
区和
From
区
已经被清空。这个时候,
“From”
和
“To”
会交换他们的角色,也就是新的
“To”
就是上
次
GC
前的
“From”
,新的
“From”
就是上次
GC
前的
“To”
。不管怎样,都会保证名为
To
的
Survivor
区域是空的。
Minor GC
会一直重复这样的过程,直到
“To”
区被填
满,
“To”
区被填满之后,会将所有对象移动到年老代中。
Minor GC和Full GC的区别
Minor GC:
指发生在新生代的垃圾收集动作,该动作非常频繁。
Full GC/Major GC:
指发生在老年代的垃圾收集动作,出现了
Major GC
,经常会伴随至少一次的
Minor GC
。
Major GC
的速度一般会比
Minor GC
慢
10
倍以上。
空间分配担保
在发生
Minor GC
之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象的总空间,如果这个条件成立,那么
Minor GC
可以 确保是安全的。如果
不成立,则虚拟机会查看
HandlePromotionFailure
设置值是否允许担保失败。如
果允许,那会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象
的平均大小,如果大于,则将尝试进行一次
Minor GC
,尽管这个
Minor GC
是有风
险的。如果小于,或者
HandlePromotionFailure
设置不允许冒险,那这时也要改为
进行一次
Full GC
。
1.标记-清除算法
算法分为两部分,即标记过程和清除过程
- 首先标记处所有需要回收的对象
- 在标记完成后统一回收所有被标记的对象
弊端:
- 效率问题:标记和清除两个过程效率都不高
- 空间问题:标记清除之后会产生大量的不连续的内存碎片,碎片太多的话会导致之后程序需要分配较大内存时,因为没有足够连续的内存空间不得不提前出发另一次垃圾收集动作
2.复制算法
目的:解决效率问题
方法:将可用内存按照容量大小划分为大小相等的两块,每次只使用其中一块。当一块内存使用完后,就将还存活的对象复制到另一块上面,然后再把已使用过的内存空间一次清理掉。这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片的问题了
弊端:将内存缩小为原来的一半
3.标记-整理算法
复制收集算法在对象存活率较高时,就要进行较多的复制操作,效率就会降低,根据老年代的特点,提出了“标记-整理”算法
方法:标记过程和“标记-清除“算法一样,但是后续步骤不是直接对可回收对象进行清理,而是让所有存货的对象都像一端移动,然后直接清理掉边界以外的内存
4.分代收集算法
一般把java堆分为新生代和老年代,然后根据各个年代的特点采用最适当的收集算法
在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存货,那就可以使用复制算法
在老年代中,对对象存活率高,没有额外空间对其进行分配担保,就必须采用“标记-清除”或者“标记-整理”来回收