JAVA GC 分代垃圾收集过程及各种GC的特点_java series gc-程序员宅基地

技术标签: JAVA基础  

1 JVM性能相关的组件

在调优性能时,JVM的如下三个组件是重点关注的:

  • 堆:存储对象数据的地方,该区域由启动时选择的垃圾收集器管理。大多数调优选项都与堆大小设置与选择的垃圾回收器相关
  • JIT编译器:对性能也有很大的影响,但是很少需要对新版本的JVM进行调优。
  • GC【垃圾回收器】:需要根据业务,选择最适合的GC

在这里插入图片描述

2 分代垃圾收集

2.1 垃圾收集算法

常见的垃圾收集算法大致分为以下两种:引用计数法和可达性分析算法,目前虚拟机都采用可达性算法

引用计数法

基本思路:每一个对象上记录这个对象被引用的次数,只要有任何一个对象引用了次对象,这个对象的计数器就+1,取消对这个对象的引用时,计数器就-1。任何一个时刻,如果该对象的计数器为0,那么这个对象就是可以回收的。但是这种算法有个弊端:无法处理循环引用的情况,见下图

在这里插入图片描述
在栈上,在method中执行完两个set后,method方法结束,图中两条红线引用消失,可以看到留下两个对象在堆内存中循环引用,但此时已经没有地方在用他们了,造成内存泄漏。两个对象不能被GC回收

class A {
    
    private B b;
    public void setB(B b) {
    
        this.b = b;
    }
}

class B {
    
    private A a = new A();
    public void setA(A a) {
    
        this.a = a;
    }
}

public void method() {
    
    A a = new A();
    B b = new B();
    a.setB(b);
    b.setA(a);
}

可达性分析算法

在Java中,是通过可达性分析(Reachability Analysis)来判定对象是否存活的。
该算法的基本思路就是通过一些被称为引用链(GC Roots)的对象作为起点,从这些节点开始向下搜索,搜索走过的路径被称为(Reference Chain),当一个对象到GC Roots没有任何引用链相连时(即从GC Roots节点到该节点不可达),则证明该对象是不可用的。
常见的GC Roots有如下几种:
- 虚拟机栈(栈帧中的本地变量表)中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中JNI(即一般说的Native方法)引用的对象
- 被同步锁持有的对象。

在这里插入图片描述
从图上看出,对象实例1、2、4、6都具有GC Roots可达性,即存活对象,不能被GC回收。
而对于对象实例3、5虽然相互引用,但没有任何一个GC Roots与之相连,属于GC Roots不可达对象,即GC需要回收的垃圾对象。

2.2 分代垃圾收集过程

  1. 任何新对象都被分配到eden空间。两个survivor空间在启动时都是空的。
    在这里插入图片描述
  2. 当eden空间填满时,会触发一个小的垃圾收集 Young GC
    在这里插入图片描述
  3. 被引用的对象被移动到第一个survivor空间。清除eden空间后,未被引用的对象将被删除。
    在这里插入图片描述
  4. 在下一个 minor GC中,伊甸园空间也发生了同样的事情。未引用的对象被删除,引用的对象被移动到幸存者空间。但是它们被移动到第二个幸存者空间(S1)。此外,来自第一个survivor空间(S0)的上一次minor GC的对象的年龄增加并移动到S1。一旦所有幸存的对象被移动到S1, S0和eden都将被清除。注意现在在幸存者空间中有不同年龄的对象。
    在这里插入图片描述
  5. 在下一个minor GC中,重复相同的过程。然而,这一次幸存者空间切换。引用的对象被移动到S0。保存下来的对象年龄增加。eden和S1被清除。
    在这里插入图片描述
  6. 在一次minor GC之后,当老化对象达到某个年龄阈值(如8)时,它们将从年轻代提升到年老代。
    可通过如下2个参数控制对象何时提升到老年代:
    • -XX:PreternureSizeThreshold 直接晋升老年代的对象大小,设置了这个参数后,大于这个参数的对象直接在老年代进行分配。
    • -XX:MaxTenuringThreshold 晋升老年代的对象年龄,对象在每一次Minor GC后年龄增加一岁,超过这个值后进入到老年代。默认值是15。
      在这里插入图片描述
  7. 随着minor gc继续发生,对象将继续被提升到老的代空间。
    在这里插入图片描述
  8. 这几乎涵盖了年轻一代的整个过程。最后,将对老的代执行一次大的GC,以清除和压缩该空间。
    在这里插入图片描述

2.3 minor GC与major GC

在这里插入图片描述

3 垃圾回收器

3.1 Serial GC

Serial GC是JAVA 5、6中默认的GC,minor gc和 major gc都是串行完成的(使用单个虚拟CPU)。
Serial GC使用标记-压缩收集方法,此方法将旧内存移到堆的开头,以便在堆的末尾将新内存分配到单个连续内存块中。这种内存压缩使得向堆分配新内存块的速度更快。
Serial GC是最基本、历史最悠久的收集器,它是一个单线程收集器。一旦回收器开始运行时,整个系统都要停止。Client模式下默认开启,其他模式默认关闭。

在这里插入图片描述
用法:
-XX:+UseSerialGC
这个参数会使新生代和老年代都使用串行回收器,新生代使用复制算法,老年代使用标记-整理算法。

-XX:+UseParNewGC
ParNew收集器是Serial收集器的多线程版本,在新生代进行并行回收,老年代仍旧使用串行回收。新生代S区任然使用复制算法。
操作系统是多核CPU上效果明显,单核CPU建议使用串行回收器。打印GC详情时ParNew标识着使用了ParNewGC回收器。默认关闭。

3.2 Parallel GC

Parallel GC使用多个线程执行年轻代垃圾收集,是JVM中GC的默认实现,也称为吞吐量收集器。
并行收集器适用于在多处理器或多线程硬件上运行的具有中型到大型数据集的应用程序。

运行并行GC还会导致“停止世界事件”,并导致应用程序冻结,当需要完成大量工作且可以接受长时间暂停时,可以使用它,例如运行批处理作业。
在这里插入图片描述
吞吐量:CPU用于运行用户代码的时间与CPU总消耗时间的比值,即吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间),虚拟机运行100分钟,垃圾收集花费1分钟,那吞吐量就99%

默认情况下,在具有N个cpu的主机上,并行垃圾收集器在收集中使用N个垃圾收集器线程。垃圾收集器线程的数量可以通过命令行选项来控制: -XX:ParallelGCThreads= = <所需数量>

-XX:+UseParallelGC

表示新生代使用Parallel收集器,并提供如下参数用于控制吞吐量:

  • -XX:MaxGCPauseMillis:设置最大停顿时间,值是一个大于0的毫秒数,收集器将尽力保证垃圾回收时间不超过设定值,系统运行的需要回收的垃圾总量是固定的,缩短停顿时间的同时会增大回收频度。
  • -XX:GCTimeRatio:用户控制垃圾回收时间占比,它运行的参数值是0-100的整数,如果参数设置为19,代表最大GC时间占总时间的5%(1/(1+19))。
  • -XX:UseAdaptiveSizePolicy:JVM会根据实际运行情况动态调整新生代大小、新生代和s区比例、晋升老年代对象大小等细节参数。

-XX:+UseParallelOldGC

新生代和老年代都使用并行收集器。打印出的GC会带PSYoungGen、ParOldGen关键字。
HotSpot只在老一代中进行压缩。HotSpot中的年轻一代被认为是一个拷贝收集器;因此不需要压缩。
这是自Java 7u4以来并行GC的默认版本

Compacting describes the act of moving objects in a way that there are no holes between objects. After a garbage collection sweep, there may be holes left between live objects. Compacting moves objects so that there are no remaining holes. It is possible that a garbage collector be a non-compacting collector. Therefore, the difference between a parallel collector and a parallel compacting collector could be the latter compacts the space after a garbage collection sweep. The former would not.

3.3 CMS

Concurrent Mark Sweep 并发标记清除,即使用CMS收集器 ConcMarkSweepGC
它试图通过与应用程序线程并发执行大部分垃圾收集工作来最小化由于垃圾收集而导致的暂停。通常并发低暂停收集器不会复制或压缩活动对象。垃圾收集是在不移动活动对象的情况下完成的。如果碎片成为一个问题,分配一个更大的堆。

它使用的是 标记清除算法,运作过程为四个步骤,分别是 初始标记—并发标识—重新标记—并发清除。它是老年代的收集算法,新生代使用ParNew收集算法。默认关闭

CMS的缺点对服务器CPU资源较为敏感,在并发标记时会降低吞吐量。它使用的标记清除算法也会产生大量空间碎片,空间碎片的存在会加大Full GC的频率,虽然老年代还有足够的内存,但是因为内存空间连续,不得不进行Full GC。

CMS收集器比其他gc使用更多的CPU。如果可以分配更多的CPU以获得更好的性能,那么CMS垃圾收集器是比并行收集器更好的选择。CMS GC中不执行压缩。
在这里插入图片描述

通过 -XX:+UseConcMarkSweepGC 设置使用CMS后,可以通过以下参数调整CMS的行为:

  • -XX:+ UseCMSCompactAtFullCollection Full GC后,进行一次整理,整理过程是独占的,会引起停顿时间变长。仅在使用CMS收集器时生效。
  • -XX:ParallelCMSThreads 设置并行GC时进行内存回收的线程数量

3.4 G1

Java 7中提供,用于替换CMS收集器。G1是一种并行、并发、递增压缩的低暂停垃圾收集器,它的布局与前面描述的其他垃圾收集器截然不同。

它像CMS一样是并行和并发的,但是它的工作原理与较老的垃圾收集器非常不同。

虽然G1也分代,但它没有针对年轻代、老年代的单独区域。相反,每个代都是一组区域,允许以灵活的方式调整年轻代的大小。

它将堆划分为一组大小相等的区域(1MB到32MB—取决于堆的大小),并使用多个线程扫描它们。在程序运行期间,一个区域可以是一个老区域,也可以是一个年轻区域。

标记阶段完成后,G1知道哪些区域包含最多的垃圾对象。如果用户对最短的暂停时间感兴趣,G1可以选择只疏散几个区域。如果用户不担心暂停时间,或者已经声明了一个相当大的暂停时间目标,G1可能会选择包含更多区域。

由于G1GC识别出垃圾最多的区域,并首先对该区域执行垃圾收集,因此它被称为Garbage First。
在这里插入图片描述
除了Eden, Survivor和Old内存区域,G1GC中还有另外两种类型的区域:

  • Humongous—用于大尺寸对象(大于堆大小的50%)
  • 可用空间-未使用或未分配的空间

使用G1垃圾收集器的JVM参数是**-XX:+UseG1GC**

3.5 Epsilon GC

Epsilon是一个不做任何事情(no-op)的垃圾收集器,作为JDK 11的一部分发布。它处理内存分配,但不实现任何实际的内存回收机制。一旦可用的Java堆耗尽,JVM就会关闭。

它可以用于超延迟敏感的应用程序,开发人员确切地知道应用程序的内存占用,甚至(几乎)拥有完全没有垃圾的应用程序。否则,不建议在任何其他场景中使用Epsilon GC。

使用Epsilon垃圾收集器的JVM参数是**-XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC**.

3.6 Shenandoah GC

Shenandoah是作为JDK 12的一部分发布的一个新的GC。Shenandoah相对于G1的关键优势是,它与应用程序线程并发执行更多的垃圾收集周期工作。G1只能在应用程序暂停时清空它的堆区域,而Shenandoah可以与应用程序同时重新定位对象。

Shenandoah可以压缩活动对象,清除垃圾,并在检测到空闲内存后几乎立即将RAM释放回操作系统。由于所有这些都是在应用程序运行时并发发生的,Shenandoah是CPU密集型的

使用Epsilon垃圾收集器的JVM参数是:-XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC

3.7 ZGC

ZGC是作为JDK 11的一部分发布的另一个GC,在JDK 12中得到了改进。它适用于需要低延迟(少于10毫秒的暂停)和/或使用非常大的堆(几万亿字节)的应用程序。

ZGC的主要目标是低延迟、可伸缩性和易用性。为了实现这一点,ZGC允许Java应用程序在执行所有垃圾收集操作时继续运行。默认情况下,ZGC未提交未使用的内存并将其返回给操作系统。

因此,通过提供极低的暂停时间(通常在2ms内),ZGC比其他传统gc带来了显著的改进。

Shenandoah和ZGC都计划成为产品特性,并在JDK 15中走出实验阶段

4 如何选择GC

如果应用程序没有严格的暂停时间要求,你应该只运行你的应用程序并允许JVM选择正确的收集器。
大多数情况下,默认设置应该可以正常工作。如果有必要,可以调整堆大小以提高性能。如果性能仍然不能满足您的目标,您可以根据应用程序的需求修改收集器:

GC 适用场景
Serial 如果应用程序有一个小的数据集(大约100 MB)和/或它将运行在没有暂停时间要求的单个处理器上
Parallel 如果峰值应用程序性能是优先级,并且没有暂停时间要求,或者一秒或更长时间的暂停是可以接受的
CMS/G1 如果响应时间比总体吞吐量更重要,那么垃圾收集暂停时间必须短于大约一秒
ZGC 如果响应时间具有高优先级,并且/或您正在使用一个非常大的堆

5. 配置参数

  • 获取到完整的机器名 -Djava.net.preferIPv4Stack=true
  • 打印GC日志 -Xloggc:/var/log/hive/hiveserver2-gc-%t.log -XX:+UseG1GC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCCause -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=10M
  • 打印OOM日志 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/hive/hs2_heapdump.hprof

参考

Java Garbage Collection Basics
Garbage Collection in Java – What is GC and How it Works in the JVM

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/penriver/article/details/118944545

智能推荐

while循环&CPU占用率高问题深入分析与解决方案_main函数使用while(1)循环cpu占用99-程序员宅基地

文章浏览阅读3.8k次,点赞9次,收藏28次。直接上一个工作中碰到的问题,另外一个系统开启多线程调用我这边的接口,然后我这边会开启多线程批量查询第三方接口并且返回给调用方。使用的是两三年前别人遗留下来的方法,放到线上后发现确实是可以正常取到结果,但是一旦调用,CPU占用就直接100%(部署环境是win server服务器)。因此查看了下相关的老代码并使用JProfiler查看发现是在某个while循环的时候有问题。具体项目代码就不贴了,类似于下面这段代码。​​​​​​while(flag) {//your code;}这里的flag._main函数使用while(1)循环cpu占用99

【无标题】jetbrains idea shift f6不生效_idea shift +f6快捷键不生效-程序员宅基地

文章浏览阅读347次。idea shift f6 快捷键无效_idea shift +f6快捷键不生效

node.js学习笔记之Node中的核心模块_node模块中有很多核心模块,以下不属于核心模块,使用时需下载的是-程序员宅基地

文章浏览阅读135次。Ecmacript 中没有DOM 和 BOM核心模块Node为JavaScript提供了很多服务器级别,这些API绝大多数都被包装到了一个具名和核心模块中了,例如文件操作的 fs 核心模块 ,http服务构建的http 模块 path 路径操作模块 os 操作系统信息模块// 用来获取机器信息的var os = require('os')// 用来操作路径的var path = require('path')// 获取当前机器的 CPU 信息console.log(os.cpus._node模块中有很多核心模块,以下不属于核心模块,使用时需下载的是

数学建模【SPSS 下载-安装、方差分析与回归分析的SPSS实现(软件概述、方差分析、回归分析)】_化工数学模型数据回归软件-程序员宅基地

文章浏览阅读10w+次,点赞435次,收藏3.4k次。SPSS 22 下载安装过程7.6 方差分析与回归分析的SPSS实现7.6.1 SPSS软件概述1 SPSS版本与安装2 SPSS界面3 SPSS特点4 SPSS数据7.6.2 SPSS与方差分析1 单因素方差分析2 双因素方差分析7.6.3 SPSS与回归分析SPSS回归分析过程牙膏价格问题的回归分析_化工数学模型数据回归软件

利用hutool实现邮件发送功能_hutool发送邮件-程序员宅基地

文章浏览阅读7.5k次。如何利用hutool工具包实现邮件发送功能呢?1、首先引入hutool依赖<dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.7.19</version></dependency>2、编写邮件发送工具类package com.pc.c..._hutool发送邮件

docker安装elasticsearch,elasticsearch-head,kibana,ik分词器_docker安装kibana连接elasticsearch并且elasticsearch有密码-程序员宅基地

文章浏览阅读867次,点赞2次,收藏2次。docker安装elasticsearch,elasticsearch-head,kibana,ik分词器安装方式基本有两种,一种是pull的方式,一种是Dockerfile的方式,由于pull的方式pull下来后还需配置许多东西且不便于复用,个人比较喜欢使用Dockerfile的方式所有docker支持的镜像基本都在https://hub.docker.com/docker的官网上能找到合..._docker安装kibana连接elasticsearch并且elasticsearch有密码

随便推点

Python 攻克移动开发失败!_beeware-程序员宅基地

文章浏览阅读1.3w次,点赞57次,收藏92次。整理 | 郑丽媛出品 | CSDN(ID:CSDNnews)近年来,随着机器学习的兴起,有一门编程语言逐渐变得火热——Python。得益于其针对机器学习提供了大量开源框架和第三方模块,内置..._beeware

Swift4.0_Timer 的基本使用_swift timer 暂停-程序员宅基地

文章浏览阅读7.9k次。//// ViewController.swift// Day_10_Timer//// Created by dongqiangfei on 2018/10/15.// Copyright 2018年 飞飞. All rights reserved.//import UIKitclass ViewController: UIViewController { ..._swift timer 暂停

元素三大等待-程序员宅基地

文章浏览阅读986次,点赞2次,收藏2次。1.硬性等待让当前线程暂停执行,应用场景:代码执行速度太快了,但是UI元素没有立马加载出来,造成两者不同步,这时候就可以让代码等待一下,再去执行找元素的动作线程休眠,强制等待 Thread.sleep(long mills)package com.example.demo;import org.junit.jupiter.api.Test;import org.openqa.selenium.By;import org.openqa.selenium.firefox.Firefox.._元素三大等待

Java软件工程师职位分析_java岗位分析-程序员宅基地

文章浏览阅读3k次,点赞4次,收藏14次。Java软件工程师职位分析_java岗位分析

Java:Unreachable code的解决方法_java unreachable code-程序员宅基地

文章浏览阅读2k次。Java:Unreachable code的解决方法_java unreachable code

标签data-*自定义属性值和根据data属性值查找对应标签_如何根据data-*属性获取对应的标签对象-程序员宅基地

文章浏览阅读1w次。1、html中设置标签data-*的值 标题 11111 222222、点击获取当前标签的data-url的值$('dd').on('click', function() { var urlVal = $(this).data('ur_如何根据data-*属性获取对应的标签对象

推荐文章

热门文章

相关标签