公司一位大牛在微博上的一条,打算消化一下,毕竟今后Perm区的上涨还是有可能遇到的。“Java应用Perm区一直呈上涨趋势的原因可以用一个简单的办法排查,就是用btrace去跟踪下是什么地方在调用ClassLoader.defineClass,在大多数情况下这招都是管用的。”
(1)Perm区存放的啥信息?
Perm叫做持久代,存放了类的信息、类的静态变量、类中final类型的变量、类的方法信息,Perm是全局共享的,在一定条件下会被GC掉,方所要承载的数据超过内存区域后,就会出现OOM异常。可以通过-XX:PermSize和-XX:MaxPermSize来指定这个区域的最小值和最大值。持久代的回收主要包括两部分,废弃常量和无用的类,废弃的常量比较容易判别,没有任何String类型的对象引用这个就算可以废弃的了,但是无用的类判别比较复杂,(该类所有的实例已经被回收,JVM中没有任何类的实例;加载该类的ClassLoader被回收;该类对应的java.lang.class没有地方引用)。可以使用-verbose:class以及-XX:TraceClassLoading和-XX:TraceClassUnLoading来查看类的加载和卸载情况。
(2)ClassLoader中的defineClass是干啥的?
该方法用于将二进制的字节码转换为Class对象,对于自定义加载类非常重要,如果二进制的字节码不符合JVM的规范,就会报ClassFormatError,如果生成的类名和二进制中的不符,报NoClassDefFoundError异常,如果加载的class是受保护的,则报SecurityException,如果此类已经在ClassLoader已经加载,会报LinkageError。
例子1:运行时常量溢出
向常量池中添加数据,可以调用String.intern(),这个是native方法,如果常量池中已经存在一个,则返回,否则添加早常量池中。
刚开始的例子如下:
[backcolor=rgb(250, 250, 250) !important][size=1em]1 [size=1em]2 [backcolor=rgb(250, 250, 250) !important][size=1em]3 [size=1em]4 [backcolor=rgb(250, 250, 250) !important][size=1em]5 [size=1em]6 [backcolor=rgb(250, 250, 250) !important][size=1em]7 [size=1em]8 [backcolor=rgb(250, 250, 250) !important][size=1em]9 | [size=1em][backcolor=rgb(250, 250, 250) !important][size=1em]public class PermTest { [size=1em] public static void main(String[] args) { [backcolor=rgb(250, 250, 250) !important][size=1em] int i = 0; [size=1em] while(true){ [backcolor=rgb(250, 250, 250) !important][size=1em] String.valueOf(i++).intern(); [size=1em] System.out.println(i); [backcolor=rgb(250, 250, 250) !important][size=1em] } [size=1em] } [backcolor=rgb(250, 250, 250) !important][size=1em]} |
发现没有抛出OOM异常,用JConsole查看,(PermSize和MaxPermSize都是10M)在10M的时候曲线又下去了,原来是FullGC把常量池中没有的引用GC掉了,所以没有OOM。
增加了一个List,持有这个引用,就不会被GC掉了。
[backcolor=rgb(250, 250, 250) !important][size=1em]1 [size=1em]2 [backcolor=rgb(250, 250, 250) !important][size=1em]3 [size=1em]4 [backcolor=rgb(250, 250, 250) !important][size=1em]5 [size=1em]6 [backcolor=rgb(250, 250, 250) !important][size=1em]7 [size=1em]8 [backcolor=rgb(250, 250, 250) !important][size=1em]9 | [size=1em][backcolor=rgb(250, 250, 250) !important][size=1em]public class PermTest { [size=1em] public static void main(String[] args) { [backcolor=rgb(250, 250, 250) !important][size=1em] List<String> all = new ArrayList<String>(); [size=1em] int i = 0; [backcolor=rgb(250, 250, 250) !important][size=1em] while(true){ [size=1em] all.add(String.valueOf(i++).intern()); [backcolor=rgb(250, 250, 250) !important][size=1em] } [size=1em] } [backcolor=rgb(250, 250, 250) !important][size=1em]} |
[backcolor=rgb(250, 250, 250) !important][size=1em]1 [size=1em]2 [backcolor=rgb(250, 250, 250) !important][size=1em]3 | [size=1em][backcolor=rgb(250, 250, 250) !important][size=1em]Exception in thread "main" java.lang.OutOfMemoryError: PermGen space [size=1em] at java.lang.String.intern(Native Method) [backcolor=rgb(250, 250, 250) !important][size=1em] at PermTest.main(PermTest.java:10) |
例子2:用cglib产生代理类,不断的创建类
[backcolor=rgb(250, 250, 250) !important][size=1em]1 [size=1em]2 [backcolor=rgb(250, 250, 250) !important][size=1em]3 [size=1em]4 [backcolor=rgb(250, 250, 250) !important][size=1em]5 [size=1em]6 [backcolor=rgb(250, 250, 250) !important][size=1em]7 [size=1em]8 [backcolor=rgb(250, 250, 250) !important][size=1em]9 [size=1em]10 [backcolor=rgb(250, 250, 250) !important][size=1em]11 [size=1em]12 [backcolor=rgb(250, 250, 250) !important][size=1em]13 [size=1em]14 [backcolor=rgb(250, 250, 250) !important][size=1em]15 [size=1em]16 [backcolor=rgb(250, 250, 250) !important][size=1em]17 [size=1em]18 [backcolor=rgb(250, 250, 250) !important][size=1em]19 [size=1em]20 [backcolor=rgb(250, 250, 250) !important][size=1em]21 [size=1em]22 [backcolor=rgb(250, 250, 250) !important][size=1em]23 | [size=1em][backcolor=rgb(250, 250, 250) !important][size=1em]import java.lang.reflect.Method; [size=1em]import net.sf.cglib.proxy.Enhancer; [backcolor=rgb(250, 250, 250) !important][size=1em]import net.sf.cglib.proxy.MethodInterceptor; [size=1em]import net.sf.cglib.proxy.MethodProxy; [backcolor=rgb(250, 250, 250) !important][size=1em]public class PermTest { [size=1em] public static void main(String[] args) { [backcolor=rgb(250, 250, 250) !important][size=1em] while(true){ [size=1em] Enhancer enhancer = new Enhancer(); [backcolor=rgb(250, 250, 250) !important][size=1em] enhancer.setSuperclass(PermTestClass.class); [size=1em] enhancer.setUseCache(false); [backcolor=rgb(250, 250, 250) !important][size=1em] enhancer.setCallback(new MethodInterceptor() { [size=1em] @Override [backcolor=rgb(250, 250, 250) !important][size=1em] public Object intercept(Object obj, Method method, Object[] args, [size=1em] MethodProxy proxy) throws Throwable { [backcolor=rgb(250, 250, 250) !important][size=1em] return proxy.invoke(obj, args); [size=1em] } [backcolor=rgb(250, 250, 250) !important][size=1em] }); [size=1em] enhancer.create(); [backcolor=rgb(250, 250, 250) !important][size=1em] } [size=1em] } [backcolor=rgb(250, 250, 250) !important][size=1em] static class PermTestClass{ [size=1em] } [backcolor=rgb(250, 250, 250) !important][size=1em]} |
[backcolor=rgb(250, 250, 250) !important][size=1em]1 [size=1em]2 [backcolor=rgb(250, 250, 250) !important][size=1em]3 [size=1em]4 [backcolor=rgb(250, 250, 250) !important][size=1em]5 | [size=1em][backcolor=rgb(250, 250, 250) !important][size=1em]Caused by: java.lang.OutOfMemoryError: PermGen space [size=1em] at java.lang.ClassLoader.defineClass1(Native Method) [backcolor=rgb(250, 250, 250) !important][size=1em] at java.lang.ClassLoader.defineClassCond(ClassLoader.java:631) [size=1em] at java.lang.ClassLoader.defineClass(ClassLoader.java:615) [backcolor=rgb(250, 250, 250) !important][size=1em] ... 8 more |
Btrace的脚本文件如下(查看哪里调用了defineClass信息):
[backcolor=rgb(250, 250, 250) !important][size=1em]1 [size=1em]2 [backcolor=rgb(250, 250, 250) !important][size=1em]3 [size=1em]4 [backcolor=rgb(250, 250, 250) !important][size=1em]5 [size=1em]6 [backcolor=rgb(250, 250, 250) !important][size=1em]7 [size=1em]8 [backcolor=rgb(250, 250, 250) !important][size=1em]9 [size=1em]10 [backcolor=rgb(250, 250, 250) !important][size=1em]11 [size=1em]12 [backcolor=rgb(250, 250, 250) !important][size=1em]13 [size=1em]14 [backcolor=rgb(250, 250, 250) !important][size=1em]15 [size=1em]16 [backcolor=rgb(250, 250, 250) !important][size=1em]17 [size=1em]18 [backcolor=rgb(250, 250, 250) !important][size=1em]19 [size=1em]20 [backcolor=rgb(250, 250, 250) !important][size=1em]21 [size=1em]22 [backcolor=rgb(250, 250, 250) !important][size=1em]23 [size=1em]24 [backcolor=rgb(250, 250, 250) !important][size=1em]25 [size=1em]26 [backcolor=rgb(250, 250, 250) !important][size=1em]27 [size=1em]28 [backcolor=rgb(250, 250, 250) !important][size=1em]29 [size=1em]30 [backcolor=rgb(250, 250, 250) !important][size=1em]31 [size=1em]32 | [size=1em][backcolor=rgb(250, 250, 250) !important][size=1em]import static com.sun.btrace.BTraceUtils.*; [size=1em]import com.sun.btrace.annotations.*; [backcolor=rgb(250, 250, 250) !important][size=1em]@BTrace [size=1em]public class BtraceAll { [backcolor=rgb(250, 250, 250) !important][size=1em] @TLS [size=1em] private static long beginTime; [backcolor=rgb(250, 250, 250) !important][size=1em] @OnMethod( [size=1em] clazz="java.lang.ClassLoader", [backcolor=rgb(250, 250, 250) !important][size=1em] method="defineClass" [size=1em] ) [backcolor=rgb(250, 250, 250) !important][size=1em] public static void traceMethodBegin(){ [size=1em] beginTime = timeMillis(); [backcolor=rgb(250, 250, 250) !important][size=1em] } [size=1em] @OnMethod( [backcolor=rgb(250, 250, 250) !important][size=1em] clazz="java.lang.ClassLoader", [size=1em] method="defineClass", [backcolor=rgb(250, 250, 250) !important][size=1em] location=@Location(Kind.RETURN) [size=1em] ) [backcolor=rgb(250, 250, 250) !important][size=1em] public static void traceMethdReturn( [size=1em] @Return String result, [backcolor=rgb(250, 250, 250) !important][size=1em] @ProbeClassName String clazzName, [size=1em] @ProbeMethodName String methodName){ [backcolor=rgb(250, 250, 250) !important][size=1em] println("==========================================================================="); [size=1em] println(strcat(strcat(clazzName, "."), methodName)); [backcolor=rgb(250, 250, 250) !important][size=1em] println(strcat("Time taken : ", str(timeMillis() - beginTime))); [size=1em] println("java thread method trace:---------------------------------------------------"); [backcolor=rgb(250, 250, 250) !important][size=1em] jstack(); [size=1em] println("----------------------------------------------------------------------------"); [backcolor=rgb(250, 250, 250) !important][size=1em] println(strcat("Reuslt :",str(result))); [size=1em] println("============================================================================"); [backcolor=rgb(250, 250, 250) !important][size=1em] } [size=1em]} |
运行后,发现cglib在创建代理类的时候,确实调用了defineClass方法
[backcolor=rgb(250, 250, 250) !important][size=1em]1 [size=1em]2 [backcolor=rgb(250, 250, 250) !important][size=1em]3 [size=1em]4 [backcolor=rgb(250, 250, 250) !important][size=1em]5 [size=1em]6 [backcolor=rgb(250, 250, 250) !important][size=1em]7 [size=1em]8 [backcolor=rgb(250, 250, 250) !important][size=1em]9 [size=1em]10 [backcolor=rgb(250, 250, 250) !important][size=1em]11 [size=1em]12 [backcolor=rgb(250, 250, 250) !important][size=1em]13 [size=1em]14 [backcolor=rgb(250, 250, 250) !important][size=1em]15 [size=1em]16 | [size=1em][backcolor=rgb(250, 250, 250) !important][size=1em]=========================================================================== [size=1em]java.lang.ClassLoader.defineClass [backcolor=rgb(250, 250, 250) !important][size=1em]Time taken : 79 [size=1em]java thread method trace:--------------------------------------------------- [backcolor=rgb(250, 250, 250) !important][size=1em]java.lang.ClassLoader.defineClass(ClassLoader.java:615) [size=1em]sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source) [backcolor=rgb(250, 250, 250) !important][size=1em]sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) [size=1em]java.lang.reflect.Method.invoke(Method.java:597) [backcolor=rgb(250, 250, 250) !important][size=1em]net.sf.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:384) [size=1em]net.sf.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:219) [backcolor=rgb(250, 250, 250) !important][size=1em]net.sf.cglib.proxy.Enhancer.createHelper(Enhancer.java:377) [size=1em]net.sf.cglib.proxy.Enhancer.create(Enhancer.java:285) [backcolor=rgb(250, 250, 250) !important][size=1em]PermTest.main(PermTest.java:19) [size=1em]---------------------------------------------------------------------------- [backcolor=rgb(250, 250, 250) !important][size=1em]Reuslt :class PermTest$PermTestClass$$EnhancerByCGLIB$$1ed16944_8 [size=1em]============================================================================ |
至此,问题场景以及如何排查,清晰了。。。
参考书籍:
1、分布式java应用基础
2、深入理解JVM