目录
1 类加载
虚拟机把描述类的数据从 Class 文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的 Java 类型,这个过程就是类加载。
- 类加载指的是将类 Class 文件读入内存,并为之创建一个 java.lang.Class 对象, class 文件被载入到了内存之后,才能被其它 class 所引用
- jvm 启动的时候,并不会一次性加载所有的 class 文件,而是根据需要去动态加载
- java 类加载器是 jre 的一部分,负责动态加载 java 类到 java 虚拟机的内存
- 类的唯一性由类加载器和类共同决定
虚拟机规范并没有指明二进制字节流要从一个Class文件获取,或者说根本没有指明从哪里获取、怎样获取。这种开放使得Java在很多领域得到充分运用,例如:
- 从ZIP包中读取,这很常见,成为JAR,EAR,WAR格式的基础
- 从网络中获取,最典型的应用就是Applet
- 运行时计算生成,最典型的是动态代理技术,在java.lang.reflect.Proxy中,就是用了ProxyGenerator.generateProxyClass来为特定接口生成形式为“*$Proxy”的代理类的二进制字节流
- 有其他文件生成,最典型的JSP应用,由JSP文件生成对应的Class类
……
2 类加载过程
-
加载:类加载过程的一个阶段,通过一个类的完全限定查找此类字节码文件,并利用字节码文件创建一个Class对象
-
验证:目的在于确保Class文件的字节流中包含信息符合当前虚拟机要求,不会危害虚拟机自身安全。主要包括四种验证,文件格式验证,元数据验证,字节码验证,符号引用验证。
-
准备:为类变量(即static修饰的字段变量)分配内存并且设置该类变量的初始值即0(如static int i=5;这里只将i初始化为0,至于5的值将在初始化时赋值),这里不包含用final修饰的static,因为final在编译的时候就会分配了,注意这里不会为实例变量分配初始化,类变量会分配在方法区中,而实例变量是会随着对象一起分配到Java堆中。
-
解析:主要将常量池中的符号引用替换为直接引用的过程。符号引用就是一组符号来描述目标,可以是任何字面量,而直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。有类或接口的解析,字段解析,类方法解析,接口方法解析(这里涉及到字节码变量的引用,如需更详细了解,可参考周志明老师的《深入了解Java虚拟机》)。
-
初始化:类加载最后阶段,若该类具有超类,则先对其超类进行初始化,执行静态初始化器和静态初始化成员变量(如前面只初始化了默认值的static变量将会在这个阶段赋值,成员变量也将被初始化)。
2.1 类的初始化与主动使用和被动使用
Java程序对类的使用方式分为:主动使用和被动使用。 主动使用:
- 创建类的实例
- 访问某个类或接口的静态变量,或者对该静态变量赋值
- 调用类的静态方法
- 反射(比如:Class.forName(“com.test.HelloWorld”))
- 初始化一个类的子类
- Java虚拟机启动时被标明为启动类的类
- JDK7开始提供的动态语言支持:
- java.lang.invoke.MethodHandle实例的解析结果REF getStatic、REF putStatic、REF invokeStatic句柄对应的类没有初始化,则初始化
除了以上情况,其他使用Java类的方式都被看作是对类的被动使用,都不会导致类的初始化。
new一个对象过程中发生了什么?
- 确认类元信息是否存在。当 JVM 接收到 new 指令时,首先在 metaspace 内检查需要创建的类元信息是否存在。 若不存在,那么在双亲委派模式下,使用当前类加载器以 ClassLoader + 包名+类名为 Key 进行查找对应的 class 文件。 如果没有找到文件,则抛出 ClassNotFoundException 异常 , 如果找到,则进行类加载(加载 - 验证 - 准备 - 解析 - 初始化),并生成对应的 Class 类对象。
- 分配对象内存。 首先计算对象占用空间大小,如果实例成员变量是引用变量,仅分配引用变量空间即可,即 4 个字节大小,接着在堆中划分—块内存给新对象。 在分配内存空间时,需要进行同步操作,比如采用 CAS (Compare And Swap) 失败重试、 区域加锁等方式保证分配操作的原子性。
设定默认值。 成员变量值都需要设定为默认值, 即各种不同形式的零值。 - 设置对象头。设置新对象的哈希码、 GC 信息、锁信息、对象所属的类元信息等。这个过程的具体设置方式取决于 JVM 实现。
- 执行 init 方法。 初始化成员变量,执行实例化代码块,调用类的构造方法,并把堆内对象的首地址赋值给引用变量。
3 类加载器
在类加载的第一阶段“加载”过程中,需要通过一个类的全限定名来获取定义此类的二进制字节流,完成这个动作的代码块就是类加载器。
将 class 文件二进制数据放入方法区内,然后在堆内(heap)创建一个 java.lang.Class 对象,Class 对象封装了类在方法区内的数据结构,并且向开发者提供了访问方法区内的数据结构的接口。
4 JVM预定义的三种类加载器
4.1 启动类加载器(引导类加载器,Bootstrap ClassLoader)
- 使用C++语言实现的,嵌套在JVM内部,无法直接通过代码获取。
- 不继承自java.lang.ClassLoader,没有父加载器。
- 加载扩展类和应用程序类加载器,是他们的父类加载器。
- 它用来加载Java的核心类库。
- 出于安全考虑,Bootstrap启动类加载器只加载包名为java、javax、sun等开头的类。
4.2 扩展类加载器(Extension ClassLoader)
- Java语言编写,由sun.misc.Launcher$ExtClassLoader实现。
- 派生于ClassLoader类,父类加载器为启动类加载器
- 从java.ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录的jre/lib/ext子目录(扩展目录)下加载类库。如果用户创建的jar放在此目录下,也会自动由扩展类加载器加载。
4.3 应用程序类加载器(系统类加载器,AppClassLoader)
- java语言编写,由sun.misc.Launcher$AppClassLoader实现
- 派生于ClassLoader类,父类加载器为扩展类加载器
- 它负责加载环境变量classpath或系统属性java.class.path指定路径下的类库
- 该类加载是程序中默认的类加载器,一般来说,Java应用的类都是由它来完成加载
- 通过
ClassLoader.getSystemClassLoader()
方法可以获取到该类加载器
4.4 用户自定义类加载器
在Java的日常应用程序开发中,类的加载几乎是由上述3种类加载器相互配合执行的,在必要时,我们还可以自定义类加载器,来定制类的加载方式。
为什么要自定义类加载器?
- 隔离加载类
- 修改类加载的方式
- 扩展加载源
- 防止源码泄漏
- 当class文件不在ClassPath路径下,默认系统类加载器无法找到该class文件,在这种情况下我们需要实现一个自定义的ClassLoader来加载特定路径下的class文件生成class对象。
- 当一个class文件是通过网络传输并且可能会进行相应的加密操作时,需要先对class文件进行相应的解密后再加载到JVM内存中,这种情况下也需要编写自定义的ClassLoader并实现相应的逻辑。
- 当需要实现热部署功能时(一个class文件通过不同的类加载器产生不同class对象从而实现热部署功能),需要实现自定义ClassLoader的逻辑。
自定义类加载器分为两步:
- 继承
java.lang.ClassLoader
- 重写父类的
findClass()
方法
针对第 1 步,为什么要继承 ClassLoader
这个抽象类,而不继承 AppClassLoader
呢?因为它和 ExtClassLoader
都是 Launcher
的静态内部类,其访问权限是缺省的包访问权限。static class AppClassLoader extends URLClassLoader{...}
第 2 步,JDK 的 loadCalss()
方法在所有父类加载器无法加载的时候,会调用本身的 findClass()
方法来进行类加载,因此我们只需重写 findClass()
方法找到类的二进制数据即可。
4.5 类加载器间的关系
这里的四者之间是包含关系,不是上层和下层,也不是继承关系 。
-
启动类加载器(Bootstrap ClassLoader),由C++实现,没有父类加载器。
-
拓展类加载器(ExtClassLoader),由Java语言实现,父类加载器为Bootstrap ClassLoader
-
系统类加载器(AppClassLoader),由Java语言实现,父类加载器为ExtClassLoader
-
自定义类加载器,父类加载器为AppClassLoader。
4.6 类的唯一性
对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性。
也就是说,在JVM中表示两个class对象是否为同一个类存在两个必要条件:
- 类的全限定名称一样
- 加载这个类的类加载器必须相同。
即使两个类来源于同一个 Class 文件,被同一个虚拟机加载,只要加载它们的类加载器不同,那这两个类也不相等。
这里所指的“相等”,包括代表类的 Class 对象的 equals()
方法、 isAssignableFrom()
方法、isInstance()
方法的返回结果,也包括使用 instanceof
关键字做对象所属关系判定等情况。
4.7 Launcher类 介绍
sun.misc.Launcher类是java的入口,在启动java应用的时候会首先创建Launcher类,创建Launcher类的时候,会创建应用程序运行中所需要的类加载器。
Launcher的构造器的源码
public