我们知道,java代码是可以一次编译,到处运行的,其主要是因为运行在java虚拟机上,不同的操作系统,只要安装了java虚拟机,就能运行编译之后的java代码。
将编译之后的class文件解析出来,其实里面就是一堆的16进制字节。下面的图是class文件的结构。
①、通过全限定名获取此类的二进制字节流。
②、将该二进制字节流代表的静态存储结构转化为方法区运行时的数据结构。
③、生成该类的java.lang.Class,作为访问入口。
该阶段主要是验证加载的二进制字节流文件是否符合Class文件的规范。
是否能被当前版本的虚拟机所理解。
①.文件格式的验证
②.元数据的验证
③.字节码验证
④.符号引用验证
这个阶段是正式仅为类变量(static修饰的变量)进行内存分配的过程。而不包括
实例变量,实例变量是在对象实例化的时候随着对象一起分配至java堆中的。
public static int value = 123;
这个变量 value 在准备阶段后的初始值为0,而不是 123。
因为这时候尚未开始执行任何java方法,把123赋值给value是在程序初始化之后进行的。
public static final int value = 123;
添加了final之后就会不一样,添加了final之后,在字段属性表中是属于常量,在准备阶段就会将value赋值为123
解析阶段是虚拟机将常量池内的符号引用转换为直接引用的过程。
类初始化阶段是执行类构造器clinit<>()的过程。
clinit<>() , 类构造器方法,在jvm第一次加载class文件时调用,因为是类级别的,所以只加载一次,是编译器自动收集类中所有类变量(static修饰的变量)和静态语句块(static{}),中的语句合并产生的,编译器收集的顺序,是由程序员在写在源文件中的代码的顺序决定的。
init<>(),实例构造器方法,在实例创建出来的时候调用,包括调用new操作符;调用Class或java.lang.reflect.Constructor对象的newInstance()方法;调用任何现有对象的clone()方法;通过java.io.ObjectInputStream类的getObject()方法反序列化。
对于java虚拟机来说,存在2种不同的类加载器。
一种是启动类加载器(bootStrap ClassLoader),是虚拟机自身的一部分。
另外一种是 其他类加载器,是由java实现的。独立与虚拟机外部,全部都继承与抽象类ClassLoader
双亲委派模型的工作过程:
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是委派给父类加载器去完成加载。只有父类加载器反馈自己无法加载这个请求的时候,子类加载器才会尝试自己去加载。
使用双亲委派模型的好处:
1、保证java核心库的类型安全。
2、Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。