1、类加载器
2、反射构造方法
3、反射成员变量
4、反射成员方法
5、反射配置文件运行类中的方法
1JVM运行时数据区
2类加载器
*A.类的加载
当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载,连接,初始化三步来实现对这个类进行初始化。
a 加载
…… 就是指将class文件读入内存,并为之创建一个Class文件的对象。
……* 任何类被使用时系统都会建立一个Class对象
b 连接
…… 验证 是否有正确的内部结构,并和其他类协调一致
…… 准备 负责为类的静态成员分配内存,并设置默认初始化值
…… 解析 将类的二进制数据中的符号引用替换为直接引用
c 初始化
…… 就是之前的初始化步骤(new 对象)
……* 注:简单的说就是:把.class文件加载到内存里,并把这个.class文件封装成一个Class类型的对象。
B.类的加载时机
以下的情况,会加载这个类。
- a. 创建类的实例
- b. 类的静态变量,或者为静态变量赋值
- c. 类的静态方法
- d. 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
- e. 初始化某个类的子类
- f. 直接使用java.exe命令来运行某个主类
* C: 类加载器
类加载器(Class Loader):顾名思义,指的是可以加载类的工具。JVM自身定义了三个类加载器:引导类加载器(Bootstrap Class Loader)、拓展类加载器(Extension Class Loader )、应用加载器(Application Class Loader)。当然,我们有时候也会自己定义一些类加载器来满足自身的需要。
负责将.class文件加载到内在中,并为之生成对应的Class对象。
* a. Bootstrap ClassLoader 引导类加载器
- 也被称为根类加载器,负责Java核心类的加载
- 比如System,String等。在JDK中JRE的lib目录下rt.jar文件中
引导类加载器(Bootstrap Class Loader): 该类加载器使JVM使用C/C++底层代码实现的加载器,用以加载JVM运行时所需要的系统类,这些系统类在{JRE_HOME}/lib目录下。由于类加载器是使用平台相关的底层C/C++语言实现的, 所以该加载器不能被Java代码访问到。但是,我们可以查询某个类是否被引导类加载器加载过。我们经常使用的系统类如:java.lang.String,java.lang.Object,java.lang*……. 这些都被放在 {JRE_HOME}/lib/rt.jar包内, 当JVM系统启动的时候,引导类加载器会将其加载到 JVM内存的方法区中。
* b. Extension ClassLoader 扩展类加载器
- 负责JRE的扩展目录中jar包的加载。
* 在JRE的lib目录下ext目录
拓展类加载器(Extension Class Loader): 该加载器是用于加载 java 的拓展类 ,拓展类一般会放在 {JRE_HOME}/lib/ext/ 目录下,用来提供除了系统类之外的额外功能。拓展类加载器是是整个JVM加载器的Java代码可以访问到的类加载器的最顶端,即是超级父加载器,拓展类加载器是没有父类加载器的。
* c. Applocatoin ClassLoader 应用类加载器
又称为是System ClassLoader 系统类加载器;
- 负责在JVM启动时加载来自java命令的class文件,以及classpath环境变量所指定的jar包和类路径。
- 我们用的是System ClassLoader 系统类加载器
应用类加载器(Applocatoin Class Loader): 该类加载器是用于加载用户代码,是用户代码的入口。我经常执行指令 java xxx.x.xxx.x.x.XClass , 实际上,JVM就是使用的AppClassLoader加载 xxx.x.xxx.x.x.XClass 类的。应用类加载器将拓展类加载器当成自己的父类加载器,当其尝试加载类的时候,首先尝试让其父加载器-拓展类加载器加载;如果拓展类加载器加载成功,则直接返回加载结果Class
* d. Customized ClassLoader 用户自定义类加载器
用户自定义类加载器(Customized Class Loader):用户可以自己定义类加载器来加载类。所有的类加载器都要继承java.lang.ClassLoader类。
3类加载过程
1、创建一个引导类加载器实例,初步加载系统类到内存方法区区域中:
JVM申请好内存空间后,JVM会创建一个引导类加载器(Bootstrap Classloader)实例,引导类加载器是使用C++语言实现的,负责加载JVM虚拟机运行时所需的基本系统级别的类,如java.lang.String, java.lang.Object等等。
引导类加载器(Bootstrap Classloader)会读取 {JRE_HOME}/lib 下的jar包和配置,然后将这些系统类加载到方法区内
- 引导类加载器将类信息加载到方法区中,以特定方式组织,对于某一个特定的类而言,在方法区中它应该有 运行时常量池、类型信息、字段信息、方法信息、类加载器的引用,对应class实例的引用等信息。
- 类加载器的引用,由于这些类是由引导类加载器(Bootstrap Classloader)进行加载的,而 引导类加载器是有C++语言实现的,所以是无法访问的,故而该引用为NULL
- 对应class实例的引用, 类加载器在加载类信息放到方法区中后,会创建一个对应的Class 类型的实例放到堆(Heap)中, 作为开发人员访问方法区中类定义的入口和切入点。
当我们在代码中尝试获取系统类如java.lang.Object的类加载器时,你会始终得到NULL:
1 | System.out.println(String.class.getClassLoader());//null |
2、创建JVM 启动器实例 Launcher,并取得类加载器ClassLoader
加载过程
上述步骤完成,JVM基本运行环境就准备就绪了。接着,我们要让JVM工作起来了:运行我们定义的程序 org.luanlouis,jvm.load.Main。
此时,JVM虚拟机调用已经加载在方法区的类sun.misc.Launcher 的静态方法getLauncher(), 获取sun.misc.Launcher 实例:1
2sun.misc.Launcher launcher = sun.misc.Launcher.getLauncher(); //获取Java启动器
ClassLoader classLoader = launcher.getClassLoader(); //获取类加载器ClassLoader用来加载class到内存来
sun.misc.Launcher 使用了单例模式设计,保证一个JVM虚拟机内只有一个sun.misc.Launcher实例。
在Launcher的内部,其定义了两个类加载器(ClassLoader),分别是sun.misc.Launcher.ExtClassLoader和sun.misc.Launcher.AppClassLoader,这两个类加载器分别被称为拓展类加载器(Extension ClassLoader) 和 应用类加载器(Application ClassLoader).如下图所示:
图例注释:除了引导类加载器(Bootstrap Class Loader )的所有类加载器,都有一个能力,就是判断某一个类是否被引导类加载器加载过,如果加载过,可以直接返回对应的Class
当AppClassLoader加载类时,会首先尝试让父加载器ExtClassLoader进行加载,如果父加载器ExtClassLoader加载成功,则AppClassLoader直接返回父加载器ExtClassLoader加载的结果;如果父加载器ExtClassLoader加载失败,AppClassLoader则会判断该类是否是引导的系统类(即是否是通过Bootstrap类加载器加载,这会调用Native方法进行查找);若要加载的类不是系统引导类,那么ClassLoader将会尝试自己加载,加载失败将会抛出“ClassNotFoundException”。
双亲委派模型(parent-delegation model)
上面讨论的应用类加载器AppClassLoader的加载类的模式就是我们常说的双亲委派模型(parent-delegation model).
对于某个特定的类加载器而言,应该为其指定一个父类加载器,当用其进行加载类的时候:
- 委托父类加载器帮忙加载;
- 父类加载器加载不了,则查询引导类加载器有没有加载过该类;
- 如果引导类加载器没有加载过该类,则当前的类加载器应该自己加载该类;
- 若加载成功,返回 对应的Class
对象;若失败,抛出异常“ClassNotFoundException”。
请注意:
双亲委派模型中的”双亲”并不是指它有两个父类加载器的意思,一个类加载器只应该有一个父加载器。上面的步骤中,有两个角色:
- 父类加载器(parent classloader):它可以替子加载器尝试加载类
- 引导类加载器(bootstrap classloader): 子类加载器只能判断某个类是否被引导类加载器加载过,而不能委托它加载某个类;换句话说,就是子类加载器不能接触到引导类加载器,引导类加载器对其他类加载器而言是透明的。
一般情况下,双亲加载模型如下所示:
3使用类加载器ClassLoader加载Main类
加载顺序:
加载java.lang.Object、java.lang.System、java.io.PrintStream、java,lang.Class
AppClassLoader尝试加载这些类的时候,会先委托ExtClassLoader进行加载;而ExtClassLoader发现不是其加载范围,其返回null;AppClassLoader发现父类加载器ExtClassLoader无法加载,则会查询这些类是否已经被BootstrapClassLoader加载过,结果表明这些类已经被BootstrapClassLoader加载过,则无需重复加载,直接返回对应的Class
实例; 加载sun.security.pkcs11.P11Util
此在{JRE_HOME}/lib/ext/sunpkcs11.jar包内,属于ExtClassLoader负责加载的范畴。AppClassLoader尝试加载这些类的时候,会先委托ExtClassLoader进行加载;而ExtClassLoader发现其正好属于加载范围,故ExtClassLoader负责将其加载到内存中。ExtClassLoader在加载sun.security.pkcs11.P11Util时也分析这个类内都使用了哪些类,并将这些类先加载内存后,才开始加载sun.security.pkcs11.P11Util,加载成功后直接返回对应的Class<sun.security.pkcs11.P11Util>实例;
加载org.luanlouis.jvm.load.Main
AppClassLoader尝试加载这些类的时候,会先委托ExtClassLoader进行加载;而ExtClassLoader发现不是其加载范围,其返回null;AppClassLoader发现父类加载器ExtClassLoader无法加载,则会查询这些类是否已经被BootstrapClassLoader加载过。而结果表明BootstrapClassLoader 没有加载过它,这时候AppClassLoader只能自己动手负责将其加载到内存中,然后返回对应的Class<org.luanlouis.jvm.load.Main>实例引用;
以上三步骤都成功,才表示classLoader.loadClass(“org.luanlouis.jvm.load.Main”)完成,上述操作完成后,JVM内存方法区的格局会如下所示:
*A. 反射定义
a. JAVA反射机制是在运行状态中,
对于任意一个类,都能够知道这个类的所有属性和方法;
对于任意一个对象,都能够调用它的任意一个方法和属性;
这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。b.反射技术
条件:处于 运行状态
已知:一个类或一个对象(根本是已知.class文件)
结果:得到这个类或对象的所有方法和属性
功能:
- 在运行时判断任意一个对象所属的类;
- 在运行时构造 任意一个类的对象;
在运行时判断任意一个类所具有的成员变量和方法;
在运行时调用 任意一个对象的方法;
生成动态代理。
- 注: 要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象。
* B. Class类
Class类及Class对象的了解
要想解剖一个类,必须先了解Class对象。
阅读API的Class类得知,Class 没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的。
- . 得到Class对象
- 有三个方法
方式一: 通过Object类中的getClass()方法
Person person = new Person();
Class clazz = person.getClass();方式二: 通过 类名.class 获取到字节码文件对象(任意数据类型都具备一个class静态属性,看上去要比第一种方式简单)。
Class clazz = Person.class;方式三: 通过Class类中的方法(将类名作为字符串传递给Class类中的静态方法forName即可)。
Class c3 = Class.forName(“Person”);
注:第三种和前两种的区别是:
前两种你必须明确Person类型.
后面是指定这种类型的字符串就行.这种扩展更强.我不需要知道你的类.我只提供字符串,按照配置文件加载就可以了
2. 得到Class对象的三个方法代码演示:
1 | 代码演示 |
1 | public class ReflectDemo { |
* C.通过反射获取无参构造方法并使用
1 | 在反射机制中,把类中的成员(构造方法、成员方法、成员变量)都封装成了对应的类进行表示。 |
1 | void test() |
* D. 通过反射获取有参构造方法并使用
1 | * a. 得到有参的构造方法 |
* E. 通过反射获取有参构造方法并使用快捷方式
1 | * a. 使用的前提 |
* F. 通过反射获取私有构造方法并使用
1 | AccessibleObject 类是 Field、Method 和 Constructor 对象的父类。 |
1 | * a. 得到私有的构造方法 |
* G. 反射获取成员变量并改值
1 | * a. 获取成员变量 |
* H. 反射获取空参数成员方法并运行
1 | 在反射机制中,把类中的成员方法使用类Method表示。可通过Class类中提供的方法获取成员方法: |
1 | * a. 获取空参数成员方法 |
* I. 反射获取有参数成员方法并运行
1 | * a. 获取有参数成员方法 |
* J. 反射与泛型擦除
1 | * a. 使用情况 |
* K. 反射通过配置文件来决定运行的步骤
1 | * a. 操作依据 |
总结
1 | 如何获取.Class文件对象 |