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 条评论
登录 后参与评论

相关文章

来自专栏Java技术

你应该知道的缓存进化史!

本文是上周去技术沙龙听了一下爱奇艺的Java缓存之路有感写出来的。先简单介绍一下爱奇艺的java缓存道路的发展吧。

561
来自专栏Web 开发

如何把捏前端模板颗粒度

今晚看到一篇博文,其原文是讲AngularJS的模板的,但觉得该作者讲的很多思路,不仅仅是AngularJS适用。凡是想在前端进行模板组织的,都可借鉴,故写下读...

600
来自专栏SDNLAB

SDNLAB技术分享(八):Neutron的基本原理与代码实现

一、Openstack网络基础 下面对Openstack和Neutron的介绍,要从几个关键词入手。 1. 三代网络 在网络这一口,OpenStack经历了由n...

2927
来自专栏落影的专栏

iOS开发笔记(九)— 数据库、Crash、内存问题分析

分享iOS开发中遇到的问题,和相关的一些思考,本次内容包括:UIKit的iOS11问题、数据库问题定位、线上Crash处理、内存问题分析。

732
来自专栏木东居士的专栏

程序员该如何管理后宫:皇上选妃(代理模式)

1775
来自专栏牛客网

多篇面经集合,你不容错过的干货!

  5. 写一个单例模式,答主写的是双检查锁单例,问了为什么用 Volatile,synchronize 移到 方法最外面会怎么样?

932
来自专栏美团技术团队

从实际案例聊聊Java应用的GC优化

当Java程序性能达不到既定目标,且其他优化手段都已经穷尽时,通常需要调整垃圾回收器来进一步提高性能,称为GC优化。但GC算法复杂,影响GC性能的参数众多,且参...

43612
来自专栏CSDN技术头条

Hamsterdb vs. LevelDB:且看非主流数据库的自白和逆袭

【编者按】虽已问世9年之久,但是相较MongoDB,Hamsterdb的知名度仍然有所欠缺,更一度被评为非主流数据库。Hamsterdb是个开源的键值类型数据库...

1827
来自专栏Java帮帮-微信公众号-技术文章全总结

【大牛经验】高吞吐低延迟Java应用的垃圾回收优化

高性能应用构成了现代网络的支柱。LinkedIn有许多内部高吞吐量服务来满足每秒数千次的用户请求。要优化用户体验,低延迟地响应这些请求非常重要。 比如说,用户经...

3219
来自专栏Java帮帮-微信公众号-技术文章全总结

虚拟机06.JVM调优_基本垃圾回收算法

1.按照基本回收策略分 引用计数(Reference Counting): 比较古老的回收算法。原理是此对象有一个引用,即增加一个计数,删除一个引用则减少一个计...

3285

扫码关注云+社区