java类是如何加载的
首先要知道类的加载的过程包括了加载、验证、准备、解析、初始化五个阶段。
虚拟机把Class文件加载到内存,然后进行校验,解析和初始化,最终形成java类型,这就是虚拟机的类加载机制。加载,验证,准备,初始化这5个阶段的顺序是确定的,类的加载过程,必须按照这种顺序开始。这些阶段通常是相互交叉和混合进行的。解析阶段在某些情况下,可以在初始化阶段之后再开始---为了支持java语言的运行时绑定。
Java虚拟机规范中,没有强制约束什么时候要开始加载,但是,却严格规定了几种情况必须进行初始化(加载,验证,准备则需要在初始化之前开始):
- 1)遇到 new、getstatic、putstatic、或者invokestatic 这4条字节码指令,如果没有类没有进行过初始化,则触发初始化
- 2)使用java.lang.reflect包的方法,对垒进行反射调用的时候,如果没有初始化,则先触发初始化
- 3)初始化一个类时候,如果发现父类没有初始化,则先触发父类的初始化
通过类的全限定名来获取定义此类的二进制字节流,将此二进制字节流所代表的静态存储结构转化成方法区的运行时数据结构,在内存中生成代表此类的java.lang.Class对象,作为该类访问入口.完成加载
连接阶段第一步.验证的目的是确保Class文件的字节流中信息符合虚拟机的要求,不会危害虚拟机安全,使得虚拟机免受恶意代码的攻击.大致完成以下四个校验动作:
文件格式验证,源数据验证,字节码验证,符号引用验证
在java虚拟机加载class文件并且验证完毕之后,就会正式给类变量分配内存并设置类变量的初始值。这些变量所使用的内存都将在方法区分配。注意这里说的是类变量,也就是static修饰符修饰的变量,在此时已经开始做内存分配,同时也设置了初始值。比如在 Public static int value = 123 这句话中,在执行准备阶段的时候,会给value分配内存并设置初始值0, 而不是我们想象中的123. 那么什么时候 才会将我们写的123 赋值给 value呢?就在下下面要讲的初始化阶段。
将常量池中的符号引用转为直接引用(得到类或者字段、方法在内存中的指针或者偏移量,以便直接调用该方法),这个可以在初始化之后再执行。可以认为是一些静态绑定的会被解析,动态绑定则只会在运行是进行解析;静态绑定包括一些final方法(不可以重写),static方法(只会属于当前类),构造器(不会被重写)
类初始化阶段是类加载过程的最后阶段。在这个阶段,java虚拟机才真正开始执行类定义中的java程序代码。Java虚拟机是怎么完成初始化的呢?这要从编译开始讲起。在编译的时候,编译器会自动收集类中的所有静态变量(类变量)和静态语句块(static{}块)中的语句合并产生的,编译器收集的顺序是根据语句在java代码中的顺序决定的。收集完成之后,会编译成java类的 static{} 方法,java虚拟机则会保证一个类的static{} 方法在多线程或者单线程环境中正确的执行,并且只执行一次。在执行的过程中,便完成了类变量的初始化。值得说明的是,如果我们的java类中,没有显式声明static{}块,如果类中有静态变量,编译器会默认给我们生成一个static{}方法。
class A {
static {
System.out.println(B.b+" **");
}
public static B b=B.b;
}
class B extends A{
static {
System.out.println("A");
}
public static B b=new B();
public static void main(String[] args) {
System.out.println(A.b);
}
}
结果输出:
null **
A
null
class A {
static {
System.out.println(B.b+" **");
}
public static B b=B.b;
}
class B extends A{
static {
System.out.println("A");
}
public static B b=new B();
}
public class C{
public static void main(String[] args) {
System.out.println(A.b);
}
}
结果输出:
A
club.test.controller.B@3d4eac69 **
club.test.controller.B@3d4eac69
package jvm;
class A {
static {
System.out.println(B.b+" **");
}
public static B b=B.b;
}
class B extends A{
static {
System.out.println("A");
}
public static B b=new B();
}
public class C{
public static void main(String[] args) {
B b=new B();
System.out.println(A.b);
}
}
结果输出:
null **
A
null