ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

JVM诊断及工具笔记(2)使用arthas定位哪里执行了System#gc()

2022-02-01 21:35:08  阅读:185  来源: 互联网

标签:System gc 内存 JVM arthas 日志 DirectByteBuffer


封面图片不要使用微信打开文章,可以使用手机/电脑浏览器

 

笔者是汽车之家实时计算平台的一名小伙伴。负责flink平台,数据湖及kafka平台的设计与开发。平时擅长做平台设计,定位及解决各种疑难杂症。第二篇文章,讲的点依旧很小,但是这次图多!!! 在这里感谢支持上篇文章的小伙伴了

前言

这篇文章是之前解决一个Flink任务在线上发生fullgc

image-20211023220400506

 

当时的想法就是fullgc发生在:

  • 堆内存: 通过堆内存监控和dump堆内存这两种方式 都发现堆占用不大 ,排除

  • metaspace: gc日志里并没有堆metaspace日志且metaspace占用很小 ,排除

因此主要把排除重点放在直接内存。接下来老规矩,我们长话短说会夹带一点点心路历程。

 

步骤

1.配置jvm参数打印gc

 clusterConf.env.java.opts=-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:${LOG_DIRS}/gc.log  

 

2.在gc日志里发现有程序主动调用System.gc()

 

image-20211023220520256

 

当时猜测是使用的flink11版本代码里面会有调用

image-20211023220653384

加了些日志,发现并没有调用。

3.使用arthas

3.1.下载 arthas,并attach需要监听的jvm进程
curl -O https://arthas.aliyun.com/arthas-boot.jar
java -jar arthas-boot.jar

 

 

image-20211023182623635

3.2.监听 System.gc()方法并把日志打到磁盘上
options unsafe true

stack java.lang.System gc -n 1 >> /tmp/gc.log &

 

image-20211023183103082

3.3 观察日志

过了一段时间 观察下日志发现

image-20220103230624856

是在Kafka connector 调用java的nio的时候调用的System.gc()

4.分析

4.1讲一下这块代码的大致背景

java的直接内存(direct memory)是不会被gc回收的,而是通过监听持有直接内存资源的对象被回收的时候才把直接内存释放掉的。主要原理使用到虚引用和引用队列:当jvm发现对象已经没有强引用,仅剩虚引用时会将其存放在Reference的discovered的列表中 --->随后变成Pending状态 (准备加入引动队列) ---->随后进入Enqueued状态(加入队列,监听者可以获取从而回收资源)

 

4.2背景介绍完毕,回到刚才找到的方法栈,看倒数第二个方法

 

image-20211023200715246

栈里 DirectByteBuffer 这个类就是直接内存资源的持有者。

DirectByteBuffer

DirectByteBuffer 在构造时便被Cleaner监听回收资源。束于篇幅稍微文字介绍下Cleaner, Cleaner 就是 PhantomReference(虚引用)的子类。 1.Cleaner#create的方法就是将DirectByteBuffer对象虚引用且监听引用队列,2.当在队列中接收到DirectByteBuffer(此时对象已经被jvm标记discovered) 执行Deallocator的回收资源操作。

 

4.3 倒数第一个方法

image-20211023203728028

 

image-20211023204240359

来到倒数第一个方法,打开实现大家应该就能明白,这个方法是给DirectByteBuffer预留资源的。如果资源充足,万事大吉。如果资源充足: 大家回忆下刚才说讲的被虚引用DirectByteBuffer的discovered,pending,enqueued状态, 图中我用箭头标记的jlra.tryHandlePendingReference() 是尽快将pending状态的对象尽快转换成enqueued状态被好被Cleaner回收掉。一顿操作过后发现资源依旧不够。那么只能调用System.gc() 方法执行fullgc了。

注: 这套实现中 使用fullgc的好处是有部分 DirectByteBuffer对象很多次回收不掉进入老年代之后,这个时候young gc是没有办法回收掉的。

 

5.总结

刚才分析那么多,其实总结起来就一句话:直接内存不够了。回头又看了下用户任务的特点。发现数据消费量很大,但是代码可用的直接内存不多,反倒是框架可用的直接内存(taskmanager.memory.framework.off-heap.size)设得很大。合理调节完资源后,这个任务就再也没有发生oom了。

 

标签:System,gc,内存,JVM,arthas,日志,DirectByteBuffer
来源: https://www.cnblogs.com/wgcn/p/15859657.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有