JVM中Load过多Class的分析

JVM中Load过多Class的分析  

2013-01-27 16:48:53|  分类: jvm |  标签: |字号 订阅

 
 

http://blog.colinsage.info/?p=123

 

问题描述:
     应用运行一段时候后,出现应用卡死的情况。使用jvisualvm观测JVM,发现load的class过多,可能是造成问题的原因。
场景重现:
     在测试环境,运行应用,并用jvisualvm观测。用httpclient写了个客户端,连续访问某个应用url。
PermGen的状态轨迹是31.361、31.362、31.364、31.371、…
Load Class的变化轨迹是7876、7948、7959、7961、7967、7971、7981、7985
 (图1)
 
从上图也可以看出,PermGen不断增加,loaded class 不断增加。
其中一个请求处理前后的PermGen变化对比。
PermGen
前:
 (图2)
 
后:
(图3)
dump堆内存,分析发现有大量的GeneratedMethodAccessor
 (图4)
 
测试环境,停掉应用。加上参数 -XX:+TraceClassLoading,观测控制台,发现创建了大量的大量的sun.reflect.GeneratedMethodAccessor,sun.reflect.GeneratedConstructorAccessor,GeneratedSerializationConstructorAccessor等Class,和dump分析结果相同。
结论:就是这些类导致PermGen不断增加
为什么要创建这些类呢?
问题分析:
这还要从反射的执行开始说起。
使用反射来执行方法调用,可以使用类名得到Class对象,再由方法名,参数类型(数目不定)拿到Method对象。
再调用Method的invoke()方法,即可执行方法的调用。
那么invoke()的内部是什么样的呢?
下面是其部分代码:
public Object invoke(Object obj, Object… args)
        throws IllegalAccessException, IllegalArgumentException,
           InvocationTargetException
    {
          … …
        MethodAccessor ma = methodAccessor;             // read volatile
        if (ma == null) {
            ma = acquireMethodAccessor();
        }
        return ma.invoke(obj, args);
从上可以看出方法的invoke实际上是由MethodAccessor执行的。这个MethodAccesor又是由acquireMethodAccessor()方法拿到的。
其代码如下:
private MethodAccessor acquireMethodAccessor() {
        // First check to see if one has been created yet, and take it
        // if so
        MethodAccessor tmp = null;
        if (root != null) tmp = root.getMethodAccessor();
        if (tmp != null) {
            methodAccessor = tmp;
        } else {
            // Otherwise fabricate one and propagate it up to the root
            tmp = reflectionFactory.newMethodAccessor(this);
            setMethodAccessor(tmp);
        }
        return tmp;
    }
这个MethodAccessor是由ReflectionFactory的newMethodAccessor()方法来创建的。
这个方法的内部如下所示:
    public MethodAccessor newMethodAccessor(Method method) {
checkInitted();

if (noInflation) {
return new MethodAccessorGenerator().
generateMethod(method.getDeclaringClass(),
method.getName(),
method.getParameterTypes(),
method.getReturnType(),
method.getExceptionTypes(),
method.getModifiers());
} else {
NativeMethodAccessorImpl acc =
new NativeMethodAccessorImpl(method);
DelegatingMethodAccessorImpl res =
new DelegatingMethodAccessorImpl(acc);
acc.setParent(res);
return res;
}
}

这个方法内有个变量noInflation变量,用于控制MethodAccessor的创建。如果noInflation为true,由MethodAccessorGenerator运行时生成java版的MethodAccessor,否则创建native版的MethodAccessor。
我们看一下NativeMethodAccessorImpl的invoke()内部的样子。
public Object invoke(Object obj, Object[] args)
throws IllegalArgumentException, InvocationTargetException
{
if (++numInvocations > ReflectionFactory.inflationThreshold()) {
MethodAccessorImpl acc = (MethodAccessorImpl)
new MethodAccessorGenerator().
generateMethod(method.getDeclaringClass(),
method.getName(),
method.getParameterTypes(),
method.getReturnType(),
method.getExceptionTypes(),
method.getModifiers());
parent.setDelegate(acc);
}

return invoke0(method, obj, args);
}

从上可以看出,调用次数较少的情况下执行的是invoke0.如果调用次数超过一个阀值(inflationThreshold = 15),则还是生成一个java版的MethodAccessor。
generateMethod()内部又调用的是generate()方法,该方法运行时生成了一个类字节码的字节数组。这个创建的类被classloader加载后,这些字节码会被放到方法区,所以看上去PermGen的占用空间越来越大。
为何要这样呢?据说java版的启动慢,但是执行快(编译器可以优化);native版的启动快,但是执行慢。所以hotspot的jdk做了个优化,调用次数少时用native版的,当发现调用次数多时,创建个java版的MethodAccessor替换掉native版的。(native版的可以从jvm源码中找到相应代码,java版麻烦点但是也能弄出来的)
结论:
1、PermGen的增加是 正常现象,只要配置合理的PermSize\MaxPermSize即可避免PermGen Space溢出的出现。PermSize\MaxPermSize也不能设置的太大,否则会造成Full GC时间过长,也会影响应用。
2、sun.reflect.GeneratedMethodAccessor,sun.reflect.GeneratedConstructorAccessor,GeneratedSerializationConstructorAccessor等的数目与应用中类的数量和方法的数量成正比。例如GeneratedMethodAccessor最多是应用中所有method的总数。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值