Java 虚拟机总结:垃圾回收机制

作者:吴亨

前段时间定了个小目标,利用晚上的时间拜读大名鼎鼎的周志明的《深入理解 Java 虚拟机》,才看几章,“人民的名义”火起来了,唉,时间被强行夺了去,小目标眼睁睁的被失败了......

所以在这里强行立 flag 吧,先把这几章的心得总结在这里,解解达康书记的毒。

现在的主流虚拟机一般都采用分代回收,新生代、老年代.

一、分代

为什么要分代?有什么意义?这里我们假设没有分代,会怎么样呢?

答案:“stop the world”,程序被卡成翔。

为什么,因为 gc 的时候需要分析死亡对象,所以不允许这个时候对象引用关系再发生变化,这就要求“stop the world”,所以线程必然会被挂起。所以我们为了尽量让用户无感知,必然要提高 gc 效率。怎么提高,答案就是分代。我们把长活对象放在一块称为老年代,再把短命鬼放在另一块称为新生代。这样我们一般情况下只需要扫描新生代区域回收无用对象即可减少 world 被 stoped 的时间,让用户在 gc 时依然有丝滑般的顺畅感。

分代实现

我们可以看到新生代内存分配要比老年代更复杂一些,为什么会有这个区别呢?答案:垃圾清理算法的不同。 新生代:复制算法,老年代:标记-整理算法。

复制算法:

上图可以明显看到,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。它的优点就是不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。但有个致命缺点就是要“浪费“一半的内存,这太蛋疼了。

那可不可以少浪费一点呢?答案:可以的,就是 Survivor 区 ,它与 eden 区比例是 1:8,有两个 survivor 区,其中一个 survivor 区和 eden 区是可用的,另一个 survivor 区是 gc 复制区,这样被浪费的区域只有 1/10 了,大大减少了。但是这样真的合理吗,可以满足实际需要吗,当然是可以的,IBM 公司研究过,98%的 Java 对象都是”朝生夕死“,生命期很短的,这样每次 gc 之后大部分对象都 over 了,真正存活下来的是少数,所以我们只用 1/10 的空间存在这些存活对象就够了。但是,异常情况也是有的,万一存活对象所占内存多于 survivor 区怎么办呢,当然也是有解决方案:分配担保。就是内存不足的时候由担保方承担,这个担保方就是老年代区。所以这里可以看到老年代扮演着最后的大佬的角色,同时可以看到这种算法的致命缺点就是必须要有一个担保方。所以老年代不能采用这种方式,因为他就是最后的担保方,没有人再能给它担保,除非 survivor 区占一半内存,但是这又太浪费了。最后,老年代使用了”标记-整理“算法。

标记-整理算法:

分为两个阶段,首先标记所有存活对象,接着让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存,这样就完全的利用了所有内存了。

到这里可能会有人疑问,新生代为什么不也采用这种算法,不就也能充分利用所有的内存了吗?其实这里是两种算法出发点的不同,所谓复制算法是空间换时间,而标记-整理算法则是时间换空间。

复制算法在工作的时候是不没有独立的“mark”与“copy”阶段的,而是合在一起做一个动作,就叫复制。也就是说,每发现一个这次收集中尚未访问过的活对象就直接 copy 到新地方,同时设置 forwarding pointer。 这样的工作方式就需要多一份空间。

标记-整理算法在工作的时候则需要分别的 mark 与整理阶段,mark 阶段用来发现并标记所有活的对象,然后整理阶段才移动对象来达到整理内存的目的。在 mark 之后就可以按顺序一个个对象“滑动”到空间的某一侧。因为已经先遍历了整个空间里的对象图,知道所有的活对象了,所以移动的时候就可以在同一个空间内而不需要多一份空间。

总结一下就是:新生代存活对象少,为了快速 gc 我们可以浪费一点内存;而老年代存活对象多,我们更在乎内存,同时因为有效对象多,所以这块区域的 gc 应该比新生代少。这就是我们下面要讲的 minor gc 和 full gc.

二、普通 GC(minor GC):全局 GC(major GC or Full GC)

从上面我们知道新生代的 gc 是快速而频繁的,老年代 gc 是缓慢而稀少的。这也是分代的目的所在,大部分“朝生夕死”的对象可以快速被清理掉,而长存对象被放在特权区,在平常的 minor gc 的时候是不会被扫描到的,这样就大大的提高了 gc 的命中率了。

那到底什么时候 full gc 会被触发呢,当然也是老年代内存不足的时候。所以这里再说一下什么情况下,对象会被放到老年代:

i. 对象足够老,所谓足够老就是经过多次 gc 之后依然存活的对象,这里的缺省设置是 15 次。

ii.分配担保中标的对象,就是上面说的 minor gc 的时候当新生代 survivor 区不足以存放的存活对象

iii. 大对象,我们知道堆内存并不是连续的,有可能在一段时间之后内存很碎片化了,这样即使剩余总内存依然足够,但是在 eden space 已经找不到一块连续区域存放这个大对象了,这时候我们就知道把大对象直接放在老年代了。这样的情况如果发生多次我们可以想象,老年很快就会被占满,导致 full gc 的发生,而 full gc 就比 minor gc 慢很多(大概 10 倍),world 就真被 stop 了。所以我们尽量必须 new 大对象尤其是很多大对象一起 new.

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏架构师之旅

Java9中的GC调优基础

在经过了几次跳票之后,Java 9终于在原计划日期的整整一年之后发布了正式版。Java 9引入了很多新的特性,除了闪瞎眼的Module System和REPL,...

2207
来自专栏java一日一条

JVM 内存分代、垃圾回收漫谈

关于 JVM 内存模型以及垃圾回收的文章网上很多,自己以前也看过很多,但是却从来也没有系统的去了解学习过,这次正巧看到一本讲解 JVM 的好书 – 周志明老师的...

884
来自专栏ImportSource

你应该知道的Java垃圾收集器 - 串行、并行、CMS、G1

当我们谈论垃圾收集时,绝大多数人都知道这个概念,并在日常编程中使用它。即使如此,有关垃圾收集,我们很多人还是不太明白。关于JVM的一个最大的误解是它有一个垃圾收...

3638
来自专栏我就是马云飞

Java 垃圾回收机制

自动垃圾回收是一种在堆内存中找出哪些对象在被使用,还有哪些对象没被使用,并且将后者删掉的机制。所谓使用中的对象(已引用对象),指的是程序中有指针指向的对象;而未...

594
来自专栏Java架构师历程

聊聊jvm内存模型及垃圾回收算法

其中New和Tenured属于堆内存,堆内存会从JVM启动参数(-Xmx:3G)指定的内存中分配,Perm不属于堆内存,有虚拟机直接分配,但可以通过-XX:Pe...

974
来自专栏java一日一条

JVM 内存分代、垃圾回收漫谈

关于 JVM 内存模型以及垃圾回收的文章网上很多,自己以前也看过很多,但是却从来也没有系统的去了解学习过,这次正巧看到一本讲解 JVM 的好书 – 周志明老师的...

582
来自专栏精讲JAVA

面试高峰期,如何应对面试官的jvm刁难,特写一篇jvm面经(第一部)

读者前言 已经进入三月份,正所谓金三银四,正是一年最好的招聘期,想必我的公号粉丝们一定有不少想要跳槽的吧,哈哈,/**偷偷告诉你们其实小编也准备...

5017
来自专栏Duncan's Blog

JAVA虚拟机了解

1.JDK(Java Developmen Kit):将Java程序设计语言,Java虚拟机和Java API类库这三部分统称为JDK.

952
来自专栏java一日一条

Java 9 中的 GC 调优基础

在经过了几次跳票之后,Java 9终于在原计划日期的整整一年之后发布了正式版。Java 9引入了很多新的特性,除了闪瞎眼的Module System和REPL,...

652
来自专栏风中追风

Java垃圾回收的细节

大家都知道 java 的垃圾回收机制,java有自己的垃圾回收器来自动回收垃圾。本人对于垃圾回收机制以前也就知道java的垃圾回收器是自动回收垃圾的,有这么回事...

34612

扫码关注云+社区