『jvm』定位项目每天上午9点半 Full GC 问题
[TOC]
背景
每天上午9点半左右,生产环境上我们的项目会发生 Full GC
我这篇博客的思路和步骤基本是根据大佬的文档 假笨说-从一起GC血案谈到反射原理 来的,按图索骥,也把大佬的一些步骤没有写清楚的地方写清楚了
GC 日志
提前配置好 jvm
参数
-Xms2048m -Xmx2048m -Xmn830m -XX:SurvivorRatio=8 -XX:-UseAdaptiveSizePolicy -Duser.timezone=GMT+08 -XX:-OmitStackTraceInFastThrow -XX:+PrintCommandLineFlags -XX:MetaspaceSize=400m -Xloggc:/ms/jvm/gc/c.log -XX:+PrintHeapAtGC -XX:+PrintReferenceGC -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/ms/jvm/oom/ -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions
![](https://img.haomeiwen.com/i13864094/f330055407b24695.png)
可以看到 capacity
,这个是已经使用的内存大小(具体参数可以参考 metaspace )
可以发现,这个空间一直在增长
每天达到 9:30
左右的时候,就会触发 Full GC
![](https://img.haomeiwen.com/i13864094/baa4ae451a61ccdc.png)
metaspace
那么这个 metaspace
占用的都是谁呢?
通过 arthas
可以看下
下载 arthas
curl -O https://arthas.aliyun.com/arthas-boot.jar
java -jar arthas-boot.jar --repo-mirror aliyun --use-http
查看 class loader
![](https://img.haomeiwen.com/i13864094/69a9760d23470abd.png)
依次分析一下
org.apache.catalina.loader.ParallelWebappClassLoader
tomcat 的类加载器,一个实例,加载了 23935 个类
sun.reflect.DelegatingClassLoader
反射,10666
个实例,每个实例只加载一个
BootstrapClassLoader
称为启动类加载器,是Java类加载层次中最顶层的类加载器,负责加载JDK中的核心类库,如:rt.jar、resources.jar、charsets.jar等
一个实例,加载了8391个类
com.navercorp.pinpoint.bootstrap.classloader.ParallelCapablePinpointURLClassLoader
arms,一个实例
com.taobao.arthas.agent.ArthasClassloader
arthas 自己
java.net.URLClassLoader
网络请求,一个实例
com.navercorp.pinpoint.common.plugin.PluginLoaderClassLoader
arms
com.alibaba.fastjson.util.ASMClassLoader
fastjson, 3个实例,加载了88
后面都是毛毛雨,影响不大,不分析了
初步判定
实例数量大于1的有 sun.reflect.DelegatingClassLoader
和 com.alibaba.fastjson.util.ASMClassLoader
其中 fastjson
虽然有问题,但是问题不大,暂时不care
那么问题就出在 sun.reflect.DelegatingClassLoader
预测是 dozer
导致的 (我们项目中用到反射的东西不多)
揪出元凶
根据大佬的文档 假笨说-从一起GC血案谈到反射原理
第一步,写一个类
import sun.jvm.hotspot.tools.jcore.ClassFilter;
import sun.jvm.hotspot.oops.InstanceKlass;
public class MethodAccessorFilter implements ClassFilter {
@Override
public boolean canInclude(InstanceKlass kls) {
String klassName = kls.getName().asString();
return klassName.startsWith("sun/reflect/GeneratedMethodAccessor");
}
}
第二步, 导出
java -classpath ".:$JAVA_HOME/lib/sa-jdi.jar" -Dsun.jvm.hotspot.tools.jcore.filter=MethodAccessorFilter -Dsun.jvm.hotspot.tools.jcore.outputDir=/usr/local/tomcat/jvmlogs/code sun.jvm.hotspot.tools.jcore.ClassDump 1
导出目录为: /usr/local/tomcat/jvmlogs/code
![](https://img.haomeiwen.com/i13864094/97d4bd97b9dd1eff.png)
执行的时候,抛异常了,不过没关系,部分类已经下载下来了
![](https://img.haomeiwen.com/i13864094/a2d9605910a87a9e.png)
第三步, 反编译查看
javap -v GeneratedMethodAccessor753.class
随便看一个
![](https://img.haomeiwen.com/i13864094/805c57b36b08d888.png)
可以看到是我们的一个模型
再看一个,依旧是我们的模型
![](https://img.haomeiwen.com/i13864094/2b46a40e422c4c64.png)
debug 确定
本地debug前,新增jvm
参数
-XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -XX:+TraceClassLoading -XX:+TraceClassUnloading
执行的时候可以看到
![](https://img.haomeiwen.com/i13864094/70b06ba9f1f95775.png)
同时,可以看到 jackson
序列化的时候,也会生成
![](https://img.haomeiwen.com/i13864094/e900f1f528aba89f.png)
到了这里,有两个嫌疑犯 dozer
和 jackson
jackson
我们的所有项目都用到了jackson
,用在接口入参反序列化到java对象和 java对象序列化为 response body
去其它项目看下,就知道 jackson
运行一段时间后的表现
选了一个用了 jackson
但是没有 dozer
的项目
![](https://img.haomeiwen.com/i13864094/785719a5fa3f5ea4.png)
经过对比,没有得出什么结论,因为这个项目 vo对象少
所以jackson
依旧属于嫌疑犯
初步结论
jackson
是java对象
序列化为二进制的 http body 业界常用工具
属于非用不可
的,后续可以思考一下如何优化
dozer
是java对象
间互相转换的工具,已被业界弃用,很古老的工具
可以不用