堆内存居高不下,JDK8自适应作怪

堆内存居高不下,JDK8自适应作怪

背景

收到线上系统报警,堆内存超过98%,长时间居高不下。

内存泄漏检查

dump文件下载下来,使用visualVM分析了一波,发现有int[]和char[],占用了堆内存50%以上的空间,但是Retained Size 很小,意思就是执行一波FULL GC就会把大部分的占用空间清掉。

68c294e5ly1fya1exmax0j20he08lab1.jpg

而且根据观察也确实是这样。这就说明这些对象实际都已经不可达了,早就应该被回收掉啊,为什么会跑到老年代呢。

Young GC

众所周知,JVM年轻代分为eden区和survivor区,对象被创建后首先在Eden区,如果一次young gc 没有将其回收的话,会到survivor区。从survivor区到old generation需要了解下动态年龄判断。

动态年龄判断:

  1. 对象超过15次没有被回收,可以通过MaxTenuringThreshold设置
  2. 相同年龄的对象超过survivor区的50%,可以通过TargetSurvivorRatio设置。

对象超过15次没被回收不太可能,那就是survivor区太小了?

Young Generation空间分配

看了一下进程的参数配置,只设置了初始堆内存和最大堆内存 2G,其他都是默认参数。

/export/servers/jdk1.8.0_20/bin/java -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Djava.library.path=/usr/local/lib -server -Xms2048m -Xmx2048m -XX:MaxPermSize=256m -XX:+UnlockExperimentalVMOptions  -Djava.awt.headless=true -Dsun.net.client.defaultConnectTimeout=60000 -Dsun.net.client.defaultReadTimeout=60000 -Djmagick.systemclassloader=no -Dnetworkaddress.cache.ttl=300 -Dsun.net.inetaddr.ttl=300 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/export/Instances/xxxxx/server1/logs -XX:ErrorFile=/export/Instances/xxxxx/server1/logs/java_error_%p.log -Djava.endorsed.dirs=/export/servers/tomcat8.0.30/endorsed -classpath /export/servers/tomcat8.0.30/bin/bootstrap.jar:/export/servers/tomcat8.0.30/bin/tomcat-juli.jar -Dcatalina.base=/export/Instances/xxxxx/server1 -Dcatalina.home=/export/servers/tomcat8.0.30 -Djava.io.tmpdir=/export/Instances/xxxxx/server1/temp org.apache.catalina.startup.Bootstrap -config /export/Instances/xxxxx/server1/conf/server.xml start

那因为新生代和老年代的默认比例是1:2,eden区和两个survivor区默认比例是8:1:1,空间分配至应该是这样的:

名称 空间大小
年轻代 2048*1/3= 682
Eden 682*8/10= 546
Survivor 682*1/10= 68

使用jmap看一下线上的情况

Heap Configuration:
   MinHeapFreeRatio         = 0
   MaxHeapFreeRatio         = 100
   MaxHeapSize              = 2147483648 (2048.0MB)
   NewSize                  = 715653120 (682.5MB)
   MaxNewSize               = 715653120 (682.5MB)
   OldSize                  = 1431830528 (1365.5MB)
   NewRatio                 = 2
   SurvivorRatio            = 8
   MetaspaceSize            = 21807104 (20.796875MB)
   CompressedClassSpaceSize = 1073741824 (1024.0MB)
   MaxMetaspaceSize         = 17592186044415 MB
   G1HeapRegionSize         = 0 (0.0MB)

Heap Usage:
PS Young Generation
Eden Space:
   capacity = 705167360 (672.5MB)
   used     = 251792376 (240.12792205810547MB)
   free     = 453374984 (432.37207794189453MB)
   35.70675420938371% used
From Space:
   capacity = 5242880 (5.0MB)
   used     = 3047568 (2.9063873291015625MB)
   free     = 2195312 (2.0936126708984375MB)
   58.12774658203125% used
To Space:
   capacity = 5242880 (5.0MB)
   used     = 0 (0.0MB)
   free     = 5242880 (5.0MB)
   0.0% used
PS Old Generation
   capacity = 1431830528 (1365.5MB)
   used     = 442316256 (421.8256530761719MB)
   free     = 989514272 (943.6743469238281MB)
   30.891662620005263% used

672.5M和5M,纳尼?哪是8:1啊,都100多倍了,而且经过几次观察发现实际比例竟然在变化!

发现UseAdaptiveSizePolicy

经过查询发现 JDK 1.8 的默认垃圾回收器是 UseParallelGC,默认启动了 AdaptiveSizePolicy。这个参数会让垃圾回收根据每次垃圾回收的GC时间和吞吐量来动态调整eden区和survivor区的比例。
引用阿菜的博客的文章:

AdaptiveSizePolicy 有三个目标:
Pause goal:应用达到预期的 GC 暂停时间。
Throughput goal:应用达到预期的吞吐量,即应用正常运行时间 / (正常运行时间 + GC 耗时)。
Minimum footprint:尽可能小的内存占用量。
AdaptiveSizePolicy 为了达到三个预期目标,涉及以下操作:
如果 GC 停顿时间超过了预期值,会减小内存大小。理论上,减小内存,可以减少垃圾标记等操作的耗时,以此达到预期停顿时间。
如果应用吞吐量小于预期,会增加内存大小。理论上,增大内存,可以降低 GC 的频率,以此达到预期吞吐量。
如果应用达到了前两个目标,则尝试减小内存,以减少内存消耗。

临时处理

关闭UseAdaptiveSizePolicy策略,JVM增加参数:

 -XX:-UseAdaptiveSizePolicy -XX:SurvivorRatio=8

同时显式申明survivor区的比例。
结果:


68c294e5ly1fyunjxobbmj20sy08iq3l.jpg

其他处理

团队netty大神贡献其他优化,主要是关闭了netty内存管理,每个对象使用完成之后立即释放。

ChannelInboundHandlerAdapter
SimpleChannelInboundHandler.java
@Override  
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {  
    boolean release = true;  
    try {  
        if (acceptInboundMessage(msg)) {  
            @SuppressWarnings("unchecked")  
            I imsg = (I) msg;  
            channelRead0(ctx, imsg);  
        } else {  
            release = false;  
            ctx.fireChannelRead(msg);  
        }  
    } finally {  
        if (autoRelease && release) {  
            ReferenceCountUtil.release(msg);  
        }  
    }  
}

参考链接
杰神的博文

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,657评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,662评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,143评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,732评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,837评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,036评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,126评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,868评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,315评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,641评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,773评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,859评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,584评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,676评论 2 351

推荐阅读更多精彩内容