码迷,mamicode.com
首页 > 编程语言 > 详细

《深入理解java虚拟机》笔记(8)类的加载机制

时间:2018-03-29 19:09:30      阅读:161      评论:0      收藏:0      [点我收藏+]

标签:nbsp   test   pre   col   xtend   init   基础   分享   字节   

 

一、类加载机制

类加载器将类的.class文件中的二进制数据读入到内存中,将其放在方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。

技术分享图片

 

二、类的生命周期

技术分享图片

类的生命周期包括:加载、验证、准备、解析、初始化、使用、卸载7个阶段。其中加载、验证、准备、初始化、卸载5个阶段是按照这种顺序按部就班的开始,而解析阶段则不一定:某些情况下,可以在初始化之后再开始,这是为了支持Java语言的运行时绑定(也称为动态绑定或晚期绑定)。

注意:这里写的是按部就班的开始,而不是按部就班地进行或完成,因为这些阶段通常都是互相交叉混合式进行的,通常会在一个阶段执行过程中调用、激活另外一个阶段。

1、加载

加载阶段会做3件事情:

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

相对于类加载的其他阶段而言,加载阶段(准确地说,是加载阶段获取类的二进制字节流的动作)是可控性最强的阶段,因为开发人员既可以使用系统提供的类加载器来完成加载,也可以自定义自己的类加载器来完成加载。

此处第一点并没指明要从哪里获取、怎样获取,因此这里给开发人员预留了创造力空间。许多Java技术就建立在此基础上,例如:

  • 从ZIP包读取,如JAR、WAR。
  • 从网络中获取,这种场景最典型应用场景应用就是Applet。
  • 运行时计算生成,使用较多场景是动态代理技术,如spring AOP。

加载阶段完成后,虚拟机外部的二进制字节流就按照虚拟机所需的格式存储在方法区之中,而且在Java堆中也创建一个java.lang.Class类的对象,这样便可以通过该对象访问方法区中的这些数据。

2、验证

确保被加载的类的正确性,分为4个验证阶段:

  • 文件格式验证
  • 元数据验证
  • 字节码验证
  • 符号引用验证

 验证阶段非常重要的,但不是必须的,它对程序运行期没有影响,如果所引用的类经过反复验证,那么可以考虑采用-Xverifynone参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间

3、准备

  • 为类的静态变量分配内存,并初始化默认值,这些内存是在方法区中分配,需要注意以下几点:
  • 此处内存分配的变量仅包含类变量(static),而不包括实例变量,实例变量会随着对象实例化被分配在java堆中。
  • 这里默认值是数据类型的默认值(如0、0L、null、false),而不是代码中被显示的赋予的值。
  • 如果类字段的字段属性表中存在ConstatntValue属性,即同时被final和static修饰,那么在准备阶段变量value就会被初始化为ConstValue属性所指定的值。

4、解析

解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。符号引用就是一组符号来描述目标,可以是任何字面量。

直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。

5、初始化

为类的静态变量赋予正确的初始值,JVM负责对类进行初始化,主要对类变量进行初始化。初始化阶段是执行类构造器<clinit>()方法的过程。

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

         技术分享图片

  • <clinit>()方法与类构造函数不一样,不需要显示调用父类构造函数,虚拟机会保证在子类的<clinit>()方法执行之前,父类的<clinit>()方法已执行完毕。
  • 由于父类的<clinit>()方法首先执行,意味着父类中的静态语句块要优先于子类的变量赋值操作,如下所示,最终得出的值是2,而不是1
public class TestClassLoader {
    public static int A = 1;
    static {
        A = 2;
//        System.out.println(A);
    }
    
    static class Sub extends TestClassLoader {
        public static int B = A;
    }
    
    public static void main(String[] args) {
        System.out.println(Sub.B);
    }
}
  • <clinit>()方法对于类和接口来说,并不是必须的,若类没有静态语句块,也没有对变量赋值操作,则不会生成<clinit>()方法。
  • 接口与类不同的是,接口不需要先执行父类的<clinit>()方法,只有父接口定义的变量使用时,父接口才会被初始化。另外接口的实现类也不会先执行接口的<clinit>()方法。
  • 虚拟机保证当多线程去初始化类时,只会有一个线程去执行<clinit>()方法,而其他线程则被阻塞。

<clinit>()方法和<linit>()方法区别

执行时机不同:init方法是对象构造器方法,在new一个对象并调用该对象的constructor方法时才会执行。clinit方法是类构造器方法,是在JVM加载期间的初始化阶段才会调用。

执行目的不同:init是对非静态变量解析初始化,而clinit是对静态变量,静态代码块进行初始化。

 

三、类加载器

虚拟机把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到Java虚拟机外部去实现,以便让应用程序自助机决定如何去获取所需要的类。这个模块称为类加载器。

类加载器最初是为了满足Java Applet需求开发出来的,但却在类层次划分、OSGI、热部署、代码加密等领域大放异彩。

 

 

 

 

《深入理解java虚拟机》笔记(8)类的加载机制

标签:nbsp   test   pre   col   xtend   init   基础   分享   字节   

原文地址:https://www.cnblogs.com/myshare/p/8671116.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!