天天看点

《从零开始带你成为JVM实战高手》 笔记七

一、第五十课 jstat指令

1、命令

jstat -gc [PID] 间隔时间 次数       每隔多少时间打印一次JVM内存状况,总共打印指定次数

二、第五十一课 jmap、jhat指令分析大对象

1、使用jmap

jmap -dump:live,format=b,file=dump.hprof [PID]    将堆内存快照放到dump.hprof文件中,这个是二进制文件,不能直接打开

2、使用jhat

jhat dump.hprof -port 7000    启动jhat服务器,指定端口7000,就可以通过图形化的方式去分析堆内存中的对象分布情况了

三、第五十九课 案例分析,元数据区不断加载类导致频繁FULL GC

1、JVM启动时新加两个参数,用来追踪类加载和卸载的情况

-XX:TraceClassLoading -XX:TraceClassUnloading
           

 2、设置后发现,输出的日志中有类似以下的内容

《从零开始带你成为JVM实战高手》 笔记七

这个类是Java反射时,通过类似getDeclaredMethod方法产生的。在使用反射时,JVM在反射调用15次后,会动态生成一些软引用对象(ReflectionData),并把类信息放到Metadata中(具体逻辑需要研究反射源码)。软引用对象在GC是否要被回收是基于如下公式判断的

clock - timestamp <= freespace * SoftRefLRUPolicyMSPerMB
           

clock - timestamp:一个软引用对象多久没被访问过了

freespace:JVM中的空闲内存空间

SoftRefLRUPolicyMSPerMB:每一MB的空闲内存空间可以允许SoftReference对象存活多久

3、产生频繁FULL GC的原始,就是将SoftRefLRUPolicyMSPerMB设置为了0。

一般情况下,软引用的对象只有在快发送OOM时才会回收,而设置为0后,软引用的对象会立刻被young gc回收掉一些,然后反射就会继续创建软引用对象,并往Metaspace中存放类信息,所以会导致Metaspace满了

四、案例分析 其他导致频繁FULL GC的原因

1、内存分配不合理,导致对象频繁进入老年代

2、存在内存泄漏问题,老年代里存放了大量对象无法被回收,比如使用本地缓存

3、永久代里太多,比如上面那个案例

4、产生了大对象,比如SQL查询返回了一个几十万数据的对象

5、主动调用了System.gc

备注:系统创建大量线程,频繁进行FULL GC,这两种情况会导致CPU负载过高

五、反射导致方法区占满的原因 

1、getDeclaredMethod的流程

《从零开始带你成为JVM实战高手》 笔记七

2、上一步获取Method对象后,调用Method.invoke的流程 

《从零开始带你成为JVM实战高手》 笔记七

3、方法区占满原因

 调用Class.getDeclaredMethod方法时,每个Class对象内部有个ReflectionData对象,它是个软引用,如果是null,就会去jvm中取出类信息并赋值。后续调用就可以直接从这个ReflectionData对象中获取,提高效率。

从ReflectionData中获取到Method对象后,就开始调用invoke方法了,这里实际执行invoke方法的有2个实现类,NativeMethodAccessorImpl和GeneratedMethodAccessorXXX,第一次调用时,默认会创建NativeMethodAccessorImpl类。NativeMethodAccessorImpl的invoke方法会有一个判断,当调用不满15次时,使用自身,当调用满15次后,会创建GeneratedMethodAccessorXXX对象,而为了创建这个对象,会先创建DelegatingClassLoader这个类加载器,它会加载所需的类信息到方法区中,后续再调用这个Method的invoke方法时,就会直接使用GeneratedMethodAccessorXXX,这样可以提高效率。

但创建GeneratedMethodAccessorXXX会往方法区中写入类信息,而当ReflectionData被回收了,那么下次调用getDeclaredMethod方法,又会重写上面的步骤,又会往方法区中写入类信息。并且NativeMethodAccessorImpl的invoke方法是不加锁的,所以在高并发下可能有好几个线程同时创建了GeneratedMethodAccessorXXX对象,虽然最后只会有一个,但却会往方法区写入n次类信息。从而导致方法区被占满

使用Enhancer时,有个方法,设置是否使用缓存,默认都是true。如果设置成false,就可能导致方法区溢出

enhancer.setUseCache(true);
           

因为如果使用缓存,Enhancer的create方法会从缓存中取类信息,否则会调用类加载往方法区写入类信息,因此如果不断创建Enhancer而又无法回收,就会导致方法区溢出