一、类的生命周期
1. 加载
加载时机
Java 虚拟机规范没有严格规定,看具体的 Java 虚拟机实现,可以预先加载。
加载过程(先到缓存找)
- 根据类的全限定名找到类的二进制字节流;
- 将二进制字节流的静态存储结构转化为方法区中的运行时数据结构;
- 在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据的访问入口。
2. 验证- 确保被加载类的二进制字节流的正确性和安全性
- 文件格式验证 - CAFEBABE、主次版本号、常量池中的数据类型是否正确等。
- 元数据验证 - 语义分析,如是否有父类等。
- 字节码验证 - 语义分析,数据流和控制流是否正确等。
- 符号引用验证 - 符号引用是否正确,保证后面解析动作能正常执行。
3. 准备 - 为类的静态字段分配内存并赋初始值
同时被 static 和 final 修饰的字段在编译后会有 ConstantValue 属性,这使得其在准备阶段就会赋值为 ConstantValue 指定的值。
4. 解析 - 将符号引用替换为直接引用
符号引用:就是描述相对位置的符号,可以是任何字面量。
直接引用:地址指针或偏移量。
invokestatic - 用于调用类(静态)方法
invokespecial - 用于调用实例方法,特化于 super 方法调用、private 方法调用与构造器调用
invokevirtual - 用于调用虚方法和 final 修饰的方法
invokeinterface - 用于调用接口方法
Java 中支持多态(包括重载和重写)的只有虚方法,虚方法是指非 static 的、非 final 的并且非 private 的方法,不包括构造器。
参数的数量、类型等信息组成了函数的 signature。在不同语言中,函数的 signature 不仅可以包含参数的数量、类型,也可能包含参数的结构/模式,甚至可能包括返回类型的数量和类型。
使用同一个名字来命名 signature 不同的函数,称为函数重载(function overloading),重载是编译时概念。
方法分派(Method Dispatch):单一分派(single-dispatch)(又叫静态分派)和多分派(multiple-dispatch)(又叫动态分派)。
重载的实现-单一分派
Java 编译器在重载的时候通过参数的类型(静态类型)而不是实际类型来确定使用哪个重载的版本。使用 invokeVirtual 字节码指令。
多态的实现-多分派
方法的第一个参数,也就是隐含参数 this(称为接受者(Reciever))的实际类型会参与到多分派,其他参数可能参与单一分派也可能参与多分派。
静态方法不能被重写,因为不参与多态,但能继承。
super 关键字:使用 super 调用父类方法的话用的是 invokespecial 指令,这个指令是不支持多态的,会找父类的方法执行。
5. 初始化 - 为类的静态变量赋予指定的初始值
初始化时机
虚拟机规范中并没强行约束什么时候开始类加载过程的第一个阶段:加载。但对于初始化阶段虚拟机规范是严格规定了如下几种情况:
- 遇到 new、getstatic、putstatic、invokestatic 这四条字节码指令时(对应 new 一个对象、读取或设置类的静态字段(static final 修饰的字段除外)、调用类的静态方法);
- 反射调用时 - Class.forName();
- 初始化一个类时如果其父类还未初始化先初始化其父类;
- 虚拟机启动时先初始化包含 mian()方法的那个类;
- 还不懂。
6. 使用
7. 卸载
- 正常结束
- 遇到异常或错误结束
- 执行了 System.exit()方法
- 操作系统错误时虚拟机进程终止
三、类加载器
1 启动类加载器(没有父类加载器)
加载 JDK\jre\lib 或-Xbootclasspath 参数指定的路径下的 Java 类。
虚拟机按照文件名识别加载 jar 包,如果文件名不被虚拟机识别,即使将 jar 包放进 lib 目录也没用。
c++ 实现。
2 扩展类加载器(父类加载器为 null)
加载 JDK\jre\ext 或-Djava.ext.dirs 指定的路径下的 Java 类。
3 应用程序类加载器
加载用户类路径(java -classpath 或 -D java.class.path 指定的路径)下所指定的类。
程序中默认的类加载器,因此可以通过 ClassLoader.getSystemClassLoader()方法获得应用类加载器。
4 用户自定义类加载器
继承 ClassLoader 类,重写 findClass()方法。
或继承 URLClassLoader 类。
四、类加载机制
- 全盘负责
- 父类委托
- 缓存机制
Java 虚拟机加载过的类的二进制字节流都会被缓存,下次要加载类的时候优先到缓存看一看有没有,这就是为什么修改类后要重启 Java 虚拟机进程修改才能生效。
五、类加载的方式(3)
1 虚拟机进程启动时加载
2 Class.forName()动态加载
3 ClassLoader.loadClass()动态加载 - 不会执行 static 中的内容
六、双亲委派模型(JDK1.2 引入)
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。
2 优点
① 避免类的重复加载,保证每个类只会被加载一次。
② 安全。案例:网络传过来一个 java.lang.Integer 类(本地加载过了);自定义一个类丢进 java.lang 包(没权限)
3 双亲委派模型的父子关系并不是 Java 中的继承关系,而是通过组合来实现的。
4 实现
ClassLoader(抽象类)
/*
* 先在缓存找class对象,找不到就加载
* 有父类委托给父类
* 没父类委托给启动类加载器
* 都没找到通过自定义的findClass方法去找
*/
loadClass(String) // ClassLoader实现,双亲委派模型的实现
loadClass(String name, boolean resolve) // 是否在生成class对象的同时进行相关解析操作
findClass(String) // 用来被loadClass方法调用,ClassLoader抽象类中没有具体实现findClass,而是直接抛出异常,各子类需要自己去实现
defineClass(byte[] b, int off, int len) // 将字节流转化为为JVM能够识别的Class对象,常与findClass方法一起使用,被findClass方法调用以生成class对象,ClassLoader抽象类已经实现它
resolve(Class<?> c) // 对class对象进行解析
SecureClassLoader(extends ClassLoader)
URLClassLoader(extends SecureClassLoader)
// 存在一个URLClassPath类,根据传进来的目录名或Jar包名创建相应的加载器(有3种)去加载对应路径下的class文件,这3个加载器就是真正加载字节码流的
findClass(String name) // 实现
findResource()
ExtClassLoader(extends URLClassLoader)
AppClassLoader(extends URLClassLoader)
ExtClassLoader 和 AppClassLoader 都是 sun.misc.Launcher 类的静态内部类。Lanunche 初始化的时候先创建 ExtClassLoader 类加载器,然后再创建 AppClassLoader 并把 ExtClassLoader 传递给它作为父类加载器。
5 自定义类加载器
① 继承 ClassLoader 类就必须重写 findClass()方法,因为 ClassLoader 类没有实现它,只是在里面抛了个异常;
② 继承 URLClassLoader
编写自定义类加载器场景:
1 class 文件不在 ClassPath 路径下;
2 class 文件通过网络传输过来,需要解密;
3 热部署功能(通过不同类加载器产生不同 class 对象)
六、JVM 中如何判断两个 class 对象是否为同一个类对象?
1 类的全限定名必须一样;
2 必须由同一个 ClassLoader 对象加载。
因为不同 ClassLoader 对象拥有独立的类名称空间,所以加载的 class 对象也会存在于不同的类名空间中。
但现实不会出现这种情况,因为有缓存,加载之前都会到缓存看一看加载没有,所以要重新加载 class 对象必须绕过缓存查询,或直接调用 findClass()方法
以“类的全限定名 +ClassLoader 实例 ID”来标明 Class 对象。
七、显示加载与隐式加载
1 显示加载
Class.forName()
obj.getClass().getClassLoader().loadClass()
2 隐式加载
JVM 加载
双亲委派模型的破坏者-线程上下文类加载器
参考:
《深入理解 Java 虚拟机》
JVM(1):Java 类的加载机制
深入理解 Java 类加载器(ClassLoader)
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于