【java虚拟机】java虚拟机的类加载机制

  这篇博文主要来总结一下java虚拟机加载一个类的过程,部分参考自《深入理解Java虚拟机》。为了避免枯燥的解说,为了让读者在读完本文后能彻底理解类加载的过程,首先来看一段java代码,我们从一个例子入手:

//ClassLoaderProcess.java文件
class Singleton {
    private static Singleton singleton = new Singleton();
    public static int count_1;
    public static int count_2 = 0;

    static {
        count_1++;
        count_2++;
    }

    private Singleton() {
        count_1++;
        count_2++;
    }

    public static Singleton getInstance() {
        return singleton;
    }
}

public class ClassLoaderProcess {   

    public static void main(String[] args) {
        System.out.println(Singleton.count_1);
        System.out.println(Singleton.count_2);
    }
}

  Singleton是个单例模式的类,里面有两个静态变量,在静态代码块中对两个静态变量做自增运算,在私有构造方法中,再对这两个静态变量做自增运算,最后打印出来的结果是多少呢?先说一下正确答案不是2和2,而是2和1。我们带着这个问题去分析虚拟机是如何加载一个类的(如果对虚拟机加载类的过程已经很清楚了,就可以不用往下看了~)。看完本文,相信你也会从虚拟机加载类的过程中来分析这段java代码了。

2. 虚拟机加载类的过程

2.1 类的生命周期

  上图(我已尽力画的不那么丑了>_<)表示一个类的生命周期图。类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载、连接(验证、准备、解析)、初始化、使用和卸载7个阶段。其中加载、验证、准备、初始化和卸载这5个阶段的顺序是确定的,类的加载过程必须按照这种顺序按部就班的开始,而解析阶段则不一定:它再某些情况下可以在初始化阶段之后再开始,这是为了支持java语言的运行时绑定。下面来逐个分析一下类加载的各个过程。

2.2 加载

  我们知道,程序要加载到内存中才可以执行,什么情况下需要开始类加载过程的第一阶段:加载呢?java虚拟机规范中并没有进行强制约定,这点可以交给虚拟机的具体实现来自由把握。

  在加载阶段,java虚拟机需要完成以下3件事情:

  1. 通过一个类的全限定名来获取定义此类的二进制字节流
  2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
  3. 在内存中(堆中)生成一个代表这个类的java.lang.Class对象,用来封装类在方法区里的数据结构,作为方法区中这个类的各种数据的访问入口

  从这三个步骤中可以很明显的看出,我们可以通过这个Class来获取类的各种数据,它就像是一面镜子,可以反射出类的信息来,所以也就明白了在用反射的时候为什么要使用Class了。

2.3 验证

  验证是连接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。

  一般我们都是通过java文件编译生成的class文件,这是没有什么问题的,但是class文件并不一定要求用java源码编译而来,可以使用很多其它的途径,比如用十六进制编辑器直接编写来产生class文件。虚拟机如果不检查输入的字节流,对其完全信任的话,可能就会因为载入了有害的字节流而导致系统崩溃,所以验证是虚拟机的一项重要的工作。

2.4 准备

  接下来就是连接的第二步:准备了。准备阶段是正式为类变量分配内存并设置类变量的初始值的阶段。这里有两个概念要搞清楚:

  1. 类变量:即被static修饰的静态变量。
  2. 初始值:指的是该数据类型所对应的“零值”

  所以也就是说,准备阶段是为静态变量分配内存,并且对其初始化为零值。不包括静态代码块和实例变量,静态代码块在下面的初始化阶段执行,实例变量将会在对象实例化的时候随着对象一起分到到java堆中的。例如:

public static int value = 123;

  在准备阶段,value的值为0,并非123!当然咯,如果是boolean型数据,则为false。零值是针对具体类型来说的。

2.5 解析

  解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,这个符号引用和直接引用有什么关联呢?

  1. 符号引用:以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。符号引用与虚拟机实现的内存布局是无关的,引用的目标不一定已经加载到内存中。
  2. 直接引用:指的是直接指向目标的指针、相对偏移量或者是一个能间接定位到目标的句柄。直接引用是和虚拟机实现的内存布局相关的,引用的目标必定已经在内存中存在。

2.6 初始化

  初始化是类加载过程的最后一步,在前面的过程中,除了第一步加载阶段用户可以通过指定自定义类加载器参与外,其余过程完全是由虚拟机自己主导控制的。到了初始化阶段,才真正开始执行类中定义的java程序代码了(或者说是字节码)。

  由上面分析可知,在准备阶段,静态变量已经赋过一次值了,只不过是系统要求的初始值而已,而在初始化阶段,为类的静态变量赋予程序中指定的初始值,还有执行静态代码块中的程序

关于类的初始化这个阶段,可以再分析的深入一点,刚刚说初始化的阶段是为类的静态变量赋实际值的阶段,我们也可以从另外的一个角度去表达:初始化阶段是执行类构造器方法(注意:不是我们平时说的类的构造方法)的过程,构造器方法是<cinit>()方法,它是由编译器自动收集类中所有的静态变量的赋值动作和静态代码块中的语句合并产生的,所以也就清楚了,为啥初始化阶段也可以叫做类构造器方法执行的过程。

  这里需要注意的是,编译器收集的顺序是由语句在程序中出现的顺序所决定的,静态代码块中只能访问到定义在静态代码块之前的变量,定义在它之后的变量,在前面的静态代码块中可以赋值,但是不能访问。可以举个例子:

public class Test {
    static {
        i = 0; //给变量赋值可以正常通过编译
        System.out.print(i); //但是不能访问,这句编译会提示非法向前引用
    }
    static int i = 1;

  <cinit>()方法与类的构造函数不同,它不需要显示的调用父类的构造器,虚拟机会保证在子类的<cinit>()方法执行前,父类的<cinit>()方法已经执行完毕,所以在虚拟机中第一个被执行的<cinit>()方法的类肯定是java.lang.Object。

  由于父类的<cinit>()方法先执行,也就意味着父类中定义的静态代码块要优先于子类的静态变量赋值操作,看一个例子:

//演示虚拟机<cinit>方法执行的过程
public class CinitMethod {

    static class Parent{
        public static int A = 1;
        static {
            A = 2;
        }
    }

    static class Sub extends Parent {
        public static int B = A;
    }

    public static void main(String[] args) {
        System.out.println(Sub.B);
    }
}

  这段程序中,在准备阶段,先将A赋为0,B赋为0,在初始化阶段,先执行父类的<cinit>()方法,所以会执行A=1;然后A=2,然后执行子类的<cinit>()方法,执行B=A,所以打印出来是2。

  虚拟机会保证一个类的<cinit>()方法在多线程环境中被正确的加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<cinit>()方法,其它线程都需要阻塞等待,直到活动线程执行完该方法。

  到这里,一个类的加载过程就算完毕了,类加载的最终产品是位于堆中的Class对象,封装了类在方法区内的数据结构,并向java程序员提供了访问方法区内数据结构的接口。所以程序员就可以使用可以使用这个类去获取与该类相关的信息了。

  要注意的是,这是类加载完毕了,跟类的对象是没有关系的,到目前只能使用类的静态变量和静态方法,类的对象需要我们去产生的,有了对象才能操作其中的普通成员变量和方法。

  现在再去看文章开头的那段java代码应该很简单了,

  1. 在准备阶段,java虚拟机将Singleton赋为空,count_1和count_2赋为0(count_2赋为0不是程序中赋的0,是int的默认值)。
  2. 在初始化阶段,java虚拟机按照顺序执行static代码,

    首先实例化Singleton,执行构造方法中的代码,count_1和cout_2变成1;

    然后按顺序执行static代码,count_1没有赋值,还是1,count_2被赋值为0;

    最后执行静态代码块中的代码,count_1和count_2各自增1,所以count_1=2,count_2=1。

分析完毕。



—–乐于分享,共同进步!

—–我的博客主页:http://blog.csdn.net/eson_15

时间: 2024-10-01 05:20:22

【java虚拟机】java虚拟机的类加载机制的相关文章

好程序员Java学习路线分享JVM类加载机制

好程序员Java学习路线分享JVM类加载机制,JVM相关概念 jdk<br>jdk(Java Development Kit)Java开发包,是Java开发人员用于编译和调试程序的一套程序的集合. jre<br>jre(Java Runtime Evironment)Java运行时环境,是运行Java程序的平台,所有的Java程序必须在这个平台中才能执行. jvm<br>jvm(Java Virtual Machine)Java虚拟机,是用代码虚拟出来的计算机,模拟执行

java虚拟机(4)--类加载机制

类加载机制 类是在运行期间第一次使用时动态加载的,而不是编译时期一次性加载.因为如果在编译时期一次性加载,那么会占用很多的内存. 1.1 类的生命周期 包括以下 7 个阶段: 加载(Loading) 验证(Verification) 准备(Preparation) 解析(Resolution) 初始化(Initialization) 使用(Using) 卸载(Unloading) 1.2 类加载过程 包含加载,验证,准备,解析,初始化这5个阶段 1.2.1 加载 加载是类加载的一个阶段,注意不要

深入理解Java虚拟机(类文件结构+类加载机制+字节码执行引擎)

周志明的<深入理解Java虚拟机>很好很强大,阅读起来颇有点费劲,尤其是当你跟随作者的思路一直探究下去,开始会让你弄不清方向,难免有些你说的啥子的感觉.但知识不得不学,于是天天看,反复看,就慢慢的理解了.我其实不想说这种硬磨的方法有多好,我甚至不推荐,我建议大家阅读这本书时,由浅入深,有舍有得,先从宏观去理解去阅读,再慢慢深入,有条不紊的看下去.具体来说,当你看书的某一部分时,先看这部分的章节名,了解这部分这一章在讲什么,然后再看某一章,我拿"类文件结构"这一章来说,我必须

转深入Java虚拟机 之四:类加载机制

转载请注明出处:http://blog.csdn.net/ns_code/article/details/17881581 类加载过程     类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载.验证.准备.解析.初始化.使用和卸载七个阶段.它们开始的顺序如下图所示: 其中类加载的过程包括了加载.验证.准备.解析.初始化五个阶段.在这五个阶段中,加载.验证.准备和初始化这四个阶段发生的顺序是确定的,而解析阶段则不一定,它在某些情况下可以在初始化阶段之后开始,这是为了支持J

Java编程的逻辑 (87) - 类加载机制

上节,我们探讨了动态代理,在前几节中,我们多次提到了类加载器ClassLoader,本节就来详细讨论Java中的类加载机制与ClassLoader. 类加载器ClassLoader就是加载其他类的类,它负责将字节码文件加载到内存,创建Class对象.与之前介绍的反射.注解.和动态代理一样,在大部分的应用编程中,我们不太需要自己实现ClassLoader. 不过,理解类加载的机制和过程,有助于我们更好的理解之前介绍的内容,更好的理解Java.在反射一节,我们介绍过Class的静态方法Class.f

深入理解java虚拟机(4)---类加载机制

类加载的过程包括: 加载class到内存,数据校验,转换和解析,初始化,使用using和卸载unloading过程. 除了解析阶段,其他过程的顺序是固定的.解析可以放在初始化之后,目的就是为了支持动态加载. 从java开发者来讲,我们并不关心具体细节,只要知道整个流程以及每个流程大体干了那些事情. 每个流程具体对开发代码会有那些影响就可以了. 一:类的加载流程 1.加载loading 在加载过程中,虚拟机需要完成3件事情: 1)通过一个类的全限定名来获得此类的二进制字节流. 2)将这个直接流的静

Java虚拟机 - 类加载机制

[深入Java虚拟机]之四:类加载机制 类加载过程     类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载.验证.准备.解析.初始化.使用和卸载七个阶段.它们开始的顺序如下图所示: 其中类加载的过程包括了加载.验证.准备.解析.初始化五个阶段.在这五个阶段中,加载.验证.准备和初始化这四个阶段发生的顺序是确定的,而解析阶段则不一定,它在某些情况下可以在初始化阶段之后开始,这是为了支持Java语言的运行时绑定(也成为动态绑定或晚期绑定).另外注意这里的几个阶段是按顺序开

深入JAVA虚拟机之类加载机制

前言: 前面学习了类Class文件格式和里面具体的内容,也已经学习了运行时数据区的各部分区域的内容.接下来就是学习JVM是如何把Class文件中记录的信息加载到运行时内存中的,以及class文件中各个部分的信息分别存放在运行时数据区的什么地方.从这篇文字中我们能获得什么? 1.虚拟机是如何加载Class文件的 2.Class文件信息进入JVM后有那些变化 3.进一步理解运行时数据区.Class文件信息.以及类加载过程中都做了那些操作. java语言特性的根基 类加载机制 虚拟机把描述类的数据从C

第七章 虚拟机类加载机制

JVM把描述类的数据从class文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成JVM可以直接使用的Java类型的过程就是类加载机制. 1. 类加载的时机 类从被加载到虚拟机内存中开始,到卸载出内存为止,它的生命周期包括了:加载(Loading).验证(Verification).准备(Preparation).解析(Resolution).初始化(Initialization).使用(Using).卸载(Unloading)七个阶段,其中验证.准备.解析三个部分统称连接.顺序如下:

Java虚拟机类加载机制

原文出处: 朱小厮 看到这个题目,很多人会觉得我写我的java代码,至于类,JVM爱怎么加载就怎么加载,博主有很长一段时间也是这么认为的.随着编程经验的日积月累,越来越感觉到了解虚拟机相关要领的重要性.闲话不多说,老规矩,先来一段代码吊吊胃口. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 public cla