用傳統的OOP思想來說,任何一個你寫好的且編譯過的生成的Class文件,在被類加載器加載後,都會對應有一個java.lang.Class這個類的實例。所以說,每個類的自有的方法屬性(類結構)自然被包含在了這個對應的實例上,因此就可以獲取到。
一、原理簡介
public class TestClassLoad {
public static void main(String[] args) throws Exception {
Class<?> clz = Class.forName("A");
Object o = clz.newInstance();
Method m = clz.getDeclaredMethod("hello", null);
m.invoke(o);
}
static class A{
public void hello() {
System.out.println("hello world");
}
}
}
上面就是最常見的反射使用的例子,前兩行實現了類的裝載、鏈接和初始化(newInstance方法實際上也是使用反射調用了方法),後兩行實現了從class對象中獲取到method對象然後執行反射調用。下面簡單分析一下後兩行的原理。
設想一下,如果想要實現method.invoke(action,null)調用action對象的myMethod方法,只需要實現這樣一個Method類即可:
Class Method{
public Object invoke(Object obj,Object[] param){
A instance=(A)obj;
return instance.foo();
}
}
反射的原理之一其實就是動態的生成類似於上述的字節碼,加載到jvm中運行。
二、獲取Method對象
調用Class類的getDeclaredMethod可以獲取指定方法名和參數的方法對象Method。
getDeclaredMethod()方法
public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
throws NoSuchMethodException, SecurityException {
checkMemberAccess(Member.DECLARED, ClassLoader.getCallerClassLoader(), true);
Method method = searchMethods(privateGetDeclaredMethods(false), name, parameterTypes); //關注這裏的兩個方法
if (method == null) {
throw new NoSuchMethodException(getName() + "." + name + argumentTypesToString(parameterTypes));
}
return method;
}
其中privateGetDeclaredMethods方法從緩存或JVM中獲取該Class中申明的方法列表,searchMethods方法將從返回的方法列表裏找到一個匹配名稱和參數的方法對象。
private static Method searchMethods(Method[] methods,String name,
Class<?>[] parameterTypes){
Method res = null;
String internedName = name.intern();
for (int i = 0; i < methods.length; i++) {
Method m = methods[i];
if (m.getName() == internedName
&& arrayContentsEq(parameterTypes, m.getParameterTypes())
&& (res == null
|| res.getReturnType().isAssignableFrom(m.getReturnType())))
res = m;
}
return (res == null ? res : getReflectionFactory().copyMethod(res));
}
如果找到一個匹配的Method,則重新copy一份返回,即Method.copy()方法
Method copy() {
Method res = new Method(clazz, name, parameterTypes, returnType,
exceptionTypes, modifiers, slot, signature,
annotations, parameterAnnotations, annotationDefault);
res.root = this;
res.methodAccessor = methodAccessor;
return res;
}
所次每次調用getDeclaredMethod方法返回的Method對象其實都是一個新的對象,且新對象的root屬性都指向原來的Method對象,如果需要頻繁調用,最好把Method對象緩存起來。
接下來看privateGetDeclaredMethods()方法,用於從緩存或JVM中獲取該Class中申明的方法列表,代碼如下:
private Method[] privateGetDeclaredMethods(boolean publicOnly) {
checkInitted();
Method[] res;
ReflectionData<T> rd = reflectionData();
if (rd != null) {
res = publicOnly ? rd.declaredPublicMethods : rd.declaredMethods;
if (res != null) return res;
}
// No cached value available; request value from VM
res = Reflection.filterMethods(this, getDeclaredMethods0(publicOnly));
if (rd != null) {
if (publicOnly) {
rd.declaredPublicMethods = res;
} else {
rd.declaredMethods = res;
}
}
return res;
}
其中reflectionData()方法實現如下:
// Lazily create and cache ReflectionData
private ReflectionData<T> reflectionData() {
SoftReference<ReflectionData<T>> reflectionData = this.reflectionData;
int classRedefinedCount = this.classRedefinedCount;
ReflectionData<T> rd;
if (useCaches &&
reflectionData != null &&
(rd = reflectionData.get()) != null &&
rd.redefinedCount == classRedefinedCount) {
return rd;
}
// else no SoftReference or cleared SoftReference or stale ReflectionData
// -> create and replace new instance
return newReflectionData(reflectionData, classRedefinedCount);
}
這裏有個比較重要的數據結構ReflectionData,用來緩存從JVM中讀取類的如下屬性數據:
// reflection data that might get invalidated when JVM TI RedefineClasses() is called
private static class ReflectionData<T> {
volatile Field[] declaredFields;
volatile Field[] publicFields;
volatile Method[] declaredMethods;
volatile Method[] publicMethods;
volatile Constructor<T>[] declaredConstructors;
volatile Constructor<T>[] publicConstructors;
// Intermediate results for getFields and getMethods
volatile Field[] declaredPublicFields;
volatile Method[] declaredPublicMethods;
volatile Class<?>[] interfaces;
// Value of classRedefinedCount when we created this ReflectionData instance
final int redefinedCount;
ReflectionData(int redefinedCount) {
this.redefinedCount = redefinedCount;
}
}
從reflectionData()方法實現可以看出:reflectionData對象是SoftReference類型的,說明在內存緊張時可能會被回收,不過也可以通過-XX:SoftRefLRUPolicyMSPerMB參數控制回收的時機,只要發生GC就會將其回收,如果reflectionData被回收之後,又執行了反射方法,那隻能通過newReflectionData方法重新創建一個這樣的對象了,newReflectionData方法實現如下:
private ReflectionData<T> newReflectionData(SoftReference<ReflectionData<T>> oldReflectionData,
int classRedefinedCount) {
if (!useCaches) return null;
while (true) {
ReflectionData<T> rd = new ReflectionData<>(classRedefinedCount);
// try to CAS it...
if (Atomic.casReflectionData(this, oldReflectionData, new SoftReference<>(rd))) {
return rd;
}
// else retry
oldReflectionData = this.reflectionData;
classRedefinedCount = this.classRedefinedCount;
if (oldReflectionData != null &&
(rd = oldReflectionData.get()) != null &&
rd.redefinedCount == classRedefinedCount) {
return rd;
}
}
}
static <T> boolean casReflectionData(Class<?> clazz,
SoftReference<ReflectionData<T>> oldData,
SoftReference<ReflectionData<T>> newData) {
return unsafe.compareAndSwapObject(clazz, reflectionDataOffset, oldData, newData);
}
方法調用了casReflectionData(),通過unsafe.compareAndSwapObject方法重新設置reflectionData字段;
在privateGetDeclaredMethods方法中,如果通過reflectionData()獲得的ReflectionData對象不爲空,則嘗試從ReflectionData對象中獲取declaredMethods屬性,如果是第一次,或則被GC回收之後,重新初始化後的類屬性爲空,則需要重新到JVM中獲取一次,並賦值給ReflectionData,下次調用就可以使用緩存數據了。
三、invoke()方法
1、MethodAccessor
public Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass(1);
checkAccess(caller, clazz, obj, modifiers);
}
}
MethodAccessor ma = methodAccessor; // read volatile
if (ma == null) {
ma = acquireMethodAccessor(); //獲取methodAccessor方法
}
return ma.invoke(obj, args);
}
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是啥呢?
public interface MethodAccessor {
public Object invoke(Object obj, Object[] args)
throws IllegalArgumentException, InvocationTargetException;
}
可以看到它只是一個單方法接口,其invoke()方法與Method.invoke()的對應。
創建MethodAccessor實例的是ReflectionFactory,裏面方法如下。
public MethodAccessor newMethodAccessor(Method var1) {
checkInitted();
if(noInflation) { //默認爲false
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;
}
}
可以看到生成MethodAccessor有兩個版本,一個是Java實現的,另一個是native code實現的。Java實現的版本在初始化時需要較多時間,但長久來說性能較好;native版本正好相反,啓動時相對較快,但運行時間長了之後速度就比不過Java版了。這是HotSpot的優化方式帶來的性能特性,同時也是許多虛擬機的共同點:跨越native邊界會對優化有阻礙作用,它就像個黑箱一樣讓虛擬機難以分析也將其內聯,於是運行時間長了之後反而是託管版本的代碼更快些。
爲了權衡兩個版本的性能,Sun的JDK使用了“inflation”的技巧:讓Java方法在被反射調用時,開頭15次使用native版,等反射調用次數超過閾值時則生成一個專用的MethodAccessor實現類,生成其中的invoke()方法的字節碼,以後對該Java方法的反射調用就會使用Java版。
2、native
在ReflectionFactory類中,noInflation默認爲false,方法newMethodAccessor都會返回DelegatingMethodAccessorImpl對象
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;
}
}
其實,DelegatingMethodAccessorImpl對象就是一個代理對象,負責調用被代理對象delegate的invoke方法,其中delegate參數目前是NativeMethodAccessorImpl對象,所以最終Method的invoke方法調用的是NativeMethodAccessorImpl對象invoke方法,實現如下:
NativeMethodAccessorImpl類
class NativeMethodAccessorImpl extends MethodAccessorImpl {
private Method method;
private DelegatingMethodAccessorImpl parent;//這是一個間接層,方便在native與Java版的MethodAccessor之間實現切換
private int numInvocations;
NativeMethodAccessorImpl(Method method) {
this.method = method;
}
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);
}
void setParent(DelegatingMethodAccessorImpl parent) {
this.parent = parent;
}
private static native Object invoke0(Method m, Object obj, Object[] args);
}
每次調用時,次數計數器加一,一旦超過閾值,則通過generateMethod方法生成Java版的MethodAccessor的實現類,並設置爲delegate對象,這樣下次執行Method.invoke時,就調用新建的MethodAccessor對象的invoke()方法了。
DelegatingMethodAccessorImpl就是一個間接層,方便在native與Java版的MethodAccessor之間實現切換。
注意到關鍵的invoke0()方法是個native方法。它在HotSpot VM裏是由JVM_InvokeMethod()函數所支持的。
3、java版
回到Java的一側。generateMethod方法在生成MethodAccessorImpl對象時,會在內存中生成對應的字節碼,並調用ClassDefiner.defineClass創建對應的class對象,部分代碼如下:
return (MagicAccessorImpl)AccessController.doPrivileged(new PrivilegedAction() {
public MagicAccessorImpl run() {
try {
return (MagicAccessorImpl)ClassDefiner.defineClass(var13, var17, 0, var17.length, var1.getClassLoader()).newInstance();
} catch (IllegalAccessException | InstantiationException var2) {
throw new InternalError(var2);
}
}
});
在ClassDefiner.defineClass方法實現中,每被調用一次都會生成一個DelegatingClassLoader類加載器對象
static Class<?> defineClass(String var0, byte[] var1, int var2, int var3, final ClassLoader var4) {
ClassLoader var5 = (ClassLoader)AccessController.doPrivileged(new PrivilegedAction() {
public ClassLoader run() {
return new DelegatingClassLoader(var4);
}
});
return unsafe.defineClass(var0, var1, var2, var3, var5, (ProtectionDomain)null);
}
這裏每次都生成新的類加載器,是爲了性能考慮,在某些情況下可以卸載這些生成的類,因爲類的卸載是只有在類加載器可以被回收的情況下才會被回收的,如果用了原來的類加載器,那可能導致這些新創建的類一直無法被卸載,從其設計來看本身就不希望這些類一直存在內存裏的,在需要的時候有就行了。
對本文開頭的例子的A.hello(),生成的Java版MethodAccessor大致如下:
public class GeneratedMethodAccessor1 extends MethodAccessorImpl {
public GeneratedMethodAccessor1() {
super();
}
public Object invoke(Object obj, Object[] args)
throws IllegalArgumentException, InvocationTargetException {
// prepare the target and parameters
if (obj == null) throw new NullPointerException();
try {
A target = (A) obj;
if (args.length != 1) throw new IllegalArgumentException();
String arg0 = (String) args[0];
} catch (ClassCastException e) {
throw new IllegalArgumentException(e.toString());
} catch (NullPointerException e) {
throw new IllegalArgumentException(e.toString());
}
// make the invocation
try {
target.hello(arg0);
} catch (Throwable t) {
throw new InvocationTargetException(t);
}
}
}
就反射調用而言,這個invoke()方法非常乾淨(然而就“正常調用”而言這額外開銷還是明顯的)。注意到參數數組被拆開了,把每個參數都恢復到原本沒有被Object[]包裝前的樣子,然後對目標方法做正常的invokevirtual調用。由於在生成代碼時已經循環遍歷過參數類型的數組,生成出來的代碼裏就不再包含循環了。
4、性能比較
從變化趨勢上看,第1次和第16次調用是最耗時的(初始化NativeMethodAccessorImpl和字節碼拼裝MethodAccessorImpl)。畢竟初始化是不可避免的,而native方式的初始化會更快,因此前幾次的調用會採用native方法。
隨着調用次數的增加,每次反射都使用JNI跨越native邊界會對優化有阻礙作用,相對來說使用拼裝出的字節碼可以直接以Java調用的形式實現反射,發揮了JIT優化的作用,避免了JNI爲了維護OopMap(HotSpot用來實現準確式GC的數據結構)進行封裝/解封裝的性能損耗。因此在已經創建了MethodAccessor的情況下,使用Java版本的實現會比native版本更快。所以當調用次數到達一定次數(15次)後,會切換成Java實現的版本,來優化未來可能的更頻繁的反射調用。
轉載自:http://rednaxelafx.iteye.com/blog/548536
http://www.fanyilun.me/2015/10/29/Java%E5%8F%8D%E5%B0%84%E5%8E%9F%E7%90%86/