Class文件由类装载器装载后,在JVM中将造成一份描述Class结构的元信息对象,经过该元信息对象能够获知Class的结构信息:如构造函数,属性和方法等,Java容许用户借由这个Class相关的元信息对象间接调用Class对象的功能。java
虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验,转换解析和初始化,最终造成能够被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。数据库
类装载器就是寻找类的字节码文件,并构造出类在JVM内部表示的对象组件。在Java中,类装载器把一个类装入JVM中,要通过如下步骤:数组
(1) 装载:查找和导入Class文件;安全
(2) 连接:把类的二进制数据合并到JRE中;网络
(a)校验:检查载入Class文件数据的正确性;数据结构
(b)准备:给类的静态变量分配存储空间;多线程
(c)解析:将符号引用转成直接引用;ide
(3) 初始化:对类的静态变量,静态代码块执行初始化操做
函数
(2) 将这个字节流所表明的静态存储结构转化为方法区的运行时数据结构oop
(3) 在Java堆中生成一个表明这个类的java.lang.Class对象,做为方法区这些数据的访问入口。(不一样虚拟机机制不一样,hotsport把Class对象放在方法区中)
虚拟机规范中并无准确说明二进制字节流应该从哪里获取以及怎样获取,这里能够经过定义本身的类加载器去控制字节流的获取方式,譬如:网络、动态生成、数据库等。
public static int value=123;那变量value在准备阶段事后的初始值为0而不是123.由于这时候还没有开始执行任何java方法,而把value赋值为123的putstatic指令是程序被编译后,存放于类构造器()方法之中,因此把value赋值为123的动做将在初始化阶段才会执行。
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。解析动做主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。
CONSTANT_Class_info
CONSTANT_Field_info
CONSTANT_Method_info
符号引用与虚拟机实现的布局无关,引用的目标并不必定要已经加载到内存中。各类虚拟机实现的内存布局能够各不相同,可是它们能接受的符号引用必须是一致的,由于符号引用的字面量形式明肯定义在Java虚拟机规范的Class文件格式中。
直接引用能够是指向目标的指针,相对偏移量或是一个能间接定位到目标的句柄。若是有了直接引用,那引用的目标一定已经在内存中存在。
public class Test { static { i=0; System.out.println(i);//这句编译器会报错:Cannot reference a field before it is defined(非法向前应用) } static int i=1; }<clinit>()方法与实例构造器<init>()方法不一样,它不须要显示地调用父类构造器,虚拟机会保证在子类<init>()方法执行以前,父类的<clinit>()方法方法已经执行完毕, 因为父类的<clinit>()方法先执行,也就意味着父类中定义的静态语句块要优先于子类的变量赋值操做。以下:
public class Parent { public static int A = 1; static { A = 2; } static class Sub extends Parent { public static int B = A; } public static void main(String[] args) { System.out.println(Sub.B); //2 } }<clinit>()方法对于类或者接口来讲并非必需的,若是一个类中没有静态语句块,也没有对变量的赋值操做,那么编译器能够不为这个类生产<clinit>()方法。
public class DealLoopTest { static class DeadLoopClass { static { if(true) { System.out.println(Thread.currentThread()+"init DeadLoopClass"); while(true) { } } } } public static void main(String[] args) { Runnable script = new Runnable(){ public void run() { System.out.println(Thread.currentThread()+" start"); DeadLoopClass dlc = new DeadLoopClass(); System.out.println(Thread.currentThread()+" run over"); } }; Thread thread1 = new Thread(script); Thread thread2 = new Thread(script); thread1.start(); thread2.start(); } }
Thread[Thread-0,5,main] start Thread[Thread-1,5,main] start Thread[Thread-0,5,main]init DeadLoopClass须要注意的是,其余线程虽然会被阻塞,但若是执行<clinit>()方法的那条线程退出<clinit>()方法后,其余线程唤醒以后不会再次进入<clinit>()方法。同一个类加载器下,一个类型只会初始化一次。
static { System.out.println(Thread.currentThread() + "init DeadLoopClass"); try { TimeUnit.SECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } }
Thread[Thread-0,5,main] start Thread[Thread-1,5,main] start Thread[Thread-1,5,main]init DeadLoopClass (以后sleep 10s) Thread[Thread-1,5,main] run over Thread[Thread-0,5,main] run over虚拟机规范严格规定了有且只有5中状况(jdk1.7)必须对类进行“初始化”(而加载、验证、准备天然须要在此以前开始):
(1) 遇到new、getstatic、putstatic或invokestatic这4条字节码指令时,若是类没有进行过初始化,则须要先触发其初始化。生成这4条指令的最多见的Java代码场景是:使用new关键字实例化对象的时候,读取或设置一个类的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外)的时候,以及调用一个类的静态方法的时候。
(2) 使用java.lang.reflect包的方法对类进行反射调用的时候,若是类没有进行过初始化,则须要先触发其初始化。
(3) 当初始化一个类的时候,若是发现其父类尚未进行过初始化,则须要先触发其父类的初始化。
(4) 当虚拟机启动时,用户须要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。
(5) 当使用jdk1.7动态语言支持时,若是一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getstatic,REF_putstatic,REF_invokeStatic的方法句柄,而且这个方法句柄所对应的类没有进行初始化,则须要先出触发其初始化。
只有上述这五种状况会触发初始化,也称为对一个类进行主动引用,除此之外,全部其余方式都不会触发初始化,称为被动引用.
注意如下几种状况不会执行类初始化: