垃圾回收算法

标记-复制

缺点:内存利用率低,有一块区域无法使用。

标记-清除

缺点:

1. 效率问题 (如果需要标记的对象太多,效率不高)

2. 空间问题(标记清除后会产生大量不连续的碎片)

标记-整理

分代收集

        根据对象存活周期的不同将内存分为几块。一般将java堆分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。

        当前虚拟机的垃圾收集都采用分代收集算法。

垃圾回收器

新生代和老年代使用的垃圾收集器的组合:

Serial收集器

(-XX:+UseSerialGC -XX:+UseSerialOldGC)

特点:

  • 新生代采用复制算法,老年代采用标记-整理算法。
  • "Stop The World"
  • 简单而高效(与其他收集器的单线程相比)

Serial Old收集器是Serial收集器的老年代版本,使用场景:

  • 在JDK1.5以及以前的版本中与Parallel Scavenge收集器搭配使用
  • 另一种用途是作为CMS收集器的后备方案

Parallel Scavenge收集器

(-XX:+UseParallelGC(年轻代),-XX:+UseParallelOldGC(老年代))

  • 新生代采用复制算法,老年代采用标记-整理算法。Serial收集器的多线程版本。
  • "Stop The World"
  • 默认的收集线程数跟cpu核数相同,当然也可以用参数(-XX:ParallelGCThreads)指定收集线程数,但是一般不推荐修改

与其他垃圾收集器比较:

  • Parallel收集器关注点是吞吐量(用户线程的高效率的利用CPU)(缩短垃圾回收时间)。
  • CMS等垃圾收集器的关注点更多的是用户线程的停顿时间(提高用户体验)(stop the world时间)

Parallel Old收集器是Parallel Scavenge收集器的老年代版本,,使用场景:

在注重吞吐量以及CPU资源的场合,都可以优先考虑 Parallel Scavenge收集器和Parallel Old收集器(JDK8默认的新生代和老年代收集器)。

ParNew收集器

(-XX:+UseParNewGC)

  • 新生代采用复制算法
  • 跟Parallel收集器很类似, 但是它只能用于新生代,和CMS收集器配合使用(Parallel收集器不能和CMS配合使用)

CMS收集器

(-XX:+UseConcMarkSweepGC(old))

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。它非常符合在注重用户体验的应用上使用,它是HotSpot虚拟机第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程(基本上)同时工作。基于“标记-清除”算法实现。

标记过程:

1. 初始标记:

stop the world, 并记录下gc roots直接能引用的对象(根对象),速度很快。

2. 并发标记:

从GC Roots的直接关联对象开始遍历整个对象图的过程, 这个过程耗时较长但是不需要停顿用户线程。因为用户程序继续运行,可能会有导致已经标记过的对象状态发生改变。出现多标注或者漏标注。

多标注会出现浮动垃圾,可以接受,可以在下一次的gc时回收掉; 漏标注很严重,未标注的会被垃圾回收,JVM不能回收还被引用着的对象。

3. 重新标记:

stop the world,修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录(主要是处理漏标问题),这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短。主要用到三色标记里的 增量更新算法。

4. 并发清理:

开启用户线程,同时GC线程开始对未标记的区域做清扫。这个阶段如果有新增对象会被标记为黑色不做任何处理。

5. 并发重置:

重置本次GC过程中的标记数据

CMS优点:并发收集、低停顿

CMS缺点:

  1. 对CPU资源敏感(会和服务(用户线程)抢资源);
  2. 无法处理浮动垃圾(在并发标记和并发清理阶段又产生垃圾,这种浮动垃圾只能等到下一次gc再清理了);
  3. 标记-清除算法会导致收集结束时会有大量空间碎片产生,当然通过参数-XX:+UseCMSCompactAtFullCollection可以让jvm在执行完标记清除后再做整理
  4. 执行过程中的不确定性。会存在上一次垃圾回收还没执行完,然后垃圾回收又被触发的情况,特别是在并发标记和并发清理阶段会出现。因为是 一边回收,系统一边运行,也许没回收完就再次触发full gc,也就是"concurrent mode failure",此时会进入stop the world,用serial old垃圾收集器来回收

CMS的相关核心参数

1. -XX:+UseConcMarkSweepGC:启用cms

2. -XX:ConcGCThreads:并发的GC线程数

3. -XX:+UseCMSCompactAtFullCollection:FullGC之后做压缩整理(减少碎片)

4. -XX:CMSFullGCsBeforeCompaction:多少次FullGC之后压缩一次,默认是0,代表每次FullGC后都会压缩一次

5. -XX:CMSInitiatingOccupancyFraction: 当老年代使用达到该比例时会触发FullGC(默认是92,这是百分比)

6. -XX:+UseCMSInitiatingOccupancyOnly:只使用设定的回收阈值(-XX:CMSInitiatingOccupancyFraction设定的值),如果不指定,JVM仅在第一次使用设定值,后续则会自动调整

7. -XX:+CMSScavengeBeforeRemark:在CMS GC前启动一次minor gc,降低CMS GC标记阶段(也会对年轻代一起做标记,如果在minor gc就干掉了很多对垃圾对象,标记阶段就会减少一些标记时间)时的开销,一般CMS的GC耗时 80%都在标记阶段

8. -XX:+CMSParallellnitialMarkEnabled:表示在初始标记的时候多线程执行,缩短STW

9. -XX:+CMSParallelRemarkEnabled:在重新标记的时候多线程执行,缩短STW;

JVM参数优化

亿级流量电商系统如何优化JVM参数设置(ParNew+CMS)

对于8G内存,我们一般是分配4G内存给JVM,正常的JVM参数配置如下:

-Xms : 堆初始内存大小

-Xmx:最大堆内存大小

-Xmn : 新生代初始和最大值

-Xss : 每个线程的栈大小

-XX:MetaspaceSize : 初始元空间大小

-XX:MaxMetaspaceSize : 最大元空间大小

-XX:SurvivorRatio : 设置 Eden 区与 Survivor 区的比例

-Xms3072M -Xmx3072M

-Xss1M

-XX:MetaspaceSize=256M

-XX:MaxMetaspaceSize=256M

-XX:SurvivorRatio=8

这样的配置, young =1G  (eden = 819M , survivor = 102M)   old = 2048M= 2G

每秒60M垃圾, 大约819 / 60 = 14s占满eden, 触发minor gc

优化 : 修改JVM参数

-Xms3072M -Xmx3072M -Xmn2048M

-Xss1M

-XX:MetaspaceSize=256M

-XX:MaxMetaspaceSize=256M

-XX:SurvivorRatio=8

这样的配置, young =2G  (eden =1638M , survivor = 204M)   old = 1G

每秒60M垃圾, 大约1638 / 60 = 27s占满eden, 触发minor gc

JVM优化手段:

1. 无非就是让短期存活的对象尽量都留在survivor里,不要进入老年代,这样在minor gc的时候这些对象都会被回收,不会进到老年代从而导致full gc。

2. 对象年龄应该为多少才移动到老年代比较合适:大多数对象一般在几秒内就会变为垃圾,完全可以将默认的15岁改小一点,比如改为5, 从而减少survivor区的占用。

// 修改移动到老年代判断的标准

-XX:MaxTenuringThreshold=5

-XX:PretenureSizeThreshold=1M    直接晋升老年代的对象大小阈值

3. 如果JVM内存超过4G, 就不适合JDK8默认的垃圾收集器(Parallel Scavenge收集器和Parallel Old收集器), 考虑使用ParNew + CMS收集器。

参数配置:

-Xms3072M -Xmx3072M -Xmn2048M

-Xss1M

-XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M

-XX:SurvivorRatio=8

-XX:MaxTenuringThreshold=5

-XX:PretenureSizeThreshold=1M

-XX:+UseParNewGC

-XX:+UseConcMarkSweepGC // 启用CMS

-XX:CMSInitiatingOccupancyFraction=92

-XX:+UseCMSCompactAtFullCollection

-XX:CMSFullGCsBeforeCompaction=3

垃圾收集底层算法实现

三色标记

三色标记算法是把Gc roots可达性分析遍历对象过程中遇到的对象, 按照“是否访问过”这个条件标记成以下三种颜色:

1. 黑色: 表示对象已经被垃圾收集器访问过, 且这个对象的所有引用都已经扫描过。黑色表示这个对象是安全存活的。

所有引用: A->B->C, A->D, A的所有引用指的是B和D, 并不包括C.

2. 灰色: 表示对象已经被垃圾收集器访问过, 但这个对象上至少存在一个引用还没有被扫描过。

3. 白色: 表示对象尚未被垃圾收集器访问过。 显然在可达性分析刚刚开始的阶段, 所有的对象都是白色的, 若在分析结束的阶段,仍然是白色的对象, 即代表不可达。

浮动垃圾(多标了存活对象)

        并发标记过程中,被标记了的对象有被用户线程销毁,这部分本应该回收但是没有回收到的内存,被称之为“浮动垃圾”。浮动垃圾并不会影响垃圾回收的正确性,只是需要等到下一轮垃圾回收中才被清除。

        另外,针对并发标记(还有并发清理)开始后产生的新对象,通常的做法是直接全部当成黑色。

读写屏障(漏标了存活对象)

        漏标会导致被引用的对象被当成垃圾误删除,这是严重bug,必须解决,有两种解决方案: 增量更新(Incremental Update) 和原始快照(Snapshot At The Beginning,SATB)。

漏标分析:

在并发标记的过程中,如果出现b.d = null, a.d = D, 就会变成右边的引用链。

按照三色标记算法,A是黑色,表示A已经扫描完成不会再被扫描,此时D并不是垃圾,但是GC root可达性分析并没有标记出D,D是会被回收的,这就是漏标,这是不可接受的。

1. 增量更新(Incremental Update

        关注增量的引用。当黑色对象插入新的指向白色对象的引用关系时, 就将这个新插入的引用记录用一个集合记录下来, 等并发扫描结束之后, 再将这些记录过的引用关系中的黑色对象为根, 重新扫描一次(重新标记阶段)。 这可以简化理解为, 黑色对象一旦新插入了指向白色对象的引用之后,它就变回灰色对象了

        通过写屏障实现。

2. 原始快照(Snapshot At The Beginning,SATB)

        关注删除引用。当灰色对象要删除指向白色对象的引用关系时, 就将这个要删除的引用 记录下来(b.d = null , 那么将d记录到集合中), 在并发扫描结束之后, 再将这些记录过的引用关系中的灰色对象为根, 重新扫描一次,这样就能扫描到白色的对象,将白色对象直接标记为黑色(目的就是让这种对象在本轮gc清理中能存活下来,待下一轮gc的时候重新扫描,这个对象也有可能是浮动垃圾)

        通过写屏障实现。

写屏障

所谓的写屏障,其实就是指在赋值操作前后,加入一些处理(可以参考AOP的概念):

void oop_field_store(oop* field, oop new_value) {pre_write_barrier(field); // 写屏障-写前操作*field = new_value;post_write_barrier(field, value); // 写屏障-写后操作
}

读屏障

oop oop_field_load(oop* field) {pre_load_barrier(field); // 读屏障-读取前操作return *field;
}

对于读写屏障,以Java HotSpot VM为例,其并发标记时对漏标的处理方案如下:

CMS:写屏障 + 增量更新

G1Shenandoah:写屏障 + SATB

ZGC:读屏障

为什么G1用SATB?CMS用增量更新?

简单理解:SATB相对增量更新效率会高(当然SATB可能造成更多的浮动垃圾),因为不需要在重新标记阶段再次深度扫描被删除引用对象,而CMS对增量引用的根对象会做深度扫描,G1因为很多对象都位于不同的region,CMS就一块老年代区域,重新深度扫描对象的话G1的代价会比CMS高,所以G1选择SATB不深度扫描对象,只是简单标记,等到下一轮GC再深度扫描。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/93287.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/93287.shtml
英文地址,请注明出处:http://en.pswp.cn/pingmian/93287.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

科研工具的一些注意事项

Origin Origin导入数据之后,可以考虑 [删除数据连接器…] 导入数据之后,删除数据连接,这样当原来的文件移动之后,就不影响origin文件里面的数据。不然就会出现空白数据:当然,没有数据了也可以加载出来&…

美国服务器环境下Windows容器工作负载智能弹性伸缩

在北美数据中心加速数字化转型的今天,企业客户日益重视Windows容器工作负载的智能化管理。本文将深入探讨基于Azure Stack HCI(混合云基础设施)的弹性伸缩方案如何突破传统资源调度瓶颈,通过分析指标收集、策略配置、混合云联动三…

欧姆龙CP系列以太网通讯实现上位机与触摸屏监控

一、行业痛点在现代工业生产中,自动化生产线的控制系统的高效性与智能化程度对生产效率和产品质量有着至关重要的影响。然而,许多传统工业生产线中使用的欧姆龙CP系列系列PLC以太网模块,由于自身设计原因,并未配备以太网接口&…

【大语言模型 00】导读

【大语言模型00】导读:你的LLM全栈工程师进阶之路关键词:大语言模型、LLM、Transformer、深度学习、AI工程化、全栈开发、技术路线图摘要:这是一份完整的大语言模型学习指南,涵盖从数学基础到商业落地的200篇深度文章。无论你是AI…

Business Magic

题目描述There are n stores located along a street, numbered from 1 to n from nearest to farthest. Last month, the storek had a net profit of rk . If rk is positive, it represents a profit of rk dollars; if rk is negative, it represents a loss of −rk dolla…

在ubuntu系统上离线安装jenkins的做法

作者:朱金灿 来源:clever101的专栏 1.安装java环境和下载war包: Jenkins 依赖于 Java 环境(OpenJDK 11 或更高版本): # 安装OpenJDK 11和字体依赖 sudo dpkg -i openjdk-11-jre-headless_*.deb fontconfi…

图像相似度算法汇总及Python实现

下面整理了一些图像相似度算法,可根据不同的需求选择不同的算法,对每种算法进行了简单描述并给出Python实现: 1. 基于像素的算法: (1).MSE(Mean Squared Error):均方误差,通过计算两幅图像对应像素值差的平…

IO流与单例模式

单例模式 单例模式是指一个类只能有一个对象。 饿汉模式 在单例模式下,在程序开始(main函数运行前)的时候创建一个对象,这之后就不能再创建这个对象。 class HungryMan { public:static HungryMan* getinstance(){return &ins…

Java设计模式之依赖倒置原则使用举例说明

示例1:司机驾驶汽车 问题场景:司机类直接依赖奔驰车类,新增宝马车需修改司机类代码。 // 未遵循DIP class Benz { public void run() { /*...*/ } } class Driver { public void drive(Benz benz) { benz.run(); } } // 遵循DIP:…

【Docker】openEuler 使用docker-compose部署gitlab-ce

docker-compose配置 services:gitlab:image: gitlab/gitlab-ce:latestcontainer_name: gitlabrestart: alwayshostname: gitlab.example.comenvironment:GITLAB_OMNIBUS_CONFIG: |# Add any other gitlab.rb configuration here, each on its own lineexternal_url https://gi…

ElasticSearch 父子文档使用简记

一. ES parent-child 文档简介 ES 提供了类似数据库中 Join 联结的实现,可以通过 Join 类型的字段维护父子关系的数据,其父文档和子文档可以单独维护。 二. 父子文档的索引创建与数据插入 ES 父子文档的创建可以分为下面三步: 创建索引 M…

【Linux】编辑器vim的使用

目录 1. vim的基本概念 2. vim的基本使用 3. vim命令模式操作 3.1 移动光标 3.2 删除 3.3 复制 3.4 替换 3.5 撤销 3.6 更改 3.7 跳转 4. vim底行模式操作 4.1 列出行号 4.2 跳到文件中的某行 4.3 查找字符 4.4 保存文件 4.5 离开vim 1. vim的基本概念 Vim&…

《零基础掌握飞算Java AI:核心概念与案例解析》

前引:飞算科技是一家专注于企业级智能化技术服务的公司,核心领域包括AI、大数据、云计算等。其Java AI解决方案主要面向企业级应用开发,提供从数据处理到模型部署的全流程支持!飞算Java AI是一款基于人工智能技术的Java开发辅助工…

Chrome腾讯翻译插件transmart的安装

文章目录一、官网地址二、安装过程1. 下载插件2. 解压crx3, chrome安装三、如何使用一、官网地址 腾讯翻译插件官网 二、安装过程 1. 下载插件 点击上面的官网地址,下拉到如图所示chrome插件位置,点击立即下载 2. 解压crx 从压缩文件中解压出crx文…

IOMMU的2级地址翻译机制及多级(2~5)页表查找

IOMMU的2级地址翻译机制及多级(2~5)页表查找 摘要:IOMMU是现代计算机系统中用于I/O设备(如GPU、NIC、网络接口卡)的地址翻译和保护机制,类似于CPU的MMU(Memory Management Unit),但专为设备DMA(Direct Memory Access,直接内存访问)设计。它支持虚拟化环境(…

C++STL标准模板库详解

一、引言STL(Standard Template Library)是 C 标准库的核心组成部分,其中容器(Containers) 作为数据存储的基础组件,为开发者提供了丰富的数据结构选择。本文将聚焦 STL 容器的核心类型,结合具体…

神经网络 常见分类

📚 神经网络的常见分类方式可以从不同角度来划分,以下是几种主流思路,帮你快速梳理清晰:1️⃣ 按网络结构分类前馈神经网络(Feedforward Neural Network, FNN) 数据从输入层→隐藏层→输出层单向传递&#…

生产环境Redis缓存穿透与雪崩防护性能优化实战指南

生产环境Redis缓存穿透与雪崩防护性能优化实战指南 在当下高并发场景下,Redis 作为主流缓存组件,能够极大地提升读写性能,但同时也容易引发缓存穿透、缓存击穿及缓存雪崩等问题,导致后端依赖数据库的请求激增,系统稳定…

【洛谷刷题】用C语言和C++做一些入门题,练习洛谷IDE模式:分支机构(一)

🔥个人主页:艾莉丝努力练剑 ❄专栏传送门:《C语言》、《数据结构与算法》、C语言刷题12天IO强训、LeetCode代码强化刷题、洛谷刷题、C/C基础知识知识强化补充、C/C干货分享&学习过程记录 🍉学习方向:C/C方向 ⭐️人…

嵌入式硬件篇---常见的单片机型号

以下是目前常用的单片机型号及其应用场景、优劣势的详细解析,结合最新行业动态和技术特性,帮助你精准匹配需求:一、经典 8 位单片机:低成本入门首选1. 51 系列(代表型号:AT89C51、STC89C52)应用…