java虚拟机(六)--垃圾收集器和内存分配策略

时间:2022-03-26 23:50:19

  目前没有完美的收集器,不同的厂商、版本的虚拟机提供的垃圾收集器会有很大的差别,用户根据自己应用特点和要求组合出各个年代所使用

的收集器。基于jdk1.7Update14之后的虚拟机。

HotSpot的垃圾收集器

java虚拟机(六)--垃圾收集器和内存分配策略

上图是作用于新生代和老年代的收集器,连线代表可以搭配使用。

新生代采用复制算法,而老年代采用标记-整理、标记-清除算法

1、Serial:串行

  采用复制算法的单线程收集器,进行GC的时候需要暂停其他线程的工作(被称为Stop The World),直到它收集结束。Serial依然是运行在

Client模式下的默认新生代收集器,因为简单、高效。用户桌面应用场景中,分配给虚拟机管理的内存一般来说不会很大,收集几十兆甚至一两百

兆的新生代停顿时间在几十毫秒最多一百毫秒,只要不是频繁发生,这点停顿是完全可以接受的。单个CPU环境下比较适合。

2、ParNew

  ParNew收集器其实就是Serial收集器的多线程版本,除了使用多条线程进行垃圾收集外,其余行为和Serial收集器完全一样,一些参数和回收策

略都相同。它是Server模式下的首选的新生代收集器,因为除了Serial收集器外,目前只有它能与CMS收集器配合工作。它默认开启的收集线程数与

CPU数量相同,在CPU数量非常多的情况下,可以使用-XX:ParallelGCThreads参数来限制垃圾收集的线程数。

3、Parallel Scavenge

  新生代,用复制算法,并行的多线程收集器,CMS等收集器的关注点是尽可能缩短垃圾收集时用户线程的停顿时间,而Parallel收集器的目标则

是打到一个可控制的吞吐量。吞吐量=CPU用于运行用户代码时间/CPU总消耗时间的比值,即吞吐量=运行用户代码时间/(运行用户代码时间+垃圾

收集时间)。另外,Parallel收集器是虚拟机运行在Server模式下的默认垃圾收集器。

  停顿时间短适合需要与用户交互的程序,良好的响应速度能提升用户体验;高吞吐量则可以高效率利用CPU时间,尽快完成运算任务,主要适合在

后台运算而不需要太多交互的任务。

4、Serial Old

  Serial收集器的老年代版本,同样是一个单线程收集器,使用“标记-整理算法”,这个收集器的主要意义也是在于给Client模式下的虚拟机使用。

5、Parallel Old

  Parallel收集器的老年代版本,使用多线程和“标记-整理”算法。这个收集器在JDK 1.6之后的出现,“吞吐量优先收集器”终于有了比较名副

其实的应用组合,在注重吞吐量以及CPU资源敏感的场合,都可以优先考虑Parallel收集器+Parallel Old收集器的组合。

6、CMS:Concurrent Mark Sweep 非常适用B/S系统

  以获取最短GC停顿时间为目标的老年代收集器。目前很大一部分Java应用集中在互联网站或者B/S系统的服务端上,注重服务的响应速度,希望系

统停顿时间最短,以给用户带来较好的体验,CMS收集器就非常符合这类应用的需求。CMS收集器基于“标记-清除”算法实现的。

运行过程分为四步:

  初始标记:会有GC停顿,只是标记一下具有可达性的对象,速度很快

  并发标记:进行GC Roots Tracing的过程

  重新标记:会有GC停顿,修正并发标记期间标记产生变动的那部分对象的标记

  并发清除:

  并发标记、并发清除是耗时最长的但是可以与用户线程同时工作

缺点:

  1、占用CPU资源,应用程序吞吐量下降

  2、并发清理阶段用户线程还在运行,就会产生浮动垃圾,由于标记过了,只能下次GC才能清理

  3、采用"标记-清除"算法就会产生大量空间碎片,大对象分配很麻烦,可能提前Full GC

7、G1(Garbage-First):兼顾吞吐量和停顿时间的GC实现,是JDK9以后的默认GC选项

  JDK 7 Update 4后开始进入商用。使用G1收集器时,Java堆的内存布局就与其他收集器有很大差别,它将整个Java堆分为多个大小相等的独立

区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分Region的集合。G1收集器跟踪各

个Region里面的垃圾堆积的价值大小,在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region(这也是Garbage-First

名称的由来)。这种使用Region划分内存空间以及有优先级的区域回收方式,保证了G1收集器在有限的时间内可以获取尽可能高的收集效率。

与其他GC收集器相比,G1的特点:

1).并发与并行

  充分利用多CPU、多核环境下的硬件优势,来缩短Stop-The-World停顿时间,G1可以通过并发让java程序继续执行

2).分带收集

  通过不同的方式处理新创建的对象和存货了一段时间、熬过多次GC的就对象来获取更好的收集效果

3).空间整合

  G1从整体来看基于“标记-整理”算法实现的收集器,从局部(两个Region之间)看基于“复制”算法实现

而且这两种算法不会产生内存空间碎片,收集后能提供规整的可用内存

4).可预测的停顿

  G1处理追求低停顿,还能建立可预测的停顿时间模型,让使用者明确指定在一个长度为Mms的时间片段内,消耗在GC的时间不超过Nms,几乎已经是实时Java的垃圾收集器的特征了。

  Region之间对象引用以及其他收集器中的新声代和老年代之间的对象引用,虚拟机都是使用Rememberd Set来避免全堆扫描。

如果不计算维护Rememberd Set的操作,收集步骤如下:

  1).初始标记

  2).并发标记

  3).最终标记

  4).筛选回收

GC日志:

DefNew:default new generation

PSYoungGen:Parallel Scavenge generation

ParNew:Parallel New Generation

老年代和永久带同理,名称也是由收集器决定

java虚拟机(六)--垃圾收集器和内存分配策略

说明:

最前面的数字代表GC发生的时间,jvm启动经过的秒数

Full GC 证明这次GC发生了GC停顿,如果是调用了system.gc(),就会显示Full GC(system)

[PSYoungGen: 33280K->2415K(38400K)] 33280K->2423K(125952K), 0.0109733 secs]

33280K->2415K(38400K):GC前该内存区域已使用容量->GC后。。。。(该内存区域总容量)

33280K->2423K(125952K):堆的容量

0.0109733 secs:GC时间

[Times: user=0.02 sys=0.00, real=0.01 secs] :分别代表用户态消耗的CPU时间、内核态消耗的CPU时间、操作从开始到结束经过的墙钟事件

CPU时间和墙钟时间区别:

墙钟时间包含多种非运算的等待耗时,例如等待磁盘IO、等待线程阻塞

CPU时间不包含这些耗时,多CPU或多核,会叠加这些CPU时间

Minor GC 对年轻代进行回收,

Major GC 是清理老年代。

Full GC 是清理整个堆空间—包括年轻代和老年代。

Minor GC触发条件:当Eden区满时,触发Minor GC。

Full GC触发条件:

(1)调用System.gc时,系统建议执行Full GC,但是不是必然执行

(2)老年代空间不足

(3)方法区空间不足

(4)通过Minor GC后进入老年代的平均大小大于老年代的可用内存

(5)由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对

象大小

内存分配与回收策略:内存分配的规则取决于使用的是哪一种垃圾回收收集器组合,还有虚拟机中与内存相关参数的配置

1、对象优先Eden space分配,如果Eden Space没有足够的空间,会发生一次Minor GC

2、大对象(很长的字符串、数组)直接分配得到老年代,通过参数-XX:PretenureSizeThreshold=3145728(3M),只要大于这个值,对象直接分

配到老年代,防止新生代进行大量内存复制

3、长时间存活的对象,会转到老年代,每个对象有个年龄计数器,在survivor每次经过一次Minor GC,就会加一,默认15的时候,会分到老年代

4、第三条不是绝对的,VM会动态判断

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

MaxTenuringThreshold要求的年龄

空间担保失败

  发生Minor GC之前,虚拟机会检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果成立,可以确保Minor GC是安全的,else

检查HandlerPromotionFailure设置值是否允许担保失败。if允许,继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小

,true,查实进行一次Minor GC,尽管有风险,如果小于,或者参数设置不允许,改为进行一次Full GC但是HandlerPromotionFailure在jdk 6

Update 24没有作用了,jdk规则变成只要老年代的连续空间大于新生代对象总大小或者历次晋升的平均大小就会进行Minor GC,否则进行Full GC