反射&动态代理

反射

Java 的动态性体现在:反射机制、动态执行脚本语言、动态操作字节码

反射:在运行时加载、探知、使用编译时未知的类。


Class.forName 使用的类加载器是调用者的类加载器

Class

表示 Java 中的类型(class、interface、enum、annotation、primitive type、void)本身。


一个类被加载之后,JVM 会创建一个对应该类的 Class 对象,类的整个结构信息会放在相应

的 Class 对象中。

这个 Class 对象就像一个镜子一样,从中可以看到类的所有信息。

反射的核心就是 Class

如果多次执行 forName 等加载类的方法,类只会被加载一次;一个类只会形成一个 Class

对象,无论执行多少次加载类的方法,获得的 Class 都是一样的。

用途


性能

反射带来灵活性的同时,也有降低程序执行效率的弊端

setAccessible 方法不仅可以标记某些私有的属性方法为可访问的属性方法,并且可以提高程

序的执行效率

实际上是启用和禁用访问安全检查的开关。如果做检查就会降低效率;关闭检查就可以提高

效率。

反射调用方法比直接调用要慢大约 30 倍,如果跳过安全检查的话比直接调用要慢大约 7 倍

开启和不开启安全检查对于反射而言可能会差 4 倍的执行效率。

为什么慢?

1)验证等防御代码过于繁琐,这一步本来在 link 阶段,现在却在计算时进行验证

2)产生很多临时对象,造成 GC 与计算时间消耗

3)由于缺少上下文,丢失了很多运行时的优化,比如 JIT(它可以看作 JVM 的重要评测标准

之一)

当然,现代 JVM 也不是非常慢了,它能够对反射代码进行缓存以及通过方法计数器同样实

现 JIT 优化,所以反射不一定慢。

实现

反射在 Java 中可以直接调用,不过最终调用的仍是 native 方法,以下为主流反射操作的实

现。

Class.forName 的实现

Class.forName 可以通过包名寻找 Class 对象,比如 Class.forName("java.lang.String")。

在 JDK 的源码实现中,可以发现最终调用的是 native 方法 forName0(),它在 JVM 中调用的

实际是 FindClassFromCaller(),原理与 ClassLoader 的流程一样。

public static Class<?> forName(String className)

throws ClassNotFoundException {

Class<?> caller = Reflection.getCallerClass();

return forName0(className, true, ClassLoader.getClassLoader(caller),

caller);

}

private static native Class<?> forName0(String name, boolean initialize,

ClassLoader loader,

Class<?> caller)

throws ClassNotFoundException;

Java_java_lang_Class_forName0(JNIEnv *env, jclass this, jstring classname,

jboolean initialize, jobject loader, jclass caller)

{

char *clname;

jclass cls = 0;

char buf[128];

jsize len;

jsize unicode_len;

if (classname == NULL) {

JNU_ThrowNullPointerException(env, 0);

return 0;

}

len = (*env)->GetStringUTFLength(env, classname);

unicode_len = (*env)->GetStringLength(env, classname);

if (len >= (jsize)sizeof(buf)) {

clname = malloc(len + 1);

if (clname == NULL) {

JNU_ThrowOutOfMemoryError(env, NULL);

return NULL;

}

} else {

clname = buf;

}

(*env)->GetStringUTFRegion(env, classname, 0, unicode_len, clname);

if (VerifyFixClassname(clname) == JNI_TRUE) {

/* slashes present in clname, use name b4 translation for exception */

(*env)->GetStringUTFRegion(env, classname, 0, unicode_len, clname);

JNU_ThrowClassNotFoundException(env, clname);

goto done;

}

if (!VerifyClassname(clname, JNI_TRUE)) { /* expects slashed name */

JNU_ThrowClassNotFoundException(env, clname);

goto done;

}

cls = JVM_FindClassFromCaller(env, clname, initialize, loader, caller);

done:

if (clname != buf) {

free(clname);

}

return cls;

}

JVM_ENTRY(jclass, JVM_FindClassFromClass(JNIEnv *env, const char *name,

jboolean init, jclass from))

JVMWrapper2("JVM_FindClassFromClass %s", name);

if (name == NULL || (int)strlen(name) > Symbol::max_length()) {

// It's impossible to create this class; the name cannot fit

// into the constant pool.

THROW_MSG_0(vmSymbols::java_lang_NoClassDefFoundError(), name);

}

TempNewSymbol h_name = SymbolTable::new_symbol(name, CHECK_NULL);

oop from_class_oop = JNIHandles::resolve(from);

Klass* from_class = (from_class_oop == NULL)

? (Klass*)NULL

: java_lang_Class::as_Klass(from_class_oop);

oop class_loader = NULL;

oop protection_domain = NULL;

if (from_class != NULL) {

class_loader = from_class->class_loader();

protection_domain = from_class->protection_domain();

}

Handle h_loader(THREAD, class_loader);

Handle h_prot (THREAD, protection_domain);

jclass result = find_class_from_class_loader(env, h_name, init, h_loader,

h_prot, true, thread);

if (TraceClassResolution && result != NULL) {

// this function is generally only used for class loading during verification.

ResourceMark rm;

oop from_mirror = JNIHandles::resolve_non_null(from);

Klass* from_class = java_lang_Class::as_Klass(from_mirror);

const char * from_name = from_class->external_name();

oop mirror = JNIHandles::resolve_non_null(result);

Klass* to_class = java_lang_Class::as_Klass(mirror);

const char * to = to_class->external_name();

tty->print("RESOLVE %s %s (verification)\n", from_name, to);

}

return result;

JVM_END

getDeclaredFields 的实现

在 JDK 源 码 中 , 可 以 知 道 class.getDeclaredFields() 方 法 实 际 调 用 的 是 native 方 法

getDeclaredFields0(),它在 JVM 主要实现步骤如下:

1)根据 Class 结构体信息,获取 field_count 与 fields[]字段,这个字段早已在 load 过程中被

放入了

2)根据 field_count 的大小分配内存、创建数组

3)将数组进行 forEach 循环,通过 fields[]中的信息依次创建 Object 对象

4)返回数组指针

主要慢在如下方面:

创建、计算、分配数组对象

对字段进行循环赋值

public Field[] getDeclaredFields() throws SecurityException {

checkMemberAccess(Member.DECLARED, Reflection.getCallerClass(), true);

return copyFields(privateGetDeclaredFields(false));

}

private Field[] privateGetDeclaredFields(boolean publicOnly) {

checkInitted();

Field[] res;

ReflectionData<T> rd = reflectionData();

if (rd != null) {

res = publicOnly ? rd.declaredPublicFields : rd.declaredFields;

if (res != null) return res;

}

// No cached value available; request value from VM

res = Reflection.filterFields(this, getDeclaredFields0(publicOnly));

if (rd != null) {

if (publicOnly) {

rd.declaredPublicFields = res;

} else {

rd.declaredFields = res;

}

}

return res;

}

private static Field[] copyFields(Field[] arg) {

Field[] out = new Field[arg.length];

ReflectionFactory fact = getReflectionFactory();

for (int i = 0; i < arg.length; i++) {

out[i] = fact.copyField(arg[i]);

}

return out;

}

Method.invoke 的实现

以下为无同步、无异常的情况下调用的步骤

1)创建 Frame

2)如果对象 flag 为 native,交给 native_handler 进行处理

3)在 frame 中执行 java 代码

4)弹出 Frame

5)返回执行结果的指针

主要慢在如下方面:

需要完全执行 ByteCode 而缺少 JIT 等优化

检查参数非常多,这些本来可以在编译器或者加载时完成

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);

}

NativeMethodAccessorImpl#invoke

public Object invoke(Object obj, Object[] args)

throws IllegalArgumentException, InvocationTargetException

{

// We can't inflate methods belonging to vm-anonymous classes because

// that kind of class can't be referred to by name, hence can't be

// found from the generated bytecode.

if (++numInvocations > ReflectionFactory.inflationThreshold()

&& !ReflectUtil.isVMAnonymousClass(method.getDeclaringClass())) {

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); }

private static native Object invoke0(Method m, Object obj, Object[] args);

Java_sun_reflect_NativeMethodAccessorImpl_invoke0

(JNIEnv *env, jclass unused, jobject m, jobject obj, jobjectArray args)

{

return JVM_InvokeMethod(env, m, obj, args);

}

JVM_ENTRY(jobject, JVM_InvokeMethod(JNIEnv *env, jobject method, jobject obj,

jobjectArray args0))

JVMWrapper("JVM_InvokeMethod");

Handle method_handle;

if (thread->stack_available((address) &method_handle) >=

JVMInvokeMethodSlack) {

method_handle = Handle(THREAD, JNIHandles::resolve(method));

Handle receiver(THREAD, JNIHandles::resolve(obj));

objArrayHandle args(THREAD, objArrayOop(JNIHandles::resolve(args0)));

oop result = Reflection::invoke_method(method_handle(), receiver, args,

CHECK_NULL);

jobject res = JNIHandles::make_local(env, result);

if (JvmtiExport::should_post_vm_object_alloc()) {

oop ret_type = java_lang_reflect_Method::return_type(method_handle());

assert(ret_type != NULL, "sanity check: ret_type oop must not be NULL!");

if (java_lang_Class::is_primitive(ret_type)) {

// Only for primitive type vm allocates memory for java object.

// See box() method.

JvmtiExport::post_vm_object_alloc(JavaThread::current(), result);

}

}

return res;

} else {

THROW_0(vmSymbols::java_lang_StackOverflowError());

}

JVM_END

class.newInstance 的实现

1)检测权限、预分配空间大小等参数

2)创建 Object 对象,并分配空间

3)通过 Method.invoke 调用构造函数(<init>())

4)返回 Object 指针

主要慢在如下方面:

参数检查不能优化或者遗漏

<init>()的查表

Method.invoke 本身耗时


public T newInstance()

throws InstantiationException, IllegalAccessException

{

if (System.getSecurityManager() != null) {

checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), false);

}

// NOTE: the following code may not be strictly correct under

// the current Java memory model.

// Constructor lookup

if (cachedConstructor == null) {

if (this == Class.class) {

throw new IllegalAccessException(

"Can not call newInstance() on the Class for java.lang.Class"

);

}

try {

Class<?>[] empty = {};

final Constructor<T> c = getConstructor0(empty, Member.DECLARED);

// Disable accessibility checks on the constructor

// since we have to do the security check here anyway

// (the stack depth is wrong for the Constructor's

// security check to work)

java.security.AccessController.doPrivileged(

new java.security.PrivilegedAction<Void>() {

public Void run() {

c.setAccessible(true);

return null;

}

});

cachedConstructor = c;

} catch (NoSuchMethodException e) {

throw (InstantiationException)

new InstantiationException(getName()).initCause(e);

}

}

Constructor<T> tmpConstructor = cachedConstructor;

// Security check (same as in java.lang.reflect.Constructor)

int modifiers = tmpConstructor.getModifiers();

if (!Reflection.quickCheckMemberAccess(this, modifiers)) {

Class<?> caller = Reflection.getCallerClass();

if (newInstanceCallerCache != caller) {

Reflection.ensureMemberAccess(caller, this, null, modifiers);

newInstanceCallerCache = caller;

}

}

// Run constructor

try {

return tmpConstructor.newInstance((Object[])null);

} catch (InvocationTargetException e) {

Unsafe.getUnsafe().throwException(e.getTargetException());

// Not reached

return null;

}

}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,367评论 6 512
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,959评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,750评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,226评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,252评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,975评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,592评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,497评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,027评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,147评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,274评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,953评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,623评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,143评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,260评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,607评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,271评论 2 358

推荐阅读更多精彩内容