虚拟机的类加载机制定义:把描述类的数据从Class
文件(一串二进制的字节流)加载到内存,并对数据进行校验、转换解析和初始化,最终造成被虚拟机直接使用的Java
类型。java
在Java
语言里,类型的加载、链接和初始化过程都是在程序运行期间完成的,Java
里天生能够动态扩展的语言特性就是依赖运行期动态加载和动态链接这个特色实现的。程序员
用户能够经过Java
预约义的和自定义类加载器,让一个本地的应用程序能够在运行时从网络或其余地方加载一个二进制流做为程序代码的一部分。数组
类从被加载到虚拟机内存中开始,到卸载出内存,所通过的生命周期有:安全
其中2-4
统称为链接,上面的过程有几个须要注意的点:bash
Java
语言的运行时绑定。有且仅有下面五种状况必须当即对类进行初始化:网络
new/getstatic/putstatic/invokestatic
这4
条字节码指令时,若是类没有进行过初始化,则须要先触发其初始化,场景:new
关键字实例化对象final
修饰,已在编译期把结果放入常量池的字段除外)//1.new关键字.
LoadInvokeClass loadInvokeClass = new LoadInvokeClass();
//2.访问静态变量
int content = LoadInvokeClass.sContent;
//3.调用静态方法.
LoadInvokeClass.staticMethod();
复制代码
java.lang.reflect
包的方法对类进行反射调用的时候,若是类没有进行过初始化,则须要先触发其初始化。try {
Class<?> mClass = Class.forName("com.example.lizejun.repojavalearn.load.LoadInvokeClass");
} catch (Exception e) { e.printStackTrace(); }
复制代码
//其中LoadInvokeClass是LoadInvokeClassChild的父类.
LoadInvokeClassChild classChild = new LoadInvokeClassChild();
复制代码
main()
方法),虚拟机会先初始化这个主类。JDK 1.7
的动态语言支持时,若是一个java.lang.invoke.MethodHandle
实例最后的解析结果REF_getStatic/REF_putStatic/REF_invokeStatic
的句柄方法,而且这个方法句柄所对应的类没有进行过初始化,则须要先触发其初始化。在2.2
中谈到的都是主动引用,除此以外,全部引用类的方法都称为被动引用,而被动引用不会触发类的初始化:数据结构
Object
为止,可是不会去初始化它所实现的接口,即当咱们初始化ClassChild
的时候,只会先初始化ClassParent
,淡不会初始化ClassInterface
。public interface ClassInterface {}
public class ClassParent implements ClassInterface {
static {
System.out.println("load ClassParent");
}
}
public class ClassChild extends ClassParent {
static {
System.out.println("load ClassChild");
}
}
复制代码
ClassChild
添加一个静态属性,访问这个静态属性不会初始化ClassParent
。public class ClassChild extends ClassParent {
public static int sNumber;
static {
System.out.println("load ClassChild");
}
}
复制代码
sNumber
,那么不会引发ClassChild
的实例化。public class ClassChild extends ClassParent {
public static final int sNumber = 2;
static {
System.out.println("load ClassChild");
}
}
复制代码
ClassChild[] children = new ClassChild[10];
复制代码
在"加载"阶段,虚拟机须要完成如下三件事情:多线程
java.lang.Class
对象,做为方法区这个类的各类数据的访问入口。"验证"阶段的目的是为了确保Class
文件的字节流中包含的信息符合当前虚拟机的要求,而且不会危害自身的安全,大体会完成下面四个阶段的校验动做:函数
"准备"阶段是正式为类变量(被static
修饰,而不是实例变量)分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。ui
static
而且非final
的类变量,将被初始化为数据类型的零值。static
且final
的类变量,在这个阶段就会被初始化为ConstantValue
属性所指定的值。“解析”阶段是虚拟机将常量池的符号引用替换为直接引用的过程,包括:
根据程序员经过程序指定的主观计划去初始化类变量和其它资源,也就是执行类构造器<clinit>()
方法的过程:
<clinit>
方法是由编译器自动收集类中的全部类变量的赋值动做和静态语句块中的语句合并而成,顺序是由语句在源文件中出现的顺序决定的。静态语句块只能访问到定义在它以前的变量,对于定义在它后面的变量只能赋值不能访问。
<clinit>()
方法与类的构造函数不一样,它不须要显示地调用父类构造器,虚拟机会保证在子类的<clinit>()
方法执行前,父类的<clinit>()
方法已经执行完毕,所以在虚拟机中第一个杯知行的<clinit>()
方法的类确定是java.lang.Object
。
父类的静态语句块要优先于子类的变量赋值操做。
若是一个类中没有静态语句块,也没有对类变量的赋值操做,那么编译器能够不为这个类生成<clinit>()
方法。
接口不能接口中仅有变量初始化的赋值操做,但执行接口的<clinit>()
方法不须要先执行父接口的<clinit>()
方法,只有当父接口中定义的变量使用时,父接口才会初始化,另外,接口的实现类在初始化时也同样不会执行接口的<clinit>()
方法。
虚拟机会保证一个类的<clinit>()
方法在多线程环境中被正确地加锁、同步。
类加载器用来“经过一个类的全限定名来获取描述此类的二进制字节流”。
类加载器用于实现类的加载动做,除此以外,任意一个类,都须要由它加载它的类加载器和这个类自己一同确立其在Java
虚拟机中的惟一性。
每个类加载器,都拥有一个独立的类名称空间,比较两个类是否相等,只有在两个类由同一个类加载器加载的前提下才有意义。
相等表明类的Class
对象的equals
方法,isAssignableFrom
方法,isInstance
方法。
绝大部分Java
程序都会用到如下三种系统提供的类加载器:
类加载器之间的层次关系,称为类加载器的双亲委派模型,这个模型要求除了顶层的启动类加载器外,其他的类都应当有本身的父类加载器,通常使用组合来复用父加载器的代码。
双亲委派模型的工做过程:若是一个类加载器收到了类加载的请求,它首先不会去尝试加载这个类,而是把这个请求委派给父类加载器去完成,只有当父类加载器反馈本身没法完成这个加载请求时,子加载器才会尝试本身加载。
在类加载过程完毕后,若是须要进行实例化对象就须要通过一下步骤,按优先加载父类,再到子类的顺序执行:
咱们用一个简单的例子: 其中ClassOther
是一个单独的类:
public class ClassOther {
public int mNumber;
public ClassOther() {
System.out.println("ClassOther Constructor");
}
public void setNumber(int number) {
this.mNumber = number;
}
public int getNumber() {
return mNumber;
}
}
复制代码
ClassChild
则继承于ClassChild
:
public class ClassParent {
{
System.out.println("ClassParent before mClassParentContent");
}
private ClassOther mClassParentContent = new ClassOther(10);
{
System.out.println("ClassParent after mClassParentContent=" + mClassParentContent.mNumber);
}
public ClassParent(int number) {
mClassParentContent.setNumber(number);
System.out.println("ClassParent Constructor, mClassParentContent=" + mClassParentContent.mNumber);
}
}
public class ClassChild extends ClassParent {
{
System.out.println("ClassChild before a");
}
private int mClassChildContent = 1;
{
System.out.println("ClassChild after mClassChildContent=" + mClassChildContent);
}
public ClassChild() {
super(2);
System.out.println("ClassChild Constructor");
}
}
复制代码
当咱们实例化一个ClassChild
对象时,调用的顺序以下: