类的生命周期是怎样的? 加载->验证->准备->解析->初始化->使用->卸载 (为了支持 Java 语言的运行时绑定 动态绑定,解析可在初始化后完成。) 什么是类加载? 把 .class 文件加载到内存,对其校验,转换,解析,初始化,最终形成可被虚拟机直接使用的 Java 类型的过程。 类加载的具体过程是怎样的? 加载: 通过类的全限定名获取二进制字节流。 将字节流代表的静态存储结构转化成方法区的运行时数据结构。 在内存中生成对应的 Class 对象,作为方法区这个类的各种数据的访问入口。 连接: 验证:确保 Class 文件字节流中的信息符合当前虚拟机的要求,不会危害虚拟机。 1. 文件格式验证:保证输入的字节流可被正确地解析,进入内存的方法区进行存储。 2. 元数据验证: 2.1 是否有父类 2.2 父类是否继承了 final 类 2.3 不符合规范的方法重载,入参一致,返回值不同等。 3. 字节码验证: 3.1 保证跳转指令不会指向方法体以外的字节码指令上等。 4. 符号引用验证:常量池中各类符号引用进行匹配性校验,确保后续的解析动作正常发生。 准备:为类变量分配内存并设置类变量的初始值(譬如 int 类型的赋值为0),类变量进入方法区。 解析:将常量池中的符号引用转化成直接引用的过程,虚拟机规范中尚未规定解析阶段发生的具体时间,可以在加载时对符号引用解析,也可以在符号引用将被使用到前再解析(invokeDynamic) 1. 类或接口的符号引用解析 2. 字段 3. 类方法 4. 接口方法 5. 方法类型 6. 方法句柄 7. 调用点限定符 初始化:真正开始执行类中定义的 Java 程序(字节码),是执行clinit() 方法的过程。该方法由编译器自动收集类中类变量的赋值动作和静态语句块(static{})—— 真正赋值。 什么是类加载器? 类加载阶段中“通过类的全限定名来获取描述此类的二进制字节流”这个动作放到 JVM 外部实现,以便让应用程序自己决定如何获取所需要的类。实现这个动作的代码模块称为“类加载器”。 介绍一下双亲委派机制 分类:启动类加载器<-扩展类加载器<-应用程序类加载器,使用组合而非继承的关系来复用父加载器的代码。 工作过程:一个类加载器收到了类加载的请求,首先不会自己去尝试加载这个类,而是把请求委派给父类加载器去完成,因此所有的加载请求最终到了顶层的启动类加载器中,只有当父类反馈无法加载(搜索范围里没有所需的类)时,子加载器才会尝试加载。 好处:Java 类随着它的类加载器一起具备了一种带有优先级的层次关系。无论哪个类要加载A类,最终都是交由启动类加载器去加载。如果没有双亲委派机制,用户编写 java.lang.Object 类并放在 ClassPath 中,由各自的类加载器加载,则 Java 类型体系中最基础的行为也就无法保证了。 ClassLoader.loadClass() 解析:检查是否已加载过,若没有则调用父加载器的 loadClass(),若父类为 null 则默认使用启动类加载器加载。若父加载器加载失败,则抛出 ClassNotFoundException异常后再调用自己的 findClass()进行加载。 破坏双亲委派机制【待完善】: 1. ClassLoader 类 在 jdk1.0就存在,而双亲委派机制在 jdk1.2引入,为了兼容前者。 2. 线程上下文类加载器 2.1 JNDI服务借助线程上下文类加载器去加载所需的 SPI 代码,这是父加载器请求子加载器完成类加载的动作。Java 中涉及到 SPI 的加载:JNDI、JDBC 等。 3. 热部署 OSGI