虚拟机类加载机制

2018-11-17  本文已影响0人  dev晴天

一 概念:

1 类加载机制

虚拟机吧描述类的数据从class文件加载到内存,并对数据进行校验,转换解析和初始化,最终形成可以被虚拟机直接使用的java类型。就是虚拟机的类加载机制。

2 java类加载特点
2.1 运行时类加载的优点缺点

优点:为java应用程序提供了高度的灵活性。
缺点:类加载时稍微增加一些性能开销

ps:java是可以动态扩展的语言就是依靠运行期动态加载,动态链接这两个特点

二 类加载时机

1 类从被加载到虚拟机内存开始,到卸载出内存为止生命周期图如下:


image.png
(按部就班的开始而不是按部就班的进行或者完成的理解:这些阶段通常是相互交叉地混合式进行)
2 什么情况下要开始类的第一阶段-加载

java虚拟机规范并没有强制约束这点可以交给虚拟机的具体实现自由把握

3 初始化

虚拟机规定了有且只有五种情况必须立即对类进行初始化

3.1 初始化触发的五种状况:
image.png

ps:这五种场景中的行为成为对一个类进行主动引用,除此之外所有引用类的方式都不会触发初始化,被称为你被动引用

3.2 主动引用被动引用栗子(参考课本-深入理解jvm第七章类的加载机制)

三 类的加载过程

1加载

加载是类加载的第一个阶段虚拟机需要完成三件事情:


image.png

心得:jvm通过类全限定名(也就是包含包名的类)吧类(.class)文件,加载到方法区,同时也会在内存中生成一个Class 对象来记录这个类的信息也就是通过这个对象你就可以访问这个类的信息。

对类的全限定名来获取此类的二进制字节流的理解:

由于虚拟机规范没有明确的指明这个二进制字节流具体从哪获取,怎样获取,所以体现出了虚拟机的具体实现与具体应用的灵活度都是相当大的。也体现出了虚拟机设计团队的追求开放性,广阔平台性。

一些建立在这些基础上的java技术:


image.png
1.1 非数组类的加载:

既可以使用系统提供的引导类加载完成。也可以用户自定义类加载器完成,开发人员可以通过自定义类加载器去控制字节流的获取方式

收获: 控制字节流的获取方式,可以通过自定义类加载器控制。

1.2 数组类的加载

数组类本身不通过类加载器创建,它是由jvm直接创建。但是数组类与类加载器还是有密切的关系的。
数组类的元素类型最终要靠类加载器去创建,一个数组类的创建要遵循特定的规则。

PS:

加载阶段完成后,虚拟机外部的二进制字节流就按照虚拟机所需要的格式,存储在方法区之中,然后再内存中实例化一个java.lang.Class 类的对象,这个对象将作为程序访问方法区中这些数据类型的外接口。
特别注意:Class对象比较特殊,他虽然是对象,但是放在方法区中。

2 验证
2.1验证的作用:

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

2.2 验证的四个阶段(如下图)
3 准备

准备阶段:
正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配
注意是类变量的分配(类的静态成员)

心得:准备阶段static 变量会赋默认值,不会赋指定值(我们指定的数值)而static final类型会赋指定的值。
4 解析:将符号引用转换成直接引用的过程

解析内容:


image.png
4 初始化:初始化阶段才真正开始执行类中定义的java程序代码(字节码)

在准备阶段,变量已经赋过一次系统要求的初始值,而在初始化阶段,则根据程序猿通过程序指定的主观计划去初始化类变量和其他资源,或者可以从另外一个角度来表达:初始化阶段是执行类构造器<clinit>()方法的过程。

类的构造器:<clinit>()方法

实例构造器<init>() 方法

<clinit>()方法:

4.1<clinit>方法是有编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{}块)中的语句合并产生的,编译器手机的顺序是由语句在源文件中出现的顺序决定的,静态语句块中只能访问到定义在静态语句块之前的变量,定义在他之后的变量,在前面的静态语句块可以赋值,但不能访问:

书本例子:
public class Test
{
    static
    {
        i = 0;//给变量赋值可以正常通过
        System.out.println(i);//这句话会编译提示”非法向前访问“
    }
}

4.2 <clinit>方法与类的构造器(或者说实例构造器<init>()方法)不同,它不需要显示地调用父类构造器,虚拟机会保证在子类的<clinit>()方法执行前,父类的<clinit>()方法已经执行完毕,因此在虚拟机中的一个被执行的<clinit>()方法的类肯定是java.lang.Object。

4.3 由于父类的<clinit>()方法先执行,也就意味着父类中定义的静态语句块要优先于子类的变量的操作

4.4 <clinit>()方法对于类或接口来说并不是必需的,如果一个类中没有静态语句块,也没有对变量的赋值操作,那么编译器可以不为这个类生成<clinit>()方法

4.5 接口中不能使用静态语句块,但仍然有变量初始化的赋值操作,因此接口与类一样都会生成<clinit>()方法,但接口与类不同的是,执行接口的<clinit>()方法不需要先执行父接口的<clinit>()方法。只有当父接口中定义的变量使用时,父接口才会初始化。另外,接口的实现类在初始化时也一样不会执行接口的<clinit>()方法。

4.6 虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确的加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clinit>()方法,其他线程都需要阻塞等待,知道活动线程执行<clinit>()方法完毕。如果在一个类的<clinit>()方法中有耗时很长的操作,就可能会造成多个进程阻塞,在实际应用中这种阻塞往往是很隐蔽的。

ps:以上有参考课本以及这位大大的文章:https://blog.csdn.net/u010805617/article/details/77802739

四 类加载器

待续。。。

上一篇下一篇

猜你喜欢

热点阅读