虚拟机类加载机制,做了这么多年Java

发布时间:2019-10-04  栏目:编程  评论:0 Comments

前言

Java字节代码:byte[]

一、类的生命周期:加载-》连接(验证-》准备-》解析)-》初始化-》使用-》卸载。

Class文件结构已经学习完毕,今天来学习下虚拟机如何加载Class文件。

Java类在JVM的表现形式:Class类的对象;


C语音编译连接后直接就生成了可执行文件,程序执行,并不需要额外操作。但Java不一样,类型的加载和连接都是在程序运行期间完成的。

Java源代码被编译成class字节码 :

二、加载:

这会导致额外的开销,但这也给Java带来了无比的灵活性,比如动态加载技术正是基于此特性完成的。

图片 1

1、通过一个类的全限定名来获取定义此类的二进制字节流。

本文主要主要内容如下:

Java字节代码 –> Class类的对象:

2、将这个字节流所代表的静态存储结构转化为方法区中的运行时数据结构。

  • 类加载时机
  • 类加载过程
  • 类加载器

加载:把Java字节码byte[]转换成JVM中的java.lang.Class类的对象;

3、在内存中生成一个代表这个类的Class对象,作为方法区这个类的各种数据的访问入口。

类加载时机

链接:Java类的链接指的是将Java类的二进制代码合并到JVM的运行状态之中的过程。

 

类从被加载到虚拟机内存开始,到卸载出内存为止,一共会经历
加载、验证、准备、解析、初始化、使用、卸载 7个阶段。其中
验证、准备、解析 这三个解析被称为连接过程。

初始化:主要是执行静态代码块初始化静态域

特殊:

图片 2

Java类的加载

数组类本身不通过类加载器创建,由Java虚拟机直接创建。

一般来说,以上过程会按部就班地开始,但只是开始,因为这些阶段会交叉进行,通常会在一个阶段开始后激活调用另一个过程。

作用  {想学习Java的请留言 有资料分享}

1、如果数据的组件类型不是引用类型,虚拟机会把数组标记为与引导类加载器关联。

以下4种情况下,一定会开始类的加载过程:

把Java字节码转换成JVM中的java.lang.Class类的对象;

2、如果数组的组件类型是引用类型,数组将在加载该组件类型的类加载器的类名称空间上被标识。

  • 遇到new、getstatic、putstatic、invokestatic等字节码指令时,而类没有经过初始化。生成这4个指令的常见情况:使用new关键字实例化对象、读取或设置类的静态字段(使用final关键字修饰除外)、调用类的静态方法

  • 使用java.lang.reflect进行反射调用,而类没有经过初始化。

  • 当初始化一个类时,发现其父类还没有被初始化,则需要先初始化其父类

  • 当虚拟机启动时,用户需要指定一个主类,虚拟机会初始化这个主类。

通过一个类的全限定名获取描述此类的二进制字节流;


回顾第1条,为啥final关键字修饰的静态成员变量会例外呢?回顾Class文件结构常量池知识,如果是final关键字修饰的static变量,它的值使用ConstantValue进行初始化,非final修饰的static变量在
clinit 方法中初始化。

将这个字节流所代表的静态存储结构保存为方法区的运行时数据结构;

三、验证

以上4种场景被虚拟机称为有且只有的会触发类初始化的场景,这4种场景被称为对类的主动引用。除此之外的所有场景都不会触发类的初始化,被称为被动引用。

在java堆中生成一个代表这个类的java.lang.Class对象,作为访问方法区的入口;

确保Class文件的字节流中包含的信息符合当前虚拟机的要求,不会危害虚拟机自身的安全。

public class SuperClass { /* * 被动引用父类静态变量,不会初始化子类 */ static{ System.out.println("super class init"); } public static int value = 123;}public class SubClass extends SuperClass{ static{ System.out.println("sub class init"); }}public class ConstClass { static{ System.out.println("const class init"); } public static final String HELLO = "hello world";}/* * 被动引用父类静态变量,不会初始化子类 */public static void invokeSuperStatic(){ System.out.println(SubClass.value);}/* * 通过数组定义来引用类,不会触发类的初始化 */public static void accessByArray(){ SuperClass[] array = new SuperClass[10];}/* * 访问final static变量,不会初始化类 */public static void accessFinalField(){ System.out.println(ConstClass.HELLO);}

类加载器分类

1、文件格式验证
,基于二进制字节流,通过后,字节流才进入方法区,后面的3个验证阶段基于方法区的存储结构。

如上代码,一共对应了3种被动引用方式。

启动类加载器(Bootstrap
ClassLoader):负责加载 JAVA_HOMElib 目录中的,或通过-Xbootclasspath参数指定路径中的,且被虚拟机认可(按文件名识别,如rt.jar)的类;

2、元数据验证
,对类的元数据进行语义校验(抽象类需要实现父类或接口中要求实现的所有方法,类的父类是否集成了final类)。

  • 引用父类的静态变量,不会初始化子类
  • 通过数组定义来引用类,不会触发类的初始化。此时没有初始化 SuperClass
    类,但初始化了 [Lcom.okunu.jvm.init
    这个类,它由字节码指令newarray触发,且实现了数组的 length
    等方法。数组也是对象,是Java自动生成的对象,所以它并不会去初始化数组中的元素类。
  • static
    final类型的常量,在编译阶段已把此常量存储到了NotInit类的常量池了,对常量
    HELLO
    的引用已经转化对自身常量池的引用了,所以不会初始化ConstClass类了

扩展类加载器(Extension
ClassLoader):负责加载 JAVA_HOMElibext 目录中的,或通过java.ext.dirs系统变量指定路径中的类库;

3、字节码验证,对类的方法体进行校验分析,保证运行时不会危害虚拟机安全。

类加载过程

应用程序类加载器(Application
ClassLoader):负责加载用户路径(classpath)上的类库;

4、符号引用验证,保证解析能正常执行(符号引用中通过字符串描述的全限定名是否能找到对应的类;符号引用中的类、字段、方法的访问性是否可悲当前类访问)。

类加载过程分为 加载、验证、准备、解析、初始化、使用、卸载
7个阶段,本文中主讲前面5个阶段

图片 3


1、加载

双亲委派模型工作过程:

四、准备

加载阶段主要做以下事情:

当一个类加载器收到类加载任务,优先交给其父类加载器去完成,因此最终加载任务都会传递到顶层的启动类加载器,只有当父类加载器无法完成加载任务时,才会尝试执行加载任务。

准备阶段是正式为类变量(static)分配内存并设置类变量初始值(0,null)的阶段,这些变量所使用的内存将在方法区中分配,static
final直接初始化。

  • 通过类的全限定名获取类的二进制文件流
  • 将字节流所代表的静态存储结构转变为方法区的运行时数据结构
  • 在堆中生成此类的 java.lang.Class 对象,作为方法区这些数据的访问入口

双亲委派模型有什么好处?


加载阶段是开发可控性最强的阶段,比如说开发可以使用自定义类加载器去加载某个类,类的来源也可以是jar包、class文件、甚至是网络流。

比如位于rt.jar包中的类java.lang.Object,无论哪个加载器加载这个类,最终都是委托给顶层的启动类加载器进行加载,确保了Object类在各种加载器环境中都是同一个类。

五、解析

留下评论

网站地图xml地图