概述
线上服务器jmap发现 Perm Generation使用量持续增长, 查看dump信息发现有很多sun.reflect.DelegatingClassLoader、sun.reflect.GeneratedConstructorAccessor77。原因是反射调用引起的,类越來越多。
当使用Java反射时,Java虚拟机有两种方法获取被反射的类的信息。它可以使用一个JNI存取器。如果使用Java字节码存取器,则需要拥有它自己的Java类和类加载器(sun/reflect/GeneratedMethodAccessor类和sun/reflect/DelegatingClassLoader)。这些类和类加载器使用本机内存。字节码存取器也可以被JIT编译,这样会增加本机内存的使用。如果Java反射被频繁使用,会显著地增加本机内存的使用。
Java虚拟机会首先使用JNI存取器,然后在访问了同一个类若干次后,会改为使用Java字节码存取器。注意使用字节码方式第一次有加载的成本比正常执行慢3-4倍,但是后面的执行会有20倍以上的性能提升,这样整体性能会有很大的提升。
这种当Java虚拟机从JNI存取器改为字节码存取器的行为被称为(Inflation)膨胀。Inflation机制提高了反射的性能,但是对于重度使用反射的项目可能存在隐患,它带来了两个问题:(1)初次加载的性能损失;(2)动态加载的字节码导致PermGen持续增长。
可以通过Java属性控制这种行为。属性sun.reflect.inflationThreshold会告诉Java虚拟机使用JNI存取器多少次。如果设为0,则总是使用JNI存取器。由于字节码存取器比JNI存取器使用更多本机内存,当看到大量Java反射时,最好使用JNI存取器。
另一种设置也会影响反射存取器。-Dsun.reflect.noInflation=true 会完全禁用扩展,但它会造成字节码存取器滥用。使用 -Dsun.reflect.noInflation=true 会增加反射类加载器占用的地址空间量,因为会创建更多的类加载器。
下面来代码分析一下。首先看下反射调用的一个示例:
import java.lang.reflect.Method;
public class TestClassLoad {
public static void main(String[] args) throws Exception {
Class<?> clz = Class.forName("A");
Object o = clz.newInstance();
Method m = clz.getMethod("foo", String.class);
for (int i = 0; i < 16; i++) {
m.invoke(o, Integer.toString(i));
}
}
}
class A {
public void foo(String name) {
System.out.println("Hello, " + name);
}
}
编译上述代码,并在执行TestClassLoad时加入-XX:+TraceClassLoading
参数(或者-verbose:class或者直接-verbose都行),如下:
java -XX:+TraceClassLoading TestClassLoad
可以看到输出了一大堆log,把其中相关的部分截取出来如下:
[Loaded sun.reflect.NativeMethodAccessorImpl from /Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded sun.reflect.DelegatingMethodAccessorImpl from /Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/jre/lib/rt.jar]
Hello, 0
Hello, 1
Hello, 2
Hello, 3
Hello, 4
Hello, 5
Hello, 6
Hello, 7
Hello, 8
Hello, 9
Hello, 10
Hello, 11
Hello, 12
Hello, 13
Hello, 14
[Loaded sun.reflect.ClassFileConstants from /Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded sun.reflect.AccessorGenerator from /Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded sun.reflect.MethodAccessorGenerator from /Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded sun.reflect.ByteVectorFactory from /Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded sun.reflect.ByteVector from /Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded sun.reflect.ByteVectorImpl from /Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded sun.reflect.ClassFileAssembler from /Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded sun.reflect.UTF8 from /Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded sun.reflect.Label from /Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded sun.reflect.Label$PatchInfo from /Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded java.util.ArrayList$Itr from /Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded sun.reflect.MethodAccessorGenerator$1 from /Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded sun.reflect.ClassDefiner from /Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded sun.reflect.ClassDefiner$1 from /Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded java.util.concurrent.ConcurrentHashMap$ForwardingNode from /Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded sun.reflect.GeneratedMethodAccessor1 from __JVM_DefineClass__]
Hello, 15
可以看到前15次反射调用A.foo()方法并没有什么特别的地方,但在第16次反射调用时似乎有什么东西被触发了,导致JVM新加载了一堆类。这是为啥?
先来看看JDK里java.lang.reflect.Method#invoke()是怎么实现的。
@CallerSensitive
public Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, obj, modifiers);
}
}
MethodAccessor ma = methodAccessor; // read volatile
if (ma == null) {
ma = acquireMethodAccessor();
}
return ma.invoke(obj, args);
}
// NOTE that there is no synchronization used here. It is correct
// (though not efficient) to generate more than one MethodAccessor
// for a given Method. However, avoiding synchronization will
// probably make the implementation more scalable.
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;
}
可以看到Method.invoke()实际上并不是自己实现的反射调用逻辑,而是委托给sun.reflect.MethodAccessor来处理。
每个实际的Java方法只有一个对应的Method对象作为root,这个root是不会暴露给用户的,而是每次在通过反射获取Method对象时新创建Method对象把root包装起来再给用户。在第一次调用一个实际Java方法对应得Method对象的invoke()方法之前,实现调用逻辑的MethodAccessor对象还没创建;等第一次调用时才新创建MethodAccessor并更新给root,然后调用MethodAccessor.invoke()真正完成反射调用。
那么MethodAccessor是啥呢?
package sun.reflect;
import java.lang.reflect.InvocationTargetException;
public interface MethodAccessor {
Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException;
}
可以看到它只是一个方法接口,其invoke()方法与Method.invoke()的对应。创建MethodAccessor实例的是sun.reflect.ReflectionFactory。来看newMethodAccessor()方法:
public MethodAccessor newMethodAccessor(Method var1) {
checkInitted();
if (noInflation && !ReflectUtil.isVMAnonymousClass(var1.getDeclaringClass())) {
return (new MethodAccessorGenerator()).generateMethod(var1.getDeclaringClass(), var1.getName(), var1.getParameterTypes(), var1.getReturnType(), var1.getExceptionTypes(), var1.getModifiers());
} else {
NativeMethodAccessorImpl var2 = new NativeMethodAccessorImpl(var1);
DelegatingMethodAccessorImpl var3 = new DelegatingMethodAccessorImpl(var2);
var2.setParent(var3);
return var3;
}
}
private static void checkInitted() {
if (!initted) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
if (System.out == null) {
return null;
} else {
String var1 = System.getProperty("sun.reflect.noInflation");
if (var1 != null && var1.equals("true")) {
ReflectionFactory.noInflation = true;
}
var1 = System.getProperty("sun.reflect.inflationThreshold");
if (var1 != null) {
try {
ReflectionFactory.inflationThreshold = Integer.parseInt(var1);
} catch (NumberFormatException var3) {
throw new RuntimeException("Unable to parse property sun.reflect.inflationThreshold", var3);
}
}
ReflectionFactory.initted = true;
return null;
}
}
});
}
}
在ReflectionFactory类中,有2个重要的字段:noInflation(默认false)和inflationThreshold(默认15),在checkInitted方法中可以通过-Dsun.reflect.inflationThreshold=xxx和-Dsun.reflect.noInflation=true对这两个字段重新设置,而且只会设置一次。
MethodAccessor实现有两个版本,一个是Java实现的,另一个是native code实现的。Java实现的版本在初始化时需要较多时间,但长久来说性能较好;native版本正好相反,启动时相对较快,但运行时间长了之后速度就比不过Java版了。为了权衡两个版本的性能,Sun的JDK使用了“inflation(膨胀)”的技巧:让Java方法在被反射调用时,开头若干次使用native版,等反射调用次数超过阈值时则生成一个专用的MethodAccessor实现类,生成其中的invoke()方法的字节码,以后对该Java方法的反射调用就会使用Java版。
来看DelegatingMethodAccessorImpl代码:
package sun.reflect;
import java.lang.reflect.InvocationTargetException;
class DelegatingMethodAccessorImpl extends MethodAccessorImpl {
private MethodAccessorImpl delegate;
DelegatingMethodAccessorImpl(MethodAccessorImpl var1) {
this.setDelegate(var1);
}
public Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException {
return this.delegate.invoke(var1, var2);
}
void setDelegate(MethodAccessorImpl var1) {
this.delegate = var1;
}
}
这是一个间接层,方便在native与Java版的MethodAccessor之间实现切换。 然后下面就是native版MethodAccessor,sun.reflect.NativeMethodAccessorImpl:
package sun.reflect;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import sun.reflect.misc.ReflectUtil;
class NativeMethodAccessorImpl extends MethodAccessorImpl {
private final Method method;
private DelegatingMethodAccessorImpl parent;
private int numInvocations;
NativeMethodAccessorImpl(Method var1) {
this.method = var1;
}
public Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException {
if (++this.numInvocations > ReflectionFactory.inflationThreshold() && !ReflectUtil.isVMAnonymousClass(this.method.getDeclaringClass())) {
MethodAccessorImpl var3 = (MethodAccessorImpl)(new MethodAccessorGenerator()).generateMethod(this.method.getDeclaringClass(), this.method.getName(), this.method.getParameterTypes(), this.method.getReturnType(), this.method.getExceptionTypes(), this.method.getModifiers());
this.parent.setDelegate(var3);
}
return invoke0(this.method, var1, var2);
}
void setParent(DelegatingMethodAccessorImpl var1) {
this.parent = var1;
}
private static native Object invoke0(Method var0, Object var1, Object[] var2);
}
每次NativeMethodAccessorImpl.invoke()方法被调用时,都会增加一个调用次数计数器,看是否超过阈值(默认15);一旦超过,则调用MethodAccessorGenerator.generateMethod()来生成Java版的MethodAccessor的实现类,并且改变DelegatingMethodAccessorImpl所引用的MethodAccessor为Java版。后续经由DelegatingMethodAccessorImpl.invoke()调用到的就是Java版的实现。
NativeMethodAccessorImpl调用MethodAccessorGenerator#generateMethod方法在生成MethodAccessorImpl对象时,会在内存中生成对应的字节码,并调用ClassDefiner.defineClass创建对应的class对象。在ClassDefiner.defineClass方法中,每被调用一次都会生成一个DelegatingClassLoader类加载器对象。这里每次都生成新的类加载器,是为了性能考虑,在某些情况下可以卸载这些生成的类,因为类的卸载是只有在类加载器可以被回收的情况下才会被回收的,如果用了原来的类加载器,那可能导致这些新创建的类一直无法被卸载,从其设计来看本身就不希望这些类一直存在内存里的,在需要的时候有就行了。
MethodAccessorGenerator的基本工作就是在内存里生成新的专用Java类,并将其加载。上面日志中sun.reflect.GeneratedMethodAccessor1这个类就是MethodAccessorGenerator生成的。相关的方法如下:
private static synchronized String generateName(boolean var0, boolean var1) {
int var2;
if (var0) {
if (var1) {
var2 = ++serializationConstructorSymnum;
return "sun/reflect/GeneratedSerializationConstructorAccessor" + var2;
} else {
var2 = ++constructorSymnum;
return "sun/reflect/GeneratedConstructorAccessor" + var2;
}
} else {
var2 = ++methodSymnum;
return "sun/reflect/GeneratedMethodAccessor" + var2;
}
}
而GeneratedMethodAccessor1这个生成类的ClassLoader即DelegatingClassLoader。
最后
以上就是精明航空为你收集整理的深入分析Java方法反射的全部内容,希望文章能够帮你解决深入分析Java方法反射所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复