前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java 虚拟机总结:垃圾回收机制

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

原创
作者头像
serena
修改2021-08-03 14:56:05
5090
修改2021-08-03 14:56:05
举报
文章被收录于专栏:社区的朋友们社区的朋友们

作者:吴亨

前段时间定了个小目标,利用晚上的时间拜读大名鼎鼎的周志明的《深入理解 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.

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

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、分代
    • 分代实现
    • 二、普通 GC(minor GC):全局 GC(major GC or Full GC)
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档