一、Class文件结构(具体参考 Class文件结构
注:工具
1. 看原始class文件(16进制)用 :winhex
2. 看class文件结构:java原生指令 javap -verbose class文件名
3. 看class文件结构工具:jclasslib
二、类加载时机(以下初始化的意思应该都是加载)
1. 遇到 new、getstatic、putstatic 或 invokestatic 这 4 条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化
1) new:使用 new 关键字实例化对象
2) getstatic:读取一个类的静态字段
3) putstatic:设置一个类的静态字段
4) invokestatic:调用类的静态方法
2. 使用 java.lang.reflect 包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化
3. 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化(emmm,有待商榷,应该是加载父类)
4. 当虚拟机启动时,用户需要指定一个要执行的主类(包含 main() 方法的那个类),虚拟机会先初始化这个主类
5. 当使用 JDK 1.7 的动态语言支持时,如果一个 java.lang.invoke.MethodHandle 实例最后的解析结果 REF_getStatic、REF_putStatic、REF_invokeStatic 的方法句柄时,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化
三、类加载的过程
1. 虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制
2. 类加载的过程:包括加载、链接(含验证、准备、解析)、初始化
1)加载
(1)类加载指的是将类的class文件读入内存,并为之创建一个java.lang.Class对象,作为方法区这个类的数据访问的入口
(2)通过类的全名产生对应类的二进制数据流。(根据early load原理,如果没找到对应的类文件,只有在类实际使用时才会抛出错误)
(3)分析并将这些二进制数据流转换为方法区特定的数据结构
(4)创建对应类的java.lang.Class对象,作为方法区的入口(有了对应的Class对象,并不意味着这个类已经完成了加载链接)
(5)通过使用不同的类加载器,可以从不同来源加载类的二进制数据,通常有如下几种来源
a. 从本地文件系统加载class文件,这是绝大部分程序的加载方式
b. 从jar包中加载class文件,这种方式也很常见,例如jdbc编程时用到的数据库驱动类就是放在jar包中,jvm可以从jar文件中直接加载该class文件
c. 通过网络加载class文件
d. 把一个Java源文件动态编译、并执行加载
2)链接
(1)链接指的是将Java类的二进制文件合并到jvm的运行状态之中的过程。在链接之前,这个类必须被成功加载
(2)类的链接包括验证、准备、解析这三步
a. 验证
I. 验证是用来确保Java类的二进制表示在结构上是否完全正确(如文件格式、语法语义等)。如果验证过程出错的话,会抛出java.lang.VertifyError错误
II. 主要验证以下内容
文件格式验证, 元数据验证:语义验证, 字节码验证
b. 准备
I. 准备过程则是创建Java类中的静态域(static修饰的内容),并将这些域的值设置为默认值,同时在方法区中分配内存空间。准备过程并不会执行代码
II. 只做默认初始化,不是做显式初始化
c. 解析
I. 解析的过程就是确保这些被引用的类能被正确的找到(将符号引用替换为直接引用)。解析的过程可能会导致其它的Java类被加载
3)初始化
(1)初始化阶段是类加载过程的最后一步。到了初始化阶段,才真正执行类中定义的Java程序代码(或者说是字节码)
(2)在以下几种情况中,会执行初始化过程:
a. 创建类的实例(比如new、反射、序列化)
b. 调用一个类或接口的静态字段,或者对这些静态字段执行赋值操作时(即在字节码中,执行getstatic或者putstatic指令),不过用final修饰的静态字段除外,它被初始化为一个编译时常量表达式
c. 调用类的静态方法(即在字节码中执行invokestatic指令)
d. 调用JavaAPI中的反射方法时(比如调用java.lang.Class中的方法,或者java.lang.reflect包中其他类的方法)
e. 初始化类的子类。(Java虚拟机规范明确要求初始化一个类时,它的超类必须提前完成初始化操作,接口例外)
f. java虚拟机启动时被标明为启动类的类(JVM启动包含main方法的启动类时)
(3)类的初始化步骤:
a.如果这个类还没有被加载和链接,那先进行加载和链接
b.假如这个类存在直接父类,并且这个类还没有被初始化(注意:在一个类加载器中,类只能初始化一次),那就初始化直接的父类(不适用于接口)
c.假如类中存在初始化语句(如static变量和static块),那就依次执行这些初始化语句。
四、类加载机制
1. 加载器(以通过一个类的全限定名来获取描述此类的二进制字节流,完成这一动作的代码模块被称为类加载器)
1)Bootstrap ClassLoader
负责加载$JAVA_HOME中jre/lib/rt.jar里所有的class,由C++实现,不是ClassLoader子类
2)Extension ClassLoader
负责加载java平台中扩展功能的一些jar包,包括$JAVA_HOME中jre/lib/ext/*.jar或-Djava.ext.dirs指定目录下的jar包
3)App ClassLoade
负责加载classpath中指定的jar包及目录中class
4)Custom ClassLoader
属于应用程序根据自身需要自定义的ClassLoader,如tomcat、jboss都会根据j2ee规范自行实现ClassLoader,加载过程中会先检查类是否被已加载,检查顺序是自底向上,从Custom ClassLoader到BootStrap ClassLoader逐层检查,只要某个classloader已加载就视为已加载此类,保证此类在所有ClassLoader只加载一次。而加载的顺序是自顶向下,也就是由上层来逐层尝试加载此类
2. 双亲委派机制
1)约束:双亲委派模型要求除了顶层的启动类加载器之外,其余的类加载器都应当有自己的父类加载器。类加载器之间的父子关系一般不以继承关系实现,而是使用组合关系来复用父加载器的代码
2)工作过程:类加载器收到一个类加载的请求,自己不会先加载,而是把该请求委派给父类加载器,每一层的类加载器都是如此,因此最终该请求会被传送到顶层的启动类加载器中,只有当父类加载器无法完成加载请求(对应搜索范围内没有找到所需的类)时,子加载器才尝试自己去加载
3)好处:双亲委派模型可以保证系统中的类在各种类加载器环境中都是同一个类,即使用户自定义一个和系统同名的类,也不能被类加载器加载,保证了 Java 程序的稳定运作,因为无论哪一个类加载器要加载一个类,最终都是委派给最顶端的启动类加载器进行加载
3. 破坏双亲委派机制
1)双亲委派模型是在 JDK 1.2 之后引入的,类加载器是在 JDK 1.0 就已经存在。所以在 JDK 1.2 之前,用户可用继承 java.lang.ClassLoader 去重写 loadClass() 方法
2)当基础类要调用用户的代码时,父类加载器可以请求这类加载器去完成类加载的动作
3)对程序动态性的追求,如:代码热替换、代码热部署等
参考网址
注:文章是经过参考其他的文章然后自己整理出来的,有可能是小部分参考,也有可能是大部分参考,但绝对不是直接转载,觉得侵权了我会删,我只是把这个用于自己的笔记,顺便整理下知识的同时,能帮到一部分人。
ps : 有错误的还望各位大佬指正,小弟不胜感激