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变化对比。
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();
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);
}
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的总数。
相关推荐
Class c = urlCL.loadClass("TestClassA"); TestClassA object = (TestClassA) c.newInstance(); object.method(); } } ``` 此示例展示了如何使用`java.net.URLClassLoader`自定义类加载器来加载特定路径下的`...
在`loadClass()`内部,它会先尝试调用`findLoadedClass(name)`检查类是否已经加载,然后如果未加载,会通过`loadClassInternal(name)`进行实际加载。这个过程可能会涉及本地系统调用来读取.class文件,或者从网络...
这篇博文主要围绕`Classloader`的`loadClass`方法进行深入探讨,并分析了类加载的细节。我们将通过以下几点来详细解析这个主题: 1. **类加载器的层次结构** Java中的类加载器通常遵循双亲委托模型。当一个类加载...
3. 指令集:JVM使用一套基于栈的指令集,这些指令对应于字节码,如`aload`用于加载引用到操作数栈,`iadd`用于执行整数加法等。 4. 执行引擎:解释器负责逐条解释执行字节码,而即时编译器(JIT)会将热点代码编译...
这份文档可能包括了10个章节,全面讲解了从class文件到JVM运行过程中的关键步骤。 在Java编程语言中,.class文件是编译后的二进制格式,它包含了Java源代码的所有信息,如类、方法、变量定义等。JVM是Java平台的...
- **类加载器**:负责加载.class文件,确保程序运行所需的类能够被找到并加载到内存中。 - **运行数据区**:包括堆、方法区、虚拟机栈、本地方法栈和程序计数器等几个部分。 - **堆**:存储对象实例,是所有线程...
**ClassLoader** 负责加载ClassFile到JVM中,它遵循双亲委派模型。常见的ClassLoader包括Bootstrap ClassLoader、Extension ClassLoader和App ClassLoader。 #### 四、内存模型、锁与同步 **Java内存模型** 主要...
首先,它具备一种名为“类加载器”(Class Loader)的组件,用于加载和管理程序中的类文件。类加载器不仅能够按需加载类文件,还能够支持热更新等功能。此外,类之间的隔离也使得每个类的运行环境相对独立,提高了...
通过对JVM执行子系统原理的深入分析,我们不仅了解了Class文件的具体结构和字节码指令的基本概念,还探讨了类加载机制的各个环节以及字节码执行引擎的核心组成部分。这些知识点对于深入理解JVM的工作原理、优化程序...
1. **类加载器(ClassLoader)**: 类加载器是Java虚拟机(JVM)的一部分,负责将.class文件加载到内存中并转化为Class对象。Java提供了一种层次结构的类加载机制,其中Bootstrap ClassLoader、Extension ClassLoader和...
开发者可以通过继承`java.lang.ClassLoader`类并覆盖其`loadClass()`方法来实现自定义的加载逻辑。 **加密代码与安全** 在描述中提到了“啊;敌法;打飞机”,这可能是一个隐喻,暗示了通过自定义类加载器加密代码...
JVM执行的是字节码指令集,如`aload`(加载引用到操作数栈)、`iload`(加载整数到操作数栈)、`invokevirtual`(调用虚方法)等。模拟JVM需要理解并实现这些指令的解析和执行逻辑: 1. **解析字节码**:读取.class...
首先,JVM指令手册中提到的操作栈和局部变量的指令分为两大类:常量压栈指令和局部变量加载指令。常量压栈指令用于将各种类型的常量值(null, int, long, float, double, char, byte, short)压入操作数栈。例如,a...
同时,虽然可能没有完全实现垃圾回收,但理解垃圾回收的基本原理,如可达性分析和标记清除等,对于实现mini-jvm也是必要的。 总结 通过实现mini-jvm,开发者可以深入理解JVM的工作机制,这对于Java程序员来说是一...
public void loadClass(String className) throws ClassNotFoundException { // 自定义类加载逻辑 } } ``` **4. 运行受限代码:** 在沙箱环境中,你可以通过`Sandbox`对象加载和运行受限的Java代码。这通常涉及...
1. **字节码**:Java源代码编译后生成的是字节码文件(.class),这些文件包含了可被JVM理解的二进制指令。每个字节码指令由一个单字节的操作码(opcode)和可能的参数组成。 2. **操作码**:JVM指令由操作码标识,...
2. **类加载机制**:JVM通过类加载器将.class文件加载到内存中。这包括加载、验证、准备、解析和初始化五个阶段。双亲委派模型是类加载器的基本工作模式,确保了类的唯一性。 3. **运行时数据区**:JVM内存分为堆、...
1. **加载和存储指令**:这类指令用于在局部变量表中加载和存储数据,例如`iload`用于从局部变量表加载一个整型值到操作数栈,`istore`则将操作数栈顶部的整型值存储回局部变量表。 2. **算术运算指令**:JVM提供了...
- **装载(Load)**:查找并读取Class文件的内容,将其转化为二进制流加载到内存中。 - **连接(Linking)**:包括验证(Verify)、准备(Prepare)和解析(Resolve)三个子阶段。 - **验证(Verify)**:确保Class文件的字节流...