1- 类的加载、连接和初始化
这一部分会介绍java.lang.reflect包下的接口和类,包括Class、Method、Field、Constructor和Array,这些类分别代表类、方法、变量、构造器和数组。Java程序可以使用这些类动态的获取某个对象、某个类的运行时信息,可以动态,的创建Java对象,动态调用Java方法,访问并修改对象的成员变量值。还有该包下的两个接口,Type是Class类实现的接口,ParameterizedType则代表带泛型参数的类型。
也介绍使用Proxy和InvocationHandler来创建JDK动态代理。和JDK动态代理和AOP的内在关系。
JVM和类
当运行某个java程序时,会启动一个java虚拟机进程,不管该Java程序有多么复杂,有多少线程,他们都处于Java虚拟机进程中,同一个JVM的所有线程、所有变量都使用JVM进程的内存区。
当系统出现以下几种情况,JVM进程被终止:
- 程序运行到正常结束
- 程序运行到System.exit()或Runtime.getRuntime().exit()代码
- 程序执行过程中遇到未捕获的异常或错误而结束
- 程序所在平台强制结束了JVM进程
类的加载
当程序使用某个类时,如果该类还未被加载到内存中,系统会通过加载、连接、初始化三个步骤来对该类进行初始化,没有意外的话,JVM会连续完成这三个步骤,所以也统称为类加载或类初始化。
类加载是指将该类的class文件读入内存,并为之创建一个java.lang.Class对象,也就是说当程序中使用任何类时,系统都会为之建立一个java.lang.Class文件。
类的加载由类加载器完成,类加载器通常由JVM提供。JVM提供的类加载器通常称为系统类加载器,除此,开发者可以继承ClassLoader基类来创建自己的类加载器。
通过使用不用的类加载器,可以从不同来源加载类的二进制数据,通常由以下几种来源:
- 从本地文件中加载class文件,这是最常用的
- 从JAR包加载class文件,这种也很常见,导入的jar包就是使用这种方式,例如JDBC编程用到的驱动类jar包
- 通过网络加载class文件
- 把一个Java源文件动态编译、并执行加载
类加载器无需等到 首次使用该类 才加载,Java虚拟机允许系统预先加载某些类。
类的连接
当类被加载后,系统为之生成一个对应的Class对象,然后进入连接阶段,连接阶段负责把类的二进制数据合并到JRE中,连接又可分为三个阶段:
- 验证:用于检验被加载的类是否有正确的内部结构,并和其他类协调一致
- 准备:负责为类的类变量分配内存,并设置默认初始值
- 解析:将类的二进制数据中的符号引用转换为直接饮用
类的初始化
虚拟机对类负责初始化,主要就是对类变量进行初始化,Java类变量指定初始值有两种方式:
- 声明类变量时指定初始值
- 使用静态初始化块指定初始值
JVM初始化一个类包括几个步骤:
- 假如这个类还没有被加载和连接,程序先加载并连接该类
- 假如该类的直接父类还没有被初始化,则先初始化该直接父类
- 假如类中有初始化语句,系统则依次执行这些初始化语句
注意:当执行第二个步骤时,系统对直接父类的初始化也遵循这三个步骤,如果该直接父类的父类也没有被初始化,那么系统会先初始化父类的直接父类,以此类推。所以JVM最先初始化的总是Object类。
类初始化的时机
当Java程序首次通过下面6种方式来使用某个类或接口时,系统会初始化该类或接口。
- 创建类的实例。包括,使用new操作符来创建实例,通过反射来创建实例,通过反序列化创建实例
- 调用某个类的类方法
- 访问某个类的类变量,或为该类变量赋值
- 使用反射强制创建某个类或接口对应的Class对象,例如
Class.forName("Person")
,如果系统未初始化Person类,那么这行代码会导致Person类被初始化,并返回对应的Class对象。 - 初始化某个类的子类
- 直接使用java.exe命令来运行某个主类
**注意:**除此之外。对于final型的类变量,如果该类变量的值在编译时就可确定下来,那么这个类变量相当于宏变量,java编译器会在编译时直接把这个类变量出现的地方替换成它的值,因此即使程序使用该静态变量,也不会导致该类初始化。
如果final类变量在编译时不能确定下来,必须等到运行时才可确定,如果通过该类访问类变量,会导致该类被初始化。
当使用ClassLoader加载该类时,并不会导致该类的初始化,它加载该类。当使用Class.forName()静态方法才会初始化该类。