龙空技术网

优化 full gc 的四个方向【草稿】

IT知识分享官 299

前言:

而今朋友们对“怎么优化c盘空间”都比较注重,小伙伴们都想要分析一些“怎么优化c盘空间”的相关内容。那么小编也在网上汇集了一些对于“怎么优化c盘空间””的相关知识,希望各位老铁们能喜欢,各位老铁们快快来学习一下吧!

常言道,道可道,老生常谈的一句话:优化 jvm 无非是调整 Xmx 和 Xms 参数。不管你怎么理解这句话,大部分实际情况场景下,此句没毛病!

如果你的是2b项目,那简直是老铁没毛病。

如果你的项目逻辑简单,业务轻量,并发不高,那简直是老铁真没毛病。

如果你的项目架构复杂,涉及高并发,大量2c接口,就有必要针对你的应用特点,优化 jvm 参数,一来 jvm 默认擦参数是通用的,未必适合你的应用,二来是个写到简历的大好机会

本文以优化 full gc 作为标题,把 full gc 的优化分为四个方向,三个方向是 jvm 层面优化,最后一个方向是 code 层面。话不多说,撸起袖子就干

jvm 层面优化初始化堆、最大堆

Xmx 和 Xms 值设置一样,避免 jvm 动态回收内存引起 gc

垃圾收集器

垃圾收集器种类不少,我根据运行方式将其分为三大类,串行,并行和并发。根据你的应用特点和服务器配置选择合适的垃圾收集器,如果还不清楚,那就看看小明怎么选吧。\

小明是个计算机系学生,因为兴趣爱好开发了技术博客,起初博客大小只有百来兆,运行在小明古董的单核电脑上,用串行垃圾收集器足以。

随着技术博客的发展,日活越来越多,gc stw时间慢慢变长,不过好在小明有一个远在大洋彼岸的表哥,表哥家境殷实,最近升级淘汰了一台双核cpu,被早已是垃圾佬的小明以200美元巨款拿下,随着小明的欢笑声,博客的终于用上并行垃圾收集器。

随着小明大学毕业,jdk9 诞生了,而小明已不是曾经的少年,在那满是头发的桌子上,小明轻轻的敲击 enter 键,伴随一阵叮咚,jdk9 升级成功。终于,小明成为宿舍里第一个吃上并行垃圾收集器的男人。

若干年后,小明已有三斤肚腩,已从少年变成中年,但他没有忘记自己是计算机系,他是爱好技术的,毅然决然升级到 jdk17,也终于用上了 g1

matespace

元空间有啥,运行时常量池,静态变量,类信息等。这里面我们重点关注类的元数据。我们都知道,jvm 用类加载器从元空间加载/卸载类,而随着加载的类数量变多,元空间的类信息变多,满了会怎样?

万幸的是,元空间用的是堆外内存,跟 heap 没有竞争,而我们的堆外内存一般跟 heap 差不多大,不需要担心元空间满的问题,但需要担心另外个参数 mateSpaceSize=20m(defualed)。该参数的作用是,当元空间使用达到mateSpaceSize后,jvm 会触发 full gc。我寻思着,这不对啊,触发 full gc那几个条件没有元空间呀。

带着疑问,撸起袖子干

ini复制代码/** * -Xms4096m * -Xmx4096m * -XX:MetaspaceSize=205m * -XX:MaxMetaspaceSize=205m */while (true) {    Enhancer enhancer = new Enhancer();    enhancer.setSuperclass(StarterApplication.class);    enhancer.setUseCache(false);    enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> proxy.invokeSuper(obj, args));    enhancer.create();}

oc是老年代容量,ou是老年代使用量,mc是元空间容量,mu是元空间使用量,ygc和fgc望文生义

打印有点乱,各位看官老爷将就将就。老爷们把目光锁定到红色方框,横红框和竖红框重合区域。从中我们可以分析道,伴随着 ygc 增长到38,元空间增长到209920k,full gc 终于开始干活了,但是我们注意到,老年代才100366k,要知道启动参数老年代有 4g,老年代的使用远没有达到 full gc 的条件。聪明的 boy 早已看穿一切,脱口而出担保机制,老咸一眼看常你的心思,随手丢了一篇,消息在人声鼎沸中

其实除了老年代放不下或满了,或者 System.gc 以外,元空间也可以触发 full gc,其触发时机跟 mateSpaceSize 有关。还记得我们甚至启动参数 mateSpaceSize=205m 吗,再看看输出日志,mu=200091.9k#≈195m,而195≈205,这难道是巧合吗,不,这是天意。

简而言之,元空间使用的内存达到 mateSpaceSize 会触发 full gc,这时候元空间需要卸载类,所以我们的调优思路是增大元空间,不差钱的设置 1g,差钱的 256m

code 层面

啰嗦完 jvm 层面,我们看看 code 层面。 code 层面目前有安全点和 tlab 可吹,但 tlab 我还不没搞清楚,这里我先插个眼,研究清楚我再传送

安全点

简单来说,安全点是保证业务线程 stw 时机

gc 时需要弄清楚两件事,一是业务线程 stw,二是业务线程 stw 时机。为了保证用户应用性能,业务线程几乎 full time 在执行业务逻辑,但 gc 时没人通知业务线程,所以需要业务线程自己问 gc,大爷,您要干活了吗?但这又带来个问题,什么时机,什么频率询问?

jvm 在特定的位置给字节码插入一个指令(比喻),当业务线程执行到该指令,会停下来询问是否要 gc,这就是安全点。另外一提,当所有业务线程到达安全点,gc 才会开始。

知道了安全点,那安全点有什么可优化的地方呢?

源于 why大神的一篇博文,一个 rocketMq 的 bug,现象是很长时间的 full gc。代码是一个循环执行业务逻辑,耗时操作,且里面有个关键字 prevent gc,大神深深沉迷于此,一步步庖丁解牛,最后发现 bug 跟 prevent gc 没关系,反而是安全点的问题

你有没有想过,我们使用循环的时候,如果循环耗时,且循环次数很大,gc 启动的时候,循环要不要停下来?回顾上面说的安全点,循环应该要停下来,所以 jvm 就得给循环插入安全点,jvm 确实插进去了,但。

凡是有个但,有两个专有名词,不可数循环和可数循环,jvm 只会在不可数循环中插入安全点,而声明不可数循环的做法是把 i 定义成 long,反而把 i 定义 int 不会插入安全点。

大概是因为,jvm 认为用户在不怎么耗时,循环次数不多的循环定义 int,在耗时,循环次数很多的循环定义 long,就是默认用户本来就会(胆子挺大)。

总结 rocketmMq 的 bug 就是它的循环耗时较长,i 定义为可数循环。gc 线程启动时,业务线程处于循环中,且没有安全点,gc 线程只能等待,而其他线程已经进入安全点,并处于 stw,所以整个应用只有循环的线程在执行,这影响可大了,轻则服务挂机,重则服务雪崩。

那是不是我们在所有的循环都用 long?要知道,安全点的插入使得业务现场轮询 gc,也是一种消耗,无脑 long 不可取,平常编码的时候,要根据具体业务场景定义

tlab分配

我们一般认为 Java 中 new 的对象都是在堆上分配,这个说法不够准确,应该是大部分对象在堆上的 TLAB 分配,还有一部分在栈上分配或者是堆上直接分配,可能 Eden 区也可能年老代。同时,对于一些的 GC 算法,还可能直接在老年代上面分配,例如 G1 中的 humongous allocations(大对象分配),就是对象在超过 Region 一半大小的时候,直接在老年代的连续空间分配。

这里,我们先只关心 TLAB 分配。

标签: #怎么优化c盘空间