JVM类的加载过程分为五个部分:加载,验证,准备,解析,初始化,其中验证,准备,解析三个部分统称为连接。如下图所示
- 通过一个类的全限定名来获取其定义的二进制字节流,将二进制数据读入内存,放入运行时数据区的方法区中。
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
- 在堆区创建一个代表这个类的java.lang.Class对象,用来封装类在方法区内的数据结构,作为对方法区中这些数据的访问入口。
我的理解:加载的最终结果是在堆中创建了一个Class对象,这个对象是独一无二的。
Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。
具体关于Class对象的内容可以查看反射相关内容。
class Car{}
public class Loading {public static void main(String[] args) {Car car1 = new Car();Car car2 = new Car();Car car3 = new Car();Class extends Car> car1Class = car1.getClass();Class extends Car> car2Class = car2.getClass();Class extends Car> car3Class = car3.getClass();System.out.println("car1:"+car1 +" car2:"+car2 +" car3:"+car3);//car1:Car@15aeb7ab car2:Car@7b23ec81 car3:Car@6acbcfc0System.out.println("car1Class:"+car1Class +"; car2Class:"+car2Class +"; car3Class:"+car3Class);//car1Class:class Car; car2Class:class Car; car3Class:class CarSystem.out.println(car1Class==car2Class);//trueSystem.out.println(car1Class.getClass());//class java.lang.Class}
}
我的理解:java中最抽象的类是java.lang.Class,而Car类对象(car1Class)可以看作是java.lang.Class类的一个实例化对象
与此相对应的,我们可以把car1对象看作是Car类的一个实例化对象。堆的唯一目的就是存放对象实例。
加载.class文件的方式
确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。大概分为4个阶段的检验动作:
文件格式验证:验证字节流是否符合Class文件格式的规范;例如:是否以 0xCAFEBABE开头、主次版本号是否在当前虚拟机的处理范围之内、常量池中的常量是否有不被支持的类型。
元数据验证:对字节码描述的信息进行语义分析(注意:对比javac编译阶段的语义分析),以保证其描述的信息符合Java语言规范的要求;例如:这个类是否有父类,除了 java.lang.Object之外。
字节码验证:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。
符号引用验证:确保解析动作能正确执行。
验证阶段是非常重要的,但不是必须的,它对程序运行期没有影响,如果所引用的类经过反复验证,那么可以考虑采用 -Xverifynone参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间。
准备阶段是正式为类的静态变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中分配。
注意:
例如:
public static int a=100;//准备阶段设为0
public static final int b=100;//准备阶段设为100
把类中的符号引用转换为直接引用
在编译时,java类并不知道引用类的实际地址,所以用符号引用来代替。在该阶段转换为实际内存地址。
初始化主要完成静态块执行以及静态变量的赋值.
先初始化父类,再初始化当前类.
只有对类主动使用时才会初始化.
触发初始化的条件有:
JVM初始化步骤
虚拟机设计团队把加载动作放到 JVM 外部实现,以便让应用程序决定如何获取所需的类,JVM 提
供了 3 种类加载器:启动类加载器(Bootstrap ClassLoader)、扩展类加载器(Extension ClassLoader)、应用程序类加载器(Application ClassLoader)。如下图所示:
先看一个例子
class Car{}
public class Loading {public static void main(String[] args) {Car car = new Car();ClassLoader classLoader1 = car.getClass().getClassLoader();System.out.println(classLoader1);System.out.println(classLoader1.getParent());System.out.println(classLoader1.getParent().getParent());}
}
运行结果
jdk.internal.loader.ClassLoaders$AppClassLoader@63947c6b
jdk.internal.loader.ClassLoaders$PlatformClassLoader@4eec7777
null
java9开始,将Bootstrap ClassLoader—>Platform ClassLoader
PlatformClassLoader的父类是null的原因:BootstrapClassLoader是用c语言实现的。
应用程序都是由这三种类加载器互相配合进行加载的,如果有必要,我们还可以加入自定义的类加载器。因为JVM自带的ClassLoader只是懂得从本地文件系统加载标准的java class文件,因此如果编写了自己的ClassLoader,便可以做到如下几点:
类加载有三种方式:
Class.forName()和ClassLoader.loadClass()区别
双亲委派模型的工作流程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。
优点: