JVM基础(5)——JVM垃圾回收算法

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO

联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬

学习必须往深处挖,挖的越深,基础越扎实!

阶段1、深入多线程

阶段2、深入多线程设计模式

阶段3、深入juc源码解析

阶段4、深入jdk其余源码解析

阶段5、深入jvm源码解析

一、简介

我们在前两章中,已经讲解了JVM垃圾回收的基本流程和对象存活判定的算法,但是,并没有深入垃圾回收内部的细节。本章,我们就深入垃圾回收的内部,看看JVM到底是如何进行对象内存的回收的。

二、复制算法

复制算法,主要用于 新生代 中对象的回收。其基本思路就是:将新生代内存按划分为大小相等的两块,每次只使用其中的一块,当一块内存用完了, 将存活的对象移动到另外一块上面 ,然后在把已使用过的内存空间一次清理掉。

2.1 算法流程

我们以示例代码来看复制算法的执行流程:

    public class Kafka {
    
        public static void main(String[] args) throws InterruptedException {
            loadReplicaFromDisk();
        }
    
        private static void loadReplicaFromDisk() {
            ReplicaManager replicaManager = new ReplicaManager();
            replicaManager.load();
        }
    }

假设程序执行到replicaManager.load(),JVM的内存数据结构如下,其中“大量垃圾对象无人引用”表示其它程序产生的垃圾对象,此时新生代中用于分配对象内存的区域也快满了,再次为对象分配内存时就会触发“Minor GC”:

此时,一种最基本的思路就是,首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。但是这种方法问题也很明显: 产生大量内存碎片,导致后续需要为大对象分配空间时内不足 。所以,JVM很少用这种方法,而是先将存活对象转移到另一块区域:

然后一次性把原来那块内存清空:

上述整个流程,就是所谓的复制算法: 把新生代划分为两块内存区域,只使用其中一块,当这块快满的时候,把存活对象一次性转移到另一块,保证没有内存碎片,然后清空原来那块,依次循环往复 。

2.2 算法优化

上述复制算法的缺点很明显:即 对内存的使用效率太低 。比如我们给新生代分配了1G内存,那其实只有512MB是实际使用的,很浪费内存空间。那么如何来优化呢?

我们回顾下 新生代中对象的特点:朝生暮死,也就是说新生代的绝大多数对象在经历1次GC后就会被回收掉,存活率非常低。

根据这个特点, HotSpot VM 采用了一种做法,把新生代区域划分成了三块: 1个Eden区(80%),2个Survivor区(各占10%) ,最开始,对象只在Eden进行分配:

如果Eden区快满了,此时触发GC会将Eden区中的存活对象转移到其中一块Survivor中,同时清空Eden:

下一次再分配空间时,依然在Eden区分配,然后触发GC,将Eden的存活对象和上一次使用的Survivor中的存活对象转移到另一块空白Survivor中,然后清空Eden和使用过的Survivor,循环往复。

这种内存划分方式的最大好处就是只有10%的空间是闲置的,无论是垃圾回收的性能、内存碎片的控制、内存使用率,都非常好。

三、标记整理算法

通过前一节,大家应该已经了解了新生代的垃圾回收算法,本节我们就来看下老年代的垃圾回收算法——标记整理算法。

3.1 何时进入老年代

这里有一个问题,新生代的对象什么时候会进入老年代?先给出一个结论,一共有五种情况:

  • 新生代对象的年龄超过一定阈值(默认15);
  • 动态年龄判断
  • 大对象直接分配
  • Survivor区空间不足

年龄阈值

之前我们提到过,新生代中的对象每逃过一轮GC,年龄都会加1,到年龄达到15时(也可以通过JVM参数-XX:MaxTenuring Threshold设置),就会被转移到老年代:

动态年龄判断

动态对象年龄判断的规则是: Survivor区的存活对象年龄从小到大进行累加,当累加到 X 年龄时的总和大于 Survivor区空间的50%时,那么比X大的对象都会晋升到老年代。

举个例子,比如当前Survivor区的分布如下,累加结果45%小于50%:

此时新生代GC后,有6%的对象进入Survivor区,则Survivor区分布如下图:

这时从1岁加到4岁的对象总和51% 大于50%,但此时没有大于四岁的对象,即没有对象晋升 。此时再经过一次新生代GC后,又有40%的对象进入Survivor区,Survivor区分布如下图:


Survivor区的对象年龄从小到大进行累加,当累加到 3 年龄时的总和大于50%,那么比3大的都会晋升到老年代,即4岁的20%、5岁的20%晋升到老年代。

可以使用-XX:TargetSurvivorRatio来设置Survivor区空间的百分比,默认值是50

大对象

对于一些大对象,JVM会直接将其分配到老年代。通过参数-XX:PretenureSizeThrehold,可以设置阈值,单位为字节。

JVM之所以要这么做,是为了避免新生代中出现屡次逃过GC的大对象,大对象在新生代的Eden和Survivor区的来回复制开销比较大。

Survivor区空间不足

最后一种情况就是,Minor GC之后发现存活对象太多,没法放入另一块Survivor区域中,比如下面这种情况:

这时候就必须把这些对象全部迁移到老年代去:

3.2 空间分配担保

前面讨论了新生代的存活对象何时会转移到老年代,那么问题又来了,如果老年代区域的内存空间不足了怎么办?这里就涉及了 空间分配担保机制 。

所谓空间分配担保,指在执行任何一次Minor GC之前,JVM会检查 老年代的最大连续可用空间 是否大于 新生代所有对象的总大小 :

如果大于,说明这次Minor GC肯定是安全的,因为老年代可以容纳新生代中的所有对象;

如果小于,则 JVM 会查看-XX:HandlePromotionFailure参数值,这个参数值表示是否允许担保失败:

  • 如果允许(HandlePromotionFailure==true),则看下 老年代的最大连续可用空间 是否大于 历次Minor GC后进入老年代的对象平均大小 。如果大于,就进行minior GC,如果这次Minior GC失败了,就会进行FULL GC(所谓FULL GC,就是既对老年代进行垃圾回收,也对新生代进行垃圾回收);如果小于,先进行FULL GC,再Minor GC。
  • 如果不允许(HandlePromotionFailure==false),则直接触发FULL GC,然后再进行一次Minor GC。

如果经过上面的操作,老年代可用空间最后发现还是不够,就会导致所谓的OOM内存溢出了。

总之,空间分配担保机制的核心目的就是 避免频繁FULL GC,能先预判就先预判 ,实在不行才FULL GC,因为FULL GC的开销非常大,既要对老年代进行回收,也要对新生代进行回收。

3.3 算法流程

了解了新生代对象何时进入老年代,以及FULL GC的触发时机,我们就可以来看下老年代的 标记整理算法 的流程了。标记整理算法,其实就是先标记存活对象,然后将存活对象都向内存端边界移动,然后清理掉端边界以外的内存,这样就可以避免出现大量内存碎片。

我们通过示例来看下,假设JVM当前的内存状态如下,老年代中散落着各种存活对象:

接着,会将存活对象都往内存的一边移动,让它们尽量紧凑,然后一次性把垃圾对象清理掉:

四、线上示例

通过前两节的讲解,相信读者已经对新生代的复制算法、老年代的标记整理算法有所了解,本节我们将通过一个生产系统的GC案例,让大家更加透彻的理解JVM中如果进行对象分配和老年代转移,以及Minor GC和Full GC的全过程。

4.1 背景

假设现在生产环境有一套“数据计算系统”,不停地从MySQL等各类数据源提取数据到内存中进行计算,系统是分布式的。每个节点(机器)每分钟执行100次操作(提取数据并计算,每次操作耗时10s),每次操作1万条数据,每条数据大小为1KB左右,那么每次数据的总大小就是10MB:

每台机器的配置是4核8G,JVM分配4G内存,其中新生代1.5G,老年代1.5G。

整个系统的初始背景大致就是上面这样,下面来分析可能存在的各种问题。

4.2 频繁Full GC

我们先来看下新生代的空间什么时候会被占满,按照8:1:1来分配Eden和Survivor区,如下图:

每执行一次操作,Eden区就会填充10MB数据,一分钟执行100次操作就是1000MB,所以 Eden区基本上1分钟左右就会被占满 。再执行操作时,就会进行Minor以回收一部分的垃圾对象:

首先,检查老年代的连续可用内存空间是否足够(即大于新生代中的所有存活对象大小),如下图,老年代目前是空的,1.5G的可用内存空间可以容纳Eden区中的1.2G对象,所以会直接进行Minor GC:

那么, 此时Eden区中有多少对象还是存活的呢? 之前说了每次操作耗时10s,那么在1分钟内的最后10s时,前面0-50s的任务已经执行完了,1分钟操作100个任务,所以大约有1/6的任务还没有执行完毕,即大约还有20个任务在计算中(大约200MB对象存活):

其实线上一般是通过GC日志去分析存活对象的大小的,GC日志中清楚的记录了每次Minor GC进入到老年代的对象大小(后面我们会详细讲解如何看懂GC日志),根据我们的线上日志分析,大约也还有200MB对象是存活的。

注意,每一块Survivor区的大小只有100MB,所以是无法容纳200MB的存活对象的,所以会通过空间担保机制,转移到老年代中,并清空Eden区,此时JVM内存空间结构如下:

由于每分钟老年代都被填充200MB存活对象,所以到第3分钟结束时,老年代已经有400MB空间被占满,且Eden区也被占满,此时如果要进行Minor GC,会怎么样呢?

首先,依然检查老年代的连续可用内存空间是否足够(即大于新生代中的所有存活对象大小),此时发现空间是不够的,老年代只有1.1GB可用,而新生代的所有对象大小有1.2GB。

此时,就会判断是否开启了空间担保机制——即判断HandlePromotionFailure是否为true,如果开启了(一般生产环境都会开启),就会看下历代晋升到老年代的对象大小是否小于老年代可用空间,根据之前的计算,历代晋升到老年代的对象大小约为200MB,小于1.1GB,所以JVM就会放心的进行一次Minor GC,此时又有200MB对象进入到老年代。

重复上述过程,大约经过8分钟,经历7次Minor GC后,JVM内存空间结构如下,此时老年代剩余可用空间大约100MB,Eden区已被占满:

此时又会进行Minor GC前的检查,但是老年代的可用空间已经比历代晋升到老年代的对象空间小了,所以会 直接触发一次Full GC ,将老年代中的垃圾对象回收(假设此时老年代中的对象全部都可回收):

然后紧接着再进行一次Minor GC,将Eden区中的200MB存活对象转移到老年代:

按照上述这个模型, 基本上8分钟左右就会触发一次Full GC ,这个频率对于生产环境是不可接受的,因为Full GC会严重影响系统性能,这个后面章节我们会详细讲解。

4.3 优化

那么该如何进行优化呢?最基本的思路就是 增加Survivor的内存大小 ,因为正是Survivor区不能容纳存活对象(200MB)导致必须晋升到老年代。所以重新分配新生代大小为2G,老年代为1G,同时改变Eden和Survivor的空间比例,这样Survivor区就能容纳每次Minor GC后的存活对象,如下图:

比如经过一段时间,JVM内存结果如下,Eden区被占满,Survivor1区有200MB上一轮Minor GC后的存活对象:

然后此时进行Minor GC,会先清理到S1区中的所有对象,然后将Eden区中的存活对象(200MB)转移到S2区:

这样,基本上就很少会有对象进入到老年代,Full GC的频率能降低到几小时一次。

五、总结

最后来总结下本章的内容,本章主要介绍了新生代的复制算法和老年代的标记整理算法的流程,重点需要掌握的是以下几点:

  1. 新生代对象何时会进入老年代?
  2. 何时会触发新生代的Minor GC?
  3. 何时会触发FULL GC?
  4. 空间分配担保机制的作用是什么?

同时,本章也给出了一个线上示例,帮助读者更好的理解JVM分代垃圾回收的整个流程。下一章开始,我们将详细介绍各种垃圾回收器,看看它们内部是如何运用GC算法进行垃圾回收的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/310784.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

HarmonyOS应用开发学习笔记 UI布局学习 创建轮播(Swiper) artTS 轮播组件 简单使用

官方文档 Swiper组件提供滑动轮播显示的能力。Swiper本身是一个容器组件,当设置了多个子组件后,可以对这些子组件进行轮播显示。通常,在一些应用首页显示推荐的内容时,需要用到轮播显示的能力。 1、简单用法 loop 控制是否循环 …

TS 36.331 V12.0.0-过程(4)-测量

​本文的内容主要涉及TS 36.331,版本是C00,也就是V12.0.0。

SVG 绘制基本的几何图形

SVG 绘制基本的几何图形 简介SVG 是什么SVG 的优点一个简单的例子 SVG 基本图形矩形 rect圆形 circle椭圆 ellipse线条 line多边形 polygon折线 polyline路径 path 简介 SVG 是什么 SVG 全称为 Scalable Vector Graphics,即可缩放矢量图形。SVG 使用 XML 格式定义…

【OpenCV学习笔记08】- 图像基本操作

关于 OpenCV 官方文档的 GUI功能告一段落,接下来开始核心操作的学习。学习笔记中会记录官方给出的例子,也会给出自己根据官方的例子完成的更改代码,同样彩蛋的实现也会结合多个知识点一起实现一些小功能,来帮助我们对学会的知识点…

leetcode:LCR 159. 库存管理 III(python3解法)

难度:简单 仓库管理员以数组 stock 形式记录商品库存表,其中 stock[i] 表示对应商品库存余量。请返回库存余量最少的 cnt 个商品余量,返回 顺序不限。 示例 1: 输入:stock [2,5,7,4], cnt 1 输出:[2]示例…

通过wireshark抓取的流量还原文件(以zip为例)

wireshark打开流量包,通过zip关键字查找 追踪流可查看详细信息 选中media Type右键, 点击导出分组字节流选项 将生成的文件进行命名,需要时什么格式就以什么格式后缀

uniapp 字母索引列表插件(组件版) Ba-SortList

简介(下载地址) Ba-SortList 是一款字母索引列表组件版插件,可自定义样式,支持首字母字母检索、首字检索、搜索等等;支持点击事件。 支持首字母字母检索支持首字检索支持搜索支持点击事件支持长按事件支持在uniapp界…

代币中的decimal精度代表了什么

精度的意义在于允许发送小数的代币。举例,一个CAT代币合约的精度为6。那么 你拥有1个CAT就意味着合约中的balance 1 * 10^6 , 转账 0.1CAT出去的话,就需要输入 0.1*10^6 10^5。 也就时在涉及代币时,查询到的余额、转账的代币数量 都和 代币…

扫描错题用什么软件?分享4个好用的工具!

在学习的道路上,我们难免会遇到错题。有些时候,手抄的笔记或是纸质错题集不易携带、查找不方便,还容易丢失。为了解决这些问题,现在有许多软件可以帮助我们快速、准确地扫描错题,并进行整理和纠正。本文将为你介绍几款…

满足ITOM需求的网络监控工具

IT 运营管理(ITOM)可以定义为监督 IT 基础架构的各种物理和虚拟组件的过程;确保其性能、运行状况和可用性;并使它们能够与基础架构的其他组件无缝协作。IT 运营管理(ITOM)在大型 IT 管理模型中也发挥着积极作用,包括 I…

使用GraphQL实现简单的增删改查

使用GraphQL实现简单的增删改查 GraphQL官网:https://graphql.cn/ Altair Graphql 调试工具:https://saltair.sirmuel.design/#download 或者添加扩展使用网页版:https://chrome.google.com/webstore/detail/altair-graphql-client/flnheeel…

在线图表编辑工具Draw.io本地部署并结合内网穿透实现远程协作办公

前言 提到流程图,大家第一时间可能会想到Visio,不可否认,VIsio确实是功能强大,但是软件为收费,并且因为其功能强大,导致安装需要很多的系统内存,并且是不可跨平台使用。所以,今天给…

怎么处理网站的一些安全风险

随时互联网的持续发展,数字化转型步伐不断加快,社会各行业都走进了信息化、数字化。但与此同时,网络发展带来了许多风险,各行业面临着日益复杂的数据安全和网络安全威胁。其中,网站的安全风险持续增长,是各…

R语言生物群落(生态)数据统计分析与绘图教程

详情点击链接:R语言生物群落(生态)数据统计分析与绘图教程 前沿 R 语言作的开源、自由、免费等特点使其广泛应用于生物群落数据统计分析。生物群落数据多样而复杂,涉及众多统计分析方法。 一:R和Rstudio及入门和作…

零基础学习数学建模——(一)什么是数学建模

本篇博客将详细介绍什么是数学建模。 文章目录 个人简介什么是数学建模(一)引例:高中数学里的简单线性规划问题数学建模的定义及用途数学建模的定义数学建模的用途 正确认识数学建模 个人简介 ​ 本人在本科阶段获得过国赛省一、mathorcup数…

【算法】基础算法001之双指针

👀樊梓慕:个人主页 🎥个人专栏:《C语言》《数据结构》《蓝桥杯试题》《LeetCode刷题笔记》《实训项目》《C》《Linux》《算法》 🌝每一个不曾起舞的日子,都是对生命的辜负 目录 前言 1.数组分块&#xf…

UTONMOS:探索元宇宙,开启未来游戏新篇章

在元宇宙的世界里,游戏不再只是消遣,而是一个全新的互动世界,等待你来探索! 逼真的虚拟现实技术,让你沉浸在充满想象力的游戏世界中,体验前所未有的刺激和乐趣。 与来自全球的玩家互动交流,结…

vue3+ts+vite中封装axios,使用方法从0到1

一、安装axios npm install axios types/axios --save二、配置代理vite.config.ts,如果没有需要新建该文件 module.exports {server: {proxy: {/api: {target: http://localhost:5000, // 设置代理目标changeOrigin: true, // 是否改变请求源地址rewrite: (path)…

【算法每日一练]-动态规划 (保姆级教程 篇16) #纸带 #围栏木桩 #四柱河内塔

目录 今日知识点: 计算最长子序列的方案个数,类似最短路径个数问题 四柱河内塔问题:dp[i]min{ (p[i-k]f[k])dp[i-k] } 纸带 围栏木桩 四柱河内塔 纸带 思路: 我们先设置dp[i]表示从i到n的方案数。 那么减法操作中&#xff…

Kafka消息存储

一、层次结构 具体到某个broker上则是, 数据目录/分区名/日志相关文件集合。其中日志文件集合内包括.log文件, index索引文件和.timeindex时间戳索引文件。 二、.log 结构 .log中记录具体的消息。一般消息由header和body组成, 这点儿在Kafka消息中也同样适用。 message MES…