类的加载、连接、初始化
加载类过程是在程序运行期间完成的,包含三个阶段
加载 连接 初始化
一、加载
查找并加载类的二进制数据 xxx.class,将其读入内存 放在运行时数据区的方法区内,然后创建一个java.lang.Class对象(规范并未说明Class对象位于哪里,HotSpot虚拟机将其放在了方法区中)用来封装类在方法区内的数据结构。
- 加载.class文件的方式
- 从本地系统中直接加载
- 从网络下载.class文件
- 从jar,zip文件中加载.class
- 从专有数据库中提取.class
- 将Java源文件动态编译为.class文件
- 类加载器并需要等到某个类被“首次主动使用”时再加载它。
- JVM规范允许类加载器在预料到某个类将要被使用时就预先加载它,如果在预先加载时遇到了.class文件缺失或者存在错误,类加载器必须在程序首次主动使用时才报告错误(LinkageError错误)
- 类的加载最终的产物 是位于内存中的Class对象,Class对象封装了类在方法区内的数据结构,且向Java程序员提供了访问方法区内数据结构的接口。
- 有两种类型的类加载器
- Java虚拟机自带的加载器
- 根类加载器(Bootstrap)
- 扩展类加载器(Extension)
- 系统(应用)类加载器(System)
- 用户自定义的类加载器
- java.lang.ClassLoader的子类
- 用户可以定制类的加载方式
- Java虚拟机自带的加载器
-
类加载器的双亲委派机制
- 双亲委派模型工作过程是:如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器完成。每个类加载器都是如此,只有当父加载器在自己的搜索范围内找不到指定的类时(即
ClassNotFoundException
),子加载器才会尝试自己去加载。 - Bootstrap ClassLoader / 启动类加载器 :$JAVA_HOME 中jre/lib/rt.jar里所有的class 由c++实现,不是ClassLoader子类
- Extension ClassLoader / 扩展类加载器:负责加载java平台中扩展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目录下的jar包
-
App Classloader / 系统类加载器:负责加载classpath中指定的jar包及目录中的class。自己编写的源代码,即class.path目录下的源代码
- 如果有一个类加载器能够成功加载Test类,那么这个类加载器被称为定义类加载起。所有能成功返回Class对象引用的类加载器(包括定义类加载器)都别成为初始类加载器
- 双亲委派模型工作过程是:如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器完成。每个类加载器都是如此,只有当父加载器在自己的搜索范围内找不到指定的类时(即
- 1
二、连接
连接就是将已经读入到内存的类的二进制数据合并到虚拟机的运行时换家中去。
1.验证:确保被加载类的正确性
- 类文件的结构检查
- 语义检查
- 字节码验证
- 二进制兼容性的验证
- 魔数的检查
2.准备:为类的静态变量分配内存,并将其初始化为默认值(static int i=5,准备阶段会赋值0.再在初始化阶段赋值5)
例如,对于以下Sample类 在准备阶段,为int类型的静态变量 a分配4个字节的内存空间,并且赋予默认值0,
为long类型的静态变量b分配8个字节的内存空间,并且赋予默认值0。
public static Sample{ private static int a = 1; public static long b; static{ b = 2; } ... }
3.解析:把类中的符号引用转换为直接引用
三、初始化
所有的Java虚拟机实现必须在每个类或接口被Java程序 首次主动使用 时才初始化他们
1.首次主动使用
-
创建类的势力
-
访问某个类、接口的静态变量
-
调用类的静态变量
-
反射
-
初始化一个类的子类
-
Java虚拟机启动时被表明为启动类的类
-
JDK1.7开始提供的动态语言支持
除了以上的七种情况,其他使用Java类的方式都别看作是对类的被动使用,都不会的导致类的初始化。
特别的,调用ClassLoader类的loadClass方法加载的一个类,并不是对类的主动使用,不会导致类的初始化。
2.静态变量初始化有两种途径,在声明处初始化,在static块中初始化,块与声明无优先级,只依照源码顺序依次执行
public class MyTest05 { public static void main(String[] args) {System.out.println(Test05.i);} } class Test05{ static {i=4;} static {i=2;} public static int i = 1; }//~out: 1 ================================================== public class MyTest05 { public static void main(String[] args) {System.out.println(Test05.i);} } class Test05{ public static int i = 1; static {i=4;} static {i=2;} }//~out: 2
如果顺序中某一个static字段的生命无赋值 如 public static int a;则保留a当前的值 跳过次行代码。(详见其中第二部分内容 https://www.cnblogs.com/chafanbusi/p/10641184.html)
3.类的初始化步骤
- 假如这个类还没有被加载连接,那就先加载和连接
- 加入类存在直接父类,并且没有被初始化,那就先初始化父类
- 假如类中存在初始化语句 那就先执行初始化语句
当Java虚拟机初始化一个类时,要求其所有父类也被初始化,但这条规则并不适用于接口。
在初始化一个类时,并不会先初始化它所实现的接口
在初始化一个接口时,并不会初始化它的父接口
一个接口只有在程序首次使用期静态变量时(且不能为编译时常量) 才会导致该接口的初始化。
四、类实例化
- 为新的对象分配内存
- 为实例变量赋默认值
- 为实例变量赋正确的初始值
- java编译器为它编译的每一个类都至少生成一个实例初始化方法,在java的class文件中,这个实例初始化方法被称为<init>,针对源码中每一个构造方法编译器都产生一个<init>方法