从源码到类加载
类编译过程
Person.java -> 词法分析器 -> tokens流 -> 语法分析器 -> 语法树/抽象语法树 -> 语义分析器 -> 注解抽象语法树 -> 字节码生成器 -> Person.class文件 编译过程知道即可,不必深入学习,对这个感兴趣的同学可以自己去了解。
类加载
一、类加载过程
JVM把class文件加载到内存,并对数据进行校验、准备、解析、初始化,最终形成JVM可以直接使用的Java类型的过程。
二、加载
将class字节码文件加载到内存中,并将这些数据转换成方法区中的运行时数据(静态变量、静态代码块、常量池等),在堆中生成一个Class类对象代表这个类(反射原理),作为方法区类数据的访问入口。
三、验证
确保加载的类信息符合JVM规范,没有安全方面的问题。
文件格式验证
元数据验证
字节码验证
符合引用验证
四、准备
正式为类变量(static变量)分配内存并设置类变量初始值的阶段,这些内存都将在方法区中进行分配。注意此时的设置初始值为默认值,具体赋值在初始化阶段完成。
五、解析
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。
解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用限定符7类符号引用进行。
符号引用:是一组符号来描述目标,可以是任何字面量,原因是在编译的时候虚拟机并不知道所引用类的地址,多以就用符号引用来代替,。
直接引用:是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。
六、初始化
初始化阶段是执行类构造器()方法的过程。类构造器()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static块)执行初始化操作。
当初始化一个类的时候,如果发现其父类还没有进行过初始化、则需要先初始化其父类。
虚拟机会保证一个类的()方法在多线程环境中被正确加锁和同步。
程序初始化顺序
父类的静态变量
父类的静态代码块
子类的静态变量
子类的静态代码块
父类的非静态变量
父类的非静态代码块
父类的构造方法
子类的非静态变量
子类的非静态代码块
子类的构造方法
类加载器ClassLoader
一、ClassLoader分类
Bootstrap ClassLoader
它用来加载 Java 的核心库(JAVA_HOME/jre/lib/rt.jar,sun.boot.class.path路径下的内容),是用原生代码(C语言)来实现的,并不继承自 java.lang.ClassLoader。
加载扩展类和应用程序类加载器。并指定他们的父类加载器。
Extensions ClassLoader
用来加载 Java 的扩展库(JAVA_HOME/jre/ext/*.jar,或java.ext.dirs路径下的内容) 。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java类。
由sun.misc.LauncherAppClassLoader实现。
Custom ClassLoader
开发人员可以通过继承 java.lang.ClassLoader类的方式实现自己的类加载器,以满足一些特殊的需求。
二、加载原则
加载的顺序:自顶向下(双亲委派)
双亲委派机制说明
定义:如果一个类加载器在接到加载类的请求时,它首先不会自己尝试去加载这个类,而是把这个请求任务委托给父类加载器去完成,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。
优势:Java类随着加载它的类加载器一起具备了一种带有优先级的层次关系。比如,Java中的Object类,它存放在rt.jar之中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此Object在各种类加载环境中都是同一个类。如果不采用双亲委派模型,那么由各个类加载器自己取加载的话,那么系统中会存在多种不同的Object 类。`
检查加载的顺序:自底向上