JVM中Perm区持续上涨问题

公司一位大牛在微博上的一条,打算消化一下,毕竟今后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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值