【Java虚拟机】垃圾收集器和内存分配策略

版权声明:转载请注明出处: https://blog.csdn.net/qq_21687635/article/details/83863907

垃圾收集器

HotSpot虚拟机所包含的所有收集器如下图所示:
HotSpot虚拟机的垃圾收集器
如上图所示,如果两个收集器之间存在连线,就说明它们可以搭配使用。虚拟机所处的区域,则表示它是属于新生代收集器还是老年代收集器。

Serial 收集器

单线程的新生代收集器
在进行回收垃圾时,必须暂停所有的工作线程,知道回收结束,也就是需要“Stop The World”。

Serial/Serial Old收集器的运行过程如下图所示:
Serial/Serial Old收集器的运行过程

ParNew 收集器

ParNew收集器就是Serial收集器的并行多线程版本

ParNew/Serial Old收集器的运行过程如下图所示:
ParNew/Serial Old收集器的运行过程

Parallel Scavenge 收集器

  • 新生代收集器,采用复制算法,并行的多线程收集器
  • 达到一个可控制的吞吐量,即吞吐量 = 运行用户代码时间/(运行用户代码时间 + 垃圾收集时间)
  • 提供了两个参数:最大垃圾回收停顿时间和吞吐量的大小
  • 可以自动调节参数(新生代的大小、Eden和Survivor的比例等)以提供最合适的停顿时间和最大的吞吐量

Parallel Scavenge/Parallel Old收集器的运行过程如下图所示:
Parallel Scavenge/Parallel Old收集器的运行过程

Serial Old 收集器

Serial Old是Serial收集器的老年代版本,同样是一个单线程收集器,使用“标记 - 整理”算法

Serial/Serial Old收集器的运行过程如下图所示:
Serial/Serial Old收集器的运行过程

Parallel Old 收集器

Parallel Old 是 Parallel Scavenge收集器的老年代版本,使用多线程和“标记 - 整理”算法

Parallel Scavenge/Parallel Old收集器的运行过程如下图所示:
Parallel Scavenge/Parallel Old收集器的运行过程

CMS 收集器(Concurrent Mark Sweep)

基于“标记 - 清除”算法实现的
运作过程包括四个步骤:初始标记、并发标记、重新标记、并发清除

下图是CMS收集器的运行过程:
CMS收集器的运行过程

其中初始标记和重新标记仍然需要“Stop The World”。初始标记仅仅只是标记一下GC Roots能直接关联到的对象,并发标记就是进行GC Roots Tracing的过程,重新标记是为了修正并发标记期间用户程序继续运作而导致标记产生变动的那一部分对象的标记记录。

CMS收集器的三个缺点:

  1. 占用用户线程的CPU资源
  2. 垃圾回收阶段用户线程需要运行,就需要预留足够的内存空间给用户线程使用。CMS运行期间预留的内存无法满足程序需要时,就会出现“Concurrent Mode Failure”失败,就会启用Serial Old收集器来重新进行老年代的垃圾收集
  3. 基于“标记 - 清除”算法实现,会产生大量碎片。为了解决这个问题,可以在进行FullGC之前进行内存碎片合并整理。

G1 收集器(Garbage-First)

  • 不需要其他收集器配合就能独立管理真个GC堆
  • 整体是基于“标记 - 整理”算法,局部(两个Region之间)上来是基于“标记 - 复制”算法,也就是说不会产生空间碎片
  • 将整个Java堆分为多个大小相等的对立区域(Region),保留新生代和老年代的概念,每次回收价值最大的Region(价值大小为回收所获得的空间大小和回收所需时间的经验值)
  • 运作过程包括四个步骤:初始标记、并发标记、最终标记、筛选回收

下图是G1收集器的运行过程:

G1收集器的运行过程
初始阶段仅仅只是标记一下GC Roots能直接关联到的对象,并且修改TAMS(Next Top at Mark Start)的值,让下一阶段用户程序并发运行时,能在正确可用的Region中创建新对象。
筛选回收阶段首先对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来指定回收计划。

内存分配策略

新生代GC(Minor GC):指发生在新生代的GC。
老年代GC(Major GC / Full GC):指发生在老年代的GC。

对象优先在Eden分配

对象在新生代Eden区中分配,当Eden没有足够空间进行分配时,虚拟机将发起一次Minor GC。

大对象直接进入老年代

虚拟机提供了一个-XX:PretenureSizeThreshold参数,令大于这个设置值的对象直接在老年代分配。

长期存活的对象将进入老年代

虚拟机为每个对象定义一个对象年龄计数器,如果对象在Eden出生冰经过一次Minor GC后仍然存活,并且能被Survivor容纳的话,将被移动到Survivor空间中,并且对象年龄设为1。
对象在Survivor区中每“熬过”一次Minor GC,年龄就增加1岁,当它的年龄增加到一定程度,就将会被晋升到老年代中。对象晋升老年代的年龄阈值,可以通过参数-XX:MaxTenuringThreshold设置。

动态对象年龄判定

如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄 对象就可以直接进入老年代,无须等到MaxTenuringThreshold中要求的年龄。

空间分配担保

在发生Minor GC之前,虚拟机会先检查老年代最大可用的了连续空间是否大于新生代所有对象总空间,如果这个条件成立,那么Minor GC可以确保是安全的。
如果不成立,那么检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于就进行Minor GC,否则将进行Full GC。

参考

  1. 深入理解Java虚拟机[书籍]

猜你喜欢

转载自blog.csdn.net/qq_21687635/article/details/83863907
今日推荐