java系列 JVM堆内存生命周期以及JVM调优

前言

经过上一章的讲解,我们应该对JVM中类加载器和内存模型已经有了初步的了解,在进行jvm调优之前我们还需要了解一下GC垃圾回收器以及堆内存中的一个生命周期,因为我们上一章也讲到了我们生成的对象实例都是在存放在堆中的,所以我们的调优主要是针对堆内存区进行的,下面就是堆内存的一个详细区域划分了

image

从上图可以看出我们堆内存中是由年轻代和老年代组成的,其中年轻代又包含伊甸园区和survivor区其中survivor区有两个,他们的内存占比默认 年轻代区占堆内存的3/1,老年代占堆内存的3/2;而年轻代中伊甸园和两个survivor区分别内存占比为8:1:1,也就是说如果我们堆内存设置的内存为1G,那伊甸园区就大约为270M大小,s1和s2大约为30M,老年代大约为670M;

堆内存的生命周期

当我们new一个对象的时候,就会在堆内存中开辟一个空间来存储这个对象并在栈中有内存地址来引向该对象实例,那我们刚new的对象首先进入的就是我们的伊甸园区,当我们的伊甸园区被放满以后就会触发Young GC(轻GC),Young GC会根据可达性算法去将年轻代里面有指针指向的对象也就是可用对象移入s0内存区,然后将他的代数+1,那些没有指针指向的对象就会被清理掉,这是第一次GC,如果第二次触发GC和第一次一样只不过他会把可用对象都移入到s1区并把代数+1,就这样一直循环,如果当一个对象的代数增加到15以后他就不会再移到另一个survivor区了而是会把他标记为顽固对象存放到老年代区,那经过时间的沉淀老年代区一直在增加不减少那老年代区也会被放满,那就会触发我的FULL GC(重GC),重GC主要清理老年代里的对象但他也会清理年轻代的,所以重GC他执行的速度会大大慢于轻GC,接下来我们可以看一个示例去验证一下我们上面的叙述是否正确

当我们执行以下代码以后,我们可以使用JDK自带的调优工具 jvisualvm 来查看堆内存的变化,但是jvisualvm需要安装一个Visual GC插件,如何安装可以看我这篇文章 java系列 jvisualvm安装Visual GC插件

image

当我们执行代码以后,进入cmd输入 : jvisualvm 就可以打开jdk自带的优化工具了,前提是你安装有jdk环境并且配置了环境变量

image

然后点击该线程进入Visual GC查看他堆内存情况

image

从上述视频加上我上面的叙述就可以大致了解堆内存的一个生命周期了,那个绿的条就是老年代,当老年代满了以后进行fullGC完还是没有空间那就会报我们经常看见的错误 OOM(内存溢出)错误了,不过这里要提到比较重要的一点就是不是必须代数为满15才会放入老年代,如果你伊甸园区中存储的可用对象要转移到s0或s1区域中,但是s0和s1放不下,那s0或s1区域中就会剩下的直接存放到老年代区域,还有就是当一个对象的大小大于s1或者s0区的一半(可以使用-XX:PretenureSizeThreshold JVM参数去设置),那他会直接从伊甸园区存到老年代区,比如一个对象50m 然后你的s0区域内存只有80M , 那这个50M的对象就不会再存入到s0了会直接存到老年代区,还有很多情况也会触发对象直接存入老年代,以后我们专门会写一个文章去统计一下!

从上面的演示我们对于堆内存的生命周期应该也大致了解了,接下来我们就要去介绍一下GC了

GC垃圾回收器

上面演示的堆内存生命周期中我们提到了轻GC和重GC,而轻GC主要负责清理年轻代区域的对象,而重GC主要清理老年代的但是他也会清理年轻代了,那我们提到的可达性算法的回收机制什么呢?

可达性分析算法

就是将"GC Roots"对象作为起点,从这些节点开始向下搜索引用对象,找到的对象都被标记为非垃圾对象,其余未标记的对象都是垃圾对象

GC Roots : 线程栈的本地变量 , 静态变量 , 本地方法栈的变量等

大致意思就是 比如我们现在栈中有一个变量User对象的内存地址然后他指向了堆内存中的User实体,那首先他指向的这个User实体就会被标记为非垃圾对象,然后User对象中又引用了order对象,那这个order对象也会被标记为非垃圾对象就这样一直引用下去直到结尾,等所有的GC Roots对象都进行梳理完毕以后,剩下的未标记的对象也就没有指针去引用他了他也就是垃圾对象了,会被直接干掉

什么是JVM调优

理论知识我们都明白的差不多了我们有没有想过什么是JVM调优他在调什么优呢

Stop the World

这里就要提到一个GC的机制STW(Stop the World),当我们字节码执行引擎进行GC的时候他就会触发STW机制,就会把所有用户阶级的线程全部暂停掉,等待GC运行,当GC运行完毕以后这些线程才会重新挂起,所以如果我们的项目在频繁GC的话那用户的线程就会频繁暂停,那用户体验也就会大打折扣!

那GC底层为什么要设计STW机制呢?

我们可以设想一下如果字节码引擎在进行GC的时候他没有停止掉用户线程,那当前用户比如在生成一个订单,那这个订单对象肯定就是非垃圾对象,然后GC就对他进行了标记,然后当GC刚标记完以后,用户的订单创建成功了就会写入数据库中,那这个order对象也就变成了垃圾对象,这就出现一个问题了一个垃圾对象头上标着非垃圾对象,那这GC了个啥,对吧!

所以JVM调优就是最大程度的减少GC次数也就是减少STW次数,特别是要减少重GC次数,因为重GC他会对整个堆内存进行扫描清理,很耗时,同时stw的时间也会很长,那用户的线程停止时间也会延长那性能可想而知!

如何减少GC次数

通过上述的分析我们知道了jvm调优就是去减少GC的次数特别是减少重GC次数,那我们该如何减少重GC次数呢,我会介绍几个比较常见的JVM优化案例,去让大家理解jvm优化

案例1

我们公司有一个电商项目,之前运作还是好好的然而有一次版本迭代以后,平常用户少的时候没有什么问题,但是当平台商户进行大促或者进行秒杀的时候,系统响应时间就会特别的慢,我们一看jvm gc , 发现当用户量增加的时候 重GC也就同步上涨,特别是商户秒杀活动能达到10秒钟左右就会进行一次GC,我们使用调优工具去查看项目中是否有死锁在暂用对象导致内存得不到释放或者代码中有死循环,然而检查下来以后发现没有任何问题,后面经过我们的分析终于发现了问题所在,就是我们一个小伙伴在进行项目迭代的时候配置一个jvm参数 -Xmx 2G ,去设置了堆大小为2G , 那时候我们用的服务器是2核4G的么, 这也是很多小伙伴会这么估算着对JVM配置,他就想 服务器一共就4个G 那除去系统和其他程序运行也就差不多剩下3个G可用,那我直接给堆内存2个G么,其实这样子估算是很不正确的,这也是问题所在,当我们堆大小设置为2个G.上面我们介绍堆内存的时候说过内存占比,也就是老年代区会占用2/3也就是大约是1351M内存,那年轻代就差不多剩下700M的内存,那伊甸园区会占用年轻代内存的8/10,也就大约是560M,那s0和s1每个也就大约是70M的空间,当我们捋清楚内存分配以后我们再去计算一下一般商家秒杀的时候会有多少用户进行请求,一般在后台都会有监控机制去检测当前网站用户请求量啥的,这也就是公司长期运行出来的数据,我们开会估算的是大约每秒有300个用户进行订单创建请求,那我们有去估算一下订单对象的大小,就是将对象中每个字段根据他的数据类型去估算,比如int就是4字节这样子进行订单对象的一个估算大小,一般也就是1kb因为订单对象中还会引用其他对象比如订单详情等,那我们这里就估一个订单对象1kb , 然后你在下单的时候还会涉及到优惠卷积分,商家促销信息,库存等业务,当时根据我们的业务对他进行了一个预估对每个对象扩大了20倍 , 同时我们在下单的时候不可能只有下单操作吧,还有订单查询操作还有什么用户修改或者其他一些操作,那我们有根据业务对其放大了10倍,如下图

image

每秒60m的对象存放到堆内存中,那差不多10秒就会把伊甸园区存满,那这时就会触发轻GC,这里还不是问题所在,问题就出现在,在GC的时候他会触发STW机制,那在进行GC的时候也是有用户在进行创建订单呢,那这一部分用户线程停止以后这些订单对象肯定是不能被回收的,那他就会把这些对象存放到s0区,那s0区的大小为70M , 你从伊甸园区会提交60M的对象,上面我们也提到过当对象大小大于s0区的一半以上就会将对象直接存放到老年代,那毫无疑问这60M对象会直接从伊甸园区存放到老年代,那老年代这1500M也就是二三十秒就会放满,那二三十米就会进行一次重GC,这些数据还都是1秒后就会变为垃圾对象的数据,这就导致了我们的项目10秒一次轻GC,30秒一次重GC,这么频繁的GC用户体验肯定会很差吖

那我们应该怎么优化才会减少我们项目在进行秒杀的时候重GC的次数呢

其实很简单,我们只需要将年轻代的内存调大一点就可以了么,将JVM参数设置为

-Xmx 2G -Xmn 1500m

那以上这么操作就是将堆内存的2G中的1.5个G分配给了年轻代,因为从上述分析我们会发现老年代并不需要多大的空间,因为这些数据都会在1秒后变为垃圾,那这么进行内存分配的话那s0和s1就会有150m的空间,那运行的60m对象就可以顺利存放在s0和s1区了,在1秒以后只需要执行轻GC就可以了,可以达到几乎不会产生重GC的效果!这也就是一个参数的问题达到了完美的JVM优化!

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇