几个名词
- Minor GC——年轻代中的GC操作
- Full GC——老年代中的GC操作
几种引用
- 强引用(
Strong Reference
)——只要存在,就不会被安排 - 软引用(
Soft Reference
)——只有实在没空间准备要凉的时候才会被安排 - 弱引用(
Weak Reference
)——下一轮GC
到来,就会被安排,不管实际上还够不够空间 - 虚引用(
Phantom Reference
)—— 想怎么安排就怎么安排,只是用来告诉持有引用的用户,你的东西被安排了
几个分代
-
为什么会有分代
我们先来屡屡,为什么需要把堆分代?不分代不能完成他所做的事情么?其实不分代完全可以,分代的唯一理由就是优化GC性能。你先想想,如果没有分代,那我们所有的对象都在一块,GC的时候我们要找到哪些对象没用,这样就会对堆的所有区域进行扫描。 -
对象能活多久
数据证明,我们应用中的对象在80%以上都是秒死亡的,也就是活不过一个GC轮回。
- 年轻代——放刚出生的对象
- 老年代——放老对象,就是那些在年轻代中生存了几个GC轮回的对象
- 持久代——存放类信息、常量池、静态字段、方法等,一般不会回收了(但是现在被一个叫元空间的东西给替代了,持久代还是归JVM管的一块内存,但元空间就不是了,它直接就是计算机的一块内存)
给引用赋空值,但是他却就是不空
A.HELP = new A();
A.HELP = null;
print((A.HELP == null) + "");
这段代码居然可以运行如下:
false
原来,A中进行了这样的操作:
class A {
static A HELP= null;
@Override
void finalize() throws Throwable {
super.finalize();
HELP = this;
}
}
为啥子重写了finalize
方法就可以使赋空值无效呢?其实关键是他方法里面的赋值操作,他把自己赋值给了静态变量HELP
,下面来说说GC是如何给对象判死刑的
给对象判死刑
当做完第一次可达性分析之后发现目标对象和GC Root
并不相连,那么就会去看看需不需要执行对象的finalize
方法(finalize
没有被执行过或者没有被重写,这两种情况都认为需要执行),认为需要执行finalize
的对象都会被加入队列中,否则直接杀死。加入了队列的对象都会排队被系统调用finalize方法,调用过后GC将再标记一次队列中的对象,加入此时对象与GC Root有链接,那么放生,否则GG

所以说对象可以在finalize方法中自救,不过只有一次机会,因为上面说了,finalize被执行过一次的对象是不会进入队列的,是直接GG的
垃圾收集算法
标记-清除
就是将要回收的块标记出来,然后清掉,这样的问题是会产生很多碎片区域
复制
复制算法就是以一块完整的区域为GC目标,GC它时,将区域中还存活着的对象都复制到另一个空闲区域,然后直接清空整个目标区域,这样就不会产生碎片,但是复制消耗大,并且需要空间大
标记-整理
他和标记清除的不同就是,标记要删除的块之后,它会把存活的对象推向一侧(玩过2048的同学可以感受一下这是什么操作),然后删掉剩余区域
Stop The World
当发生GC时,正在执行Java code的线程必须全部停下来,才可以进行垃圾回收,这就是熟悉的STW(stop the world), 但是并不是说停就停的,线程需要跑到最近的安全点
才能够挂起,所以说GC操作耗时,开销大。考虑这样一种情况,某个线程跑不到安全点,也就是说,他本来就挂起了,那么这时候GC操作就很难受,因为它必须要等所有线程都在安全点了才能开始。为了尽量避免这样的情况,又引出了另外一个玩意叫安全区
,就是把点扩展为面,在一段区域内,引用关系不会发生变化,这就叫安全区
。
各种收集器
Serial
单线程啊,不行啊,而且不支持和用户线程并发,在GC的时候会停掉用户线程
ParNew
多线程了,但是还是不支持和用户线程并发
Parallel Scavenge
很像前者ParNew,区别就是后者适合后台工作的应用,就是那种不需要交互的东西,因为这个收集器优化的点不在如何缩短停止掉用户线程的时间长短,而是在于吞吐量
( 用户代码运行时间/(用户代码运行时间+GC时间))
Serial Old
和第一个收集器只有收集算法上的区别
Parallel Old
多线程,采用“标记——整理”
算法
CMS
使用较多的收集器吧,多线程,它的并发标记和并发清理阶段都可以和用户线程并发执行
G1
除了初始标记阶段,其他阶段都可以并发执行,并发标记阶段、回收阶段可以与用户线程并发,好像很棒棒?但是还不成熟,很水。
一般如何分配
优先放在Eden
科普一哈,年轻代中有两种区,Eden区和Survivor区,通常是8:1的大小关系
当对象不是超级大的情况下,都优先塞进Eden区,要是塞不下了就来一个Minor GC,然后一般就有足够空间放下它。
大对象直接放老年代
在优先分配到Eden区的时候,如果对象大于Eden那就直接放进老年代
在年轻代中活久了的对象放进老年代
这个一看就懂啦,老了就换区啊,一般有一个 触发换区的岁数。对象经历一次GC,岁数就加一
动态年龄判断
感觉这个其实有点像平均年龄的概念,但是又有所区别。在Survivor中相同年龄的对象综合达到Survivor区的一半大小时,超过这个年龄的对象也会转移到老年代
担保机制
考虑这样一种情况,在Minor GC中,年轻代中想要存活下来的对象太多,Survivor区放不下,那这时候咋子办咯。这时候就需要老年代分一部分来存放对象了。但是,这里要知道一个事实,年轻代中到底会有多少对象存活下来在Minor GC完成前系统是无法知道的,因此根本不知道老年代中的可用空间够不够存放多出来的对象。这时候就需要冒险
了:担保机制中,老年代每次都会记录有多少对象过来了,这样系统就可以算出一个平均值,当要担保的时候就以这个平均值为参考,认为它就是这次要担保的空间大小,若老年代中的可用空间小于它的话就触发Full GC来清理一波老年代,再来Minor GC
网友评论