一、反射的概述
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
简而言之:在Java 中要想不通过常规手段(new Object的方式)操作一个类以及它的实例对象,必须先要获取到该类的字节码文件对象。而Java中的字节码对象就是Class类,以及该类中的方法,所以先要获取到每一个字节码文件对应的Class类型的对象.
二、反射基础
RTTI(Run-Time Type Identification)运行时类型识别。在《Thinking in Java》一书第十四章中有提到,其作用是在运行时识别一个对象的类型和类的信息。主要有两种方式:一种是“传统的”RTTI,它假定我们在编译时已经知道了所有的类型(泛型机制);另一种是“反射”机制,它允许我们在运行时发现和使用类的信息。
反射就是把java类中的各种成分:成员变量,构造方法,普通方法,方法和变量的修饰符等等,映射成一个个的Java对象。
例如:一个类有:成员变量、方法、构造方法、包等等信息,利用反射技术可以对一个类进行解剖,把个个组成部分映射成一个个对象。
这里我们首先需要理解 Class类,以及类的加载机制; 然后基于此我们如何通过反射获取Class类以及类中的成员变量、方法、构造方法等。
三、Class类介绍
Class类,Class类也是一个实实在在的类,存在于JDK的java.lang包中。Class类的实例表示java应用运行时的类(class and enum)或接口(interface and annotation)
每个java类运行时都在JVM里表现为一个class对象,可通过如下三种方法获取class对象。
- 可通过类名.class
- 实例对象.getClass()
- Class.forName("类名")
数组同样也被映射为Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象。基本类型boolean,byte,char,short,int,long,float,double和关键字void同样表现为 Class 对象。
顺便说一下:如何看源码。
JDK源码是编写者智慧结晶,是众多开发者,合力的结果。里面的每一段代码,写出来都是有意义的,不是说想怎么写就怎么写。都是这些大佬,经过深思熟虑想出来的方式方法。一定程度上都是考虑到了各种因素:兼容性,可读性,性能,组织结构,所以不要质疑大佬写的代码。
我们要学习的是大佬写代码的思路和方式方法和解决问题的能力。通过阅读源码,更好的了解Java的性能和机制。
如何看源码:
第一步:看类的继承结构,以及该类本身的类型以及修饰符,实现了哪些接口,具备哪些功能,有没有泛型。
第二步:看静态代码块,是否有提前加载的内容。看构造方法,是否有私有的构造方法。以及该类的实例化模式
第三步:看类的成员变量,有哪些数学,分别代表了什么行为,用于哪些方法
第四步:看方法,读注释,看返回值。方法的修饰类型,返回值,调用方式,是否支持泛型
Class源码分析
1、Class类继承结构和修饰类型
public final class Class<T> implements java.io.Serializable,
GenericDeclaration,
Type,
AnnotatedElement {
}
到这我们也就可以得出以下几点信息:
首先Class的结构上:实现了四个接口,直接说明Class类具有四种能力。分别是:
1、java 序列化接口 Serializable 支持序列化
2、GenericDeclaration 泛型通用接口里面只有一个方法
//返回定义泛型对象时顺序声明的泛型对象的数组,这些对象表示由此GenericDeclaration对象表示
public TypeVariable<?>[] getTypeParameters();
3、 Type 泛型描述接口
//返回制定反响的具体信息和参数的类型名称
default String getTypeName() {
return toString();
}
4、AnnotatedElement 注解接口:实现这个接口(AnnotatedElement)的对象代表了在当前JVM中的一个“被注解元素”(可以是Class,Method,Field,Constructor,Package等)。
所以Class类具有的能力:可以被序列化,可以获取类的注解,泛型对象以及对应的泛型参数。
其次:Class类通被final修饰说明:
1、Class类被初始化后,在内存里的地址,不能够被修改,但是其值不能修改并不意味着其所指向的对象不能修改。
2、表示该类是无法被任何其他类继承的,意味着此类在一个继承树中是一个叶子类,并且此类的设计已被认为很完美而不需要进行修改或扩展。(大佬们就是这么自信)
2、成员变量
1、常量
//定义注解类型的常量
private static final int ANNOTATION = 0x00002000;
//定义枚举类型的常量
private static final int ENUM = 0x00004000;
//定义复合类的常量
private static final int SYNTHETIC = 0x00001000;
0x代表该常量是使用16进制进行解析,三个常量是用于与类的修饰类型做&操作得到具体类型的判断
// 获取类或者接口的修饰类型:
// public,protected,private,final,static,abstract,interface
public native int getModifiers();
//返回 boolean :如果是注解类型返回true
public boolean isAnnotation() {
return (getModifiers() & ANNOTATION) != 0;
}
//返回 boolean :如果是复合类返回true
public boolean isSynthetic() {
return (getModifiers() & SYNTHETIC) != 0;
}
//枚举判断特殊一点,父类要继承ENUM类,修饰符是枚举修饰符才算是枚举
public boolean isEnum() {
// An enum must both directly extend java.lang.Enum and have
// the ENUM bit set; classes for specialized enum constants
// don't do the former.
return (this.getModifiers() & ENUM) != 0 &&
this.getSuperclass() == java.lang.Enum.class;
}
有些小伙伴可能会对复合类有疑问?这边引用stackFlow大佬的回答:
回答:Java具有在运行期间动态生成字节码大的能力。这些被动态生成的类就被称之为复合类或者动态代理。比如java通过proxy包下的方法实现的类。
比如:其他开源库(例如 CGLIB 和 ASM)也提供生成复合类,并且比 JRE 提供的库更强大。 合成类被 AOP(面向方面编程)库(如 Spring AOP 和 AspectJ)以及 ORM 库(如 Hibernate)所使用。
2、引用类型的普通变量
// 不会被序列化的构造函数对象缓存,而且对于执行线程,被修改时是可见的
private volatile transient Constructor<T> cachedConstructor;
//不会被序列化的Class对象,当构造函数对象缓存被修改时,需要重新进行赋值
private volatile transient Class<?> newInstanceCallerCache;
//缓存该类的全类名,避免多次调用Java虚拟机
private transient String name;
//类加载器 不是由私有的构造函数初始化的,而是由虚拟机进行初始化
private final ClassLoader classLoader;
//java 类加载时安全校验机制
private static java.security.ProtectionDomain allPermDomain;
//是否使用缓存 :有确定的反射结果,才会进行缓存
private static boolean useCaches = true;
//反射数据的软引用,且不会被序列化
private volatile transient SoftReference<ReflectionData<T>> reflectionData;
//调用JVM TI RedefineClasses()方法,计数器。(重新定义该类或者父类)
private volatile transient int classRedefinedCount = 0;
// 生成类的相关信息保存,懒加载完成
private volatile transient ClassRepository genericInfo;
//类的序列化流工具
private static final ObjectStreamField[] serialPersistentFields =
new ObjectStreamField[0];
//反射工厂,提供反射相关操作
private static ReflectionFactory reflectionFactory;
//是否可以查询系统属性 boolean类型
private static boolean initted = false;
//获取枚举类中的元素 数组类型
private volatile transient T[] enumConstants = null;
//生成枚举类型的Map K:全类名字,T:枚举类型
private volatile transient Map<String, T> enumConstantDirectory = null;
//注解类型数据
private volatile transient AnnotationData annotationData;
//注解类型
private volatile transient AnnotationType annotationType;
//该字节码类型的备份,由classValueMap 存储
transient ClassValue.ClassValueMap classValueMap;
从修饰符可以看出Class类中用的最多便是 volatile 和 transient
volatile:避免指令重排序,变量被修改时对所有线程可见。
transient:保证该成员变量不被序列化,变量将不再是对象持久化的一部分,该变量内容在序列化后无法获得访问。
3、构造方法和成员方法
3.1、构造方法
// C++/C 写的注册方法
private static native void registerNatives();
// 类加载器由JVM初始化,并且这个字段会从反射中过滤,如果想通过getDeclaredField获取类加载器会抛出NoSuchFieldException
private final ClassLoader classLoader;
// 确定要一点初始化的过程,应该是初始化了类加载器的。
static {
registerNatives();
}
//私有构造器,只有JVM才能调用和创建Class对象,并且这个构造方法是先于对象的构造方法产生的。
private Class(ClassLoader loader) {
classLoader = loader;
}
私有构造方法说明:Class 源代码中的注释指出,初始化值,使JIT知道 classLoader 字段不为空。
JIT编译(just-in-time compilation)狭义来说是当某段代码即将第一次被执行时进行编译,因而叫“即时编译”
大佬的回答:java - JIT optimization preventing techniques - Stack Overflow
3.2、核心成员方法
- 再来看看 Class类的方法
方法名 | 说明 |
---|---|
forName() | (1)获取Class对象的一个引用,但引用的类还没有加载(该类的第一个对象没有生成)就加载了这个类。 (2)为了产生Class引用,forName()立即就进行了初始化。 |
Object-getClass() | 获取Class对象的一个引用,返回表示该对象的实际类型的Class引用。 |
getName() | 取全限定的类名(包括包名),即类的完整名字。 |
getSimpleName() | 获取类名(不包括包名) |
getCanonicalName() | 获取全限定的类名(包括包名) |
isInterface() | 判断Class对象是否是表示一个接口 |
getInterfaces() | 返回Class对象数组,表示Class对象所引用的类所实现的所有接口。 |
getSupercalss() | 返回Class对象,表示Class对象所引用的类所继承的直接基类。应用该方法可在运行时发现一个对象完整的继承结构。 |
newInstance() | 返回一个Oject对象,是实现“虚拟构造器”的一种途径。使用该方法创建的类,必须带有无参的构造器。 |
getFields() | 获得某个类的所有的公共(public)的字段,包括继承自父类的所有公共字段。 类似的还有getMethods和getConstructors。 |
getDeclaredFields | 获得某个类的自己声明的字段,即包括public、private和proteced,默认但是不包括父类声明的任何字段。类似的还有getDeclaredMethods和getDeclaredConstructors。 |
由于大部分方法都是Native方法,这里我们选取重点的方法说明:
方法一:forName(Sting name)
说明:通过全限定类名,来获取对应该类的字节码对象:Class<T> 其中T:全限定类名类型,可以是类,接口,枚举。
// 通过全限定类名加载对应的Class<T> 对象
@CallerSensitive
public static Class<?> forName(String className)
throws ClassNotFoundException {
//返回调用此方法的调用者Class对象
Class<?> caller = Reflection.getCallerClass();
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}
//实例化Class类型对象
//name:全限定类名
//initialize:是否需要立即初始化
//loader:类加载器
//caller: 调用者class对象
private static native Class<?> forName0(String name, boolean initialize,
ClassLoader loader,
Class<?> caller)
throws ClassNotFoundException;
实际调用的是forName0()方法,
其中令人疑惑的是Reflection.getCallerClass()方法和@CallerSensitive注解
Reflection.getCallerClass():返回调用此方法的方法的调用者的类,忽略与java.lang.reflect.Method.invoke()相关联的框架及其实现
@CallerSensitive public static native Class<?> getCallerClass();
主要有两个目的:
1、 @CallerSensitive注解:用来找到真正发起反射请求的类的,保证该类是符合JVM的安全策略,解决通过反射导致的安全漏洞。
这个漏洞简单说:避免多重反射来提升类的权限。
这个注解是为了配合Reflection.getCallerClass()用的,Reflection.getCallerClass()作用是返回它的调用者,但是要使用Reflection.getCallerClass()就必须满足两个条件。
第一个条件使用他的类必须是启动类或者拓展类指定路径上的类。
第二个是必须加上这个注解。也就是说,如果用户想使用Reflection.getCallerClass(),除了加上这个注解外,还需要指定自己定义的类位于启动类或者拓展类的类路径下,即需要使用-Xbootclasspath参数去指定
2、获取到调用该方法的源头类的Class,从而获取到对应的类加载器。
具体介绍Reflection.getCallerClass()的文章链接,这里提供两个
1、Reflection.getCallerClass()-解释
2、Reflection.getCallerClass()-解释
阅读完这两篇文章,应该就明白了。
简单来说:
反射相关的类是有很高权限的,而在 我->反射1->反射2 这样的调用链上,反射2检查权限时看到的是反射1的类,这就被欺骗了,导致安全漏洞。使用CallerSensitive后,getCallerClass不再用固定深度去寻找actual caller(“我”),而是把所有跟反射相关的接口方法都标注上CallerSensitive,搜索时凡看到该注解都直接跳过,这样就有效解决了前面举例的问题
核心方法二 public T newInstance()
返回泛型,该泛型,是由forName(String name) 方法中传入的全限定类名指定
源码:
@CallerSensitive
public T newInstance()
throws InstantiationException, IllegalAccessException
{
//校验Java安全管理器,不为null
if (System.getSecurityManager() != null) {
//校验调用该类的权限能否通过
checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), false);
}
//注意:以下代码可能不是严格的符合目前Java的内存模型(1.8)。
// 构造函数从这里开始
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);
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;
}
}
// 运行构造函数
try {
// 默认调用的是无参的构造函数
return tmpConstructor.newInstance((Object[])null);
} catch (InvocationTargetException e) {
Unsafe.getUnsafe().throwException(e.getTargetException());
return null;
}
}
代码流程:
1、同样也有@CallerSensitive注解,不在赘述
2、获取java安全管理器:System.getSecurityManager()
此方法的返回类型为SecurityManager ,仅在当前为当前接口建立安全管理器时才返回安全管理器,而在没有为当前应用程序建立安全管理器时返回null。
3、权限校验检查
private void checkMemberAccess(int which, Class<?> caller, boolean checkProxyInterfaces)
参数1:which:int :常量,可以获取的权限类型
参数2: caller:Class<?> 调用类的字节码对象
参数3: checkProxyInterfaces:boolean 是否校验代理接口
checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), false);
入参:Member.PUBLIC,Reflection.getCallerClass(),false
/*
Member 是一个接口,这个接口定义了单个成员(字段或方法)或构造函数的标识。
*/
public interface Member {
//可以访问类或者接口中通过 public 修饰的成员信息,包括继承的成员
public static final int PUBLIC = 0;
// 定义了类或者接口中声明的成员信息,不包括继承的成员
public static final int DECLARED = 1;
// 获取声明类或者接口的字节码对象
public Class<?> getDeclaringClass();
//返回成员的名称 eg:成员变量的名字,成员方法,构造函数
public String getName();
//返回成员的修饰符
public int getModifiers();
//判断该成员是否是动态生成的。 true:动态生成。
public boolean isSynthetic();
}
checkMemberAccess() 具体实现
// 校验成员的权限
private void checkMemberAccess(int which, Class<?> caller, boolean checkProxyInterfaces) {
//获取安全管理器
final SecurityManager s = System.getSecurityManager();
if (s != null) {
//获取调用类的类加载器
final ClassLoader ccl = ClassLoader.getClassLoader(caller);
//返回Class类的类加载器
final ClassLoader cl = getClassLoader0();
//如果调用成员的权限不是public
if (which != Member.PUBLIC) {
//两者的类加载器不一致
if (ccl != cl) {
//校验权限是否是获取声明成员权限
s.checkPermission(SecurityConstants.CHECK_MEMBER_ACCESS_PERMISSION);
}
}
//校验包的权限
this.checkPackageAccess(ccl, checkProxyInterfaces);
}
}
//Class类初始化的类加载器
ClassLoader getClassLoader0() { return classLoader; }
// accessDeclaredMembers:获取声明成员权限
public static final RuntimePermission CHECK_MEMBER_ACCESS_PERMISSION = new RuntimePermission("accessDeclaredMembers");
//检验包权限
private void checkPackageAccess(final ClassLoader ccl, boolean checkProxyInterfaces) {
final SecurityManager s = System.getSecurityManager();
if (s != null) {
final ClassLoader cl = getClassLoader0();
//通过反射工具类校验包的权限
if (ReflectUtil.needsPackageAccessCheck(ccl, cl)) {
String name = this.getName();
int i = name.lastIndexOf('.');
if (i != -1) {
//获取包名
String pkg = name.substring(0, i);
if (!Proxy.isProxyClass(this) || ReflectUtil.isNonPublicProxyClass(this)) {
s.checkPackageAccess(pkg);
}
}
}
if (checkProxyInterfaces && Proxy.isProxyClass(this)) {
ReflectUtil.checkProxyPackageAccess(ccl, this.getInterfaces());
}
}
}
//校验包权限的方法
public static boolean needsPackageAccessCheck(ClassLoader var0, ClassLoader var1) {
// 源类加载器 不为 null ,目标类加载器不等于源类加载器
if (var0 != null && var0 != var1) {
//目标类加载器 等于 null
if (var1 == null) {
//说明使用的都是同一个类加载器
return true;
} else {
//判断父类是否使用的是同一个类加载器
return !isAncestor(var0, var1);
}
} else {
return false;
}
}
//校验父加载器是否一致
private static boolean isAncestor(ClassLoader var0, ClassLoader var1) {
ClassLoader var2 = var1;
do {
//目标类的类加载器的父类
var2 = var2.getParent();
if (var0 == var2) {
return true;
}
} while(var2 != null);
return false;
}
4、核心逻辑
// 构造函数对象缓存
private volatile transient Constructor<T> cachedConstructor;
// 字节码对象缓存
private volatile transient Class<?> newInstanceCallerCache;
// 判断是否有缓存的构造函数
if (cachedConstructor == null) {
// 如果是Class类本身
if (this == Class.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);
//安全校验
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;
//获取构造函数的权限修饰符
int modifiers = tmpConstructor.getModifiers();
//校验构造函数的权限是否是 public
if (!Reflection.quickCheckMemberAccess(this, modifiers)) {
// 获取调用类的字节码对象
Class<?> caller = Reflection.getCallerClass();
if (newInstanceCallerCache != caller) {
// 成员权限的校验
Reflection.ensureMemberAccess(caller, this, null, modifiers);
// 缓存调用类的字节码对象
newInstanceCallerCache = caller;
}
}
其中:
Class<?>[] empty = {};
//核心方法,获取构造函数
final Constructor<T> c = getConstructor0(empty, Member.DECLARED);
注意到 empty是空的Class<?> 数组
//参数1: 字节码对象数组
//参数2: 权限标识常量
private Constructor<T> getConstructor0(Class<?>[] parameterTypes,
int which) throws NoSuchMethodException
{
//获取构造方法,不止一个,用数组表示,并且为public的构造方法
Constructor<T>[] constructors = privateGetDeclaredConstructors((which == Member.PUBLIC));
//匹配空参的构造方法
for (Constructor<T> constructor : constructors) {
if (arrayContentsEq(parameterTypes,
constructor.getParameterTypes())) {
return getReflectionFactory().copyConstructor(constructor);
}
}
// 没有获取到,则返回没有该方法异常
throw new NoSuchMethodException(getName() + ".<init>" + argumentTypesToString(parameterTypes));
}
核心方法 privateGetDeclaredConstructors(boolean publicOnly)
用于获取public修饰的构造方法
private Constructor<T>[] privateGetDeclaredConstructors(boolean publicOnly) {
checkInitted();
Constructor<T>[] res;
//获取反射得到数据
ReflectionData<T> rd = reflectionData();
if (rd != null) {
res = publicOnly ? rd.publicConstructors : rd.declaredConstructors;
if (res != null) return res;
}
//如果是接口
if (isInterface()) {
@SuppressWarnings("unchecked")
Constructor<T>[] temporaryRes = (Constructor<T>[]) new Constructor<?>[0];
res = temporaryRes;
} else {
res = getDeclaredConstructors0(publicOnly);
}
if (rd != null) {
if (publicOnly) {
rd.publicConstructors = res;
} else {
rd.declaredConstructors = res;
}
}
return res;
}
- checkInitted() 来判断是否配置了useCached属性,通过sun.reflect.noCaches来配置,如果配置的话,将useCached改为配置的属性
- reflectionData()获取缓存当中的数据,如果缓存当中有数据,直接返回缓存当中的构造方法
- 如果缓存当中没有数据,就从JVM当中读取数据
- 如果是接口类型,直接生成一个数组长度为0的Constructor数组,因为接口没有构造方法
- 如果不是接口类型,从JVM当中获取getDeclaredConstructors0 是native方法
- 最后更新缓存reflectionData当中的构造方法
接下来,我们来看下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
- 如果可以使用缓存,并且缓存当中的数据不为null,而且缓存没有失效,(缓存当中redefinedCount等于classRedefinedCount的值),直接返回缓存当中的reflectionData
- 如果以上都不是的话,就创建新的reflectionData,并保存到缓存当中去
我们来看下newReflectionData方法的实现
private ReflectionData<T> newReflectionData(SoftReference<ReflectionData<T>> oldReflectionData,
int classRedefinedCount) {
if (!useCaches) return null;
while (true) {
ReflectionData<T> rd = new ReflectionData<>(classRedefinedCount);
// 使用CAS 进行更新
if (Atomic.casReflectionData(this, oldReflectionData, new SoftReference<>(rd))) {
return rd;
}
// 进行重试
oldReflectionData = this.reflectionData;
classRedefinedCount = this.classRedefinedCount;
if (oldReflectionData != null &&
(rd = oldReflectionData.get()) != null &&
rd.redefinedCount == classRedefinedCount) {
return rd;
}
}
}
- 首先,还是判断是否使用缓存,如果不使用,直接返回
- 使用while+CAS方式更新数据,创建一个新的ReflectionData,如果更新成功直接返回,否则进入3
- 获取到旧的reflectionData和classRedefinedCount的值,如果旧的值不为null, 并且缓存未失效,说明其他线程更新成功了,直接返回
4、Class对象小结
一旦类被加载了到了内存中,那么不论通过哪种方式获得该类的Class对象,它们返回的都是指向同一个java堆地址上的Class引用。jvm不会创建两个相同类型的Class对象
其实对于任意一个Class对象,都需要由它的类加载器和这个类本身一同确定其在就Java虚拟机中的唯一性,也就是说,即使两个Class对象来源于同一个Class文件,只要加载它们的类加载器不同,那这两个Class对象就必定不相等。这里的“相等”包括了代表类的Class对象的equals()、isAssignableFrom()、isInstance()等方法的返回结果,也包括了使用instanceof关键字对对象所属关系的判定结果。所以在java虚拟机中使用双亲委派模型来组织类加载器之间的关系,来保证Class对象的唯一性。