深入拆解Java虚拟机
郑雨迪
Oracle 高级研究员,计算机博士
立即订阅
28017 人已学习
课程目录
已完结 39 讲
0/4登录后,你可以任选4讲全文学习。
开篇词 (1讲)
开篇词 | 为什么我们要学习Java虚拟机?
免费
模块一:Java虚拟机基本原理 (12讲)
01 | Java代码是怎么运行的?
02 | Java的基本类型
03 | Java虚拟机是如何加载Java类的?
04 | JVM是如何执行方法调用的?(上)
05 | JVM是如何执行方法调用的?(下)
06 | JVM是如何处理异常的?
07 | JVM是如何实现反射的?
08 | JVM是怎么实现invokedynamic的?(上)
09 | JVM是怎么实现invokedynamic的?(下)
10 | Java对象的内存布局
11 | 垃圾回收(上)
12 | 垃圾回收(下)
模块二:高效编译 (12讲)
【工具篇】 常用工具介绍
13 | Java内存模型
14 | Java虚拟机是怎么实现synchronized的?
15 | Java语法糖与Java编译器
16 | 即时编译(上)
17 | 即时编译(下)
18 | 即时编译器的中间表达形式
19 | Java字节码(基础篇)
20 | 方法内联(上)
21 | 方法内联(下)
22 | HotSpot虚拟机的intrinsic
23 | 逃逸分析
模块三:代码优化 (10讲)
24 | 字段访问相关优化
25 | 循环优化
26 | 向量化
27 | 注解处理器
28 | 基准测试框架JMH(上)
29 | 基准测试框架JMH(下)
30 | Java虚拟机的监控及诊断工具(命令行篇)
31 | Java虚拟机的监控及诊断工具(GUI篇)
32 | JNI的运行机制
33 | Java Agent与字节码注入
模块四:黑科技 (3讲)
34 | Graal:用Java编译Java
35 | Truffle:语言实现框架
36 | SubstrateVM:AOT编译框架
尾声 (1讲)
尾声 | 道阻且长,努力加餐
深入拆解Java虚拟机
登录|注册

03 | Java虚拟机是如何加载Java类的?

郑雨迪 2018-07-25
听我的意大利同事说,他们那边有个习俗,就是父亲要帮儿子盖栋房子。
这事要放在以前还挺简单,亲朋好友搭把手,盖个小砖房就可以住人了。现在呢,整个过程要耗费好久的时间。首先你要请建筑师出个方案,然后去市政部门报备、验证,通过后才可以开始盖房子。盖好房子还要装修,之后才能住人。
盖房子这个事,和 Java 虚拟机中的类加载还是挺像的。从 class 文件到内存中的类,按先后顺序需要经过加载、链接以及初始化三大步骤。其中,链接过程中同样需要验证;而内存中的类没有经过初始化,同样不能使用。那么,是否所有的 Java 类都需要经过这几步呢?
我们知道 Java 语言的类型可以分为两大类:基本类型(primitive types)和引用类型(reference types)。在上一篇中,我已经详细介绍过了 Java 的基本类型,它们是由 Java 虚拟机预先定义好的。
至于另一大类引用类型,Java 将其细分为四种:类、接口、数组类和泛型参数。由于泛型参数会在编译过程中被擦除(我会在专栏的第二部分详细介绍),因此 Java 虚拟机实际上只有前三种。在类、接口和数组类中,数组类是由 Java 虚拟机直接生成的,其他两种则有对应的字节流。
说到字节流,最常见的形式要属由 Java 编译器生成的 class 文件。除此之外,我们也可以在程序内部直接生成,或者从网络中获取(例如网页中内嵌的小程序 Java applet)字节流。这些不同形式的字节流,都会被加载到 Java 虚拟机中,成为类或接口。为了叙述方便,下面我就用“类”来统称它们。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《深入拆解Java虚拟机》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(122)

  • 东方
    1. 新建数组会加载元素类LazyHolder;不会初始化元素类

    2. 新建数组不会链接元素类LazyHolder;在getInstance(false)时才真正链接和初始化
    -----------------
    链接的第一步:验证字节码,awk把字节码改为不符合jvm规范
    初始化调用<clinit>(即class init)



    PS:好像二个问题包含了第一个问题的答案

    作者回复: 多谢指出!

    2018-07-25
    61
  • 迈克擂
    学习了!可以的话希望老师能附上一些图解,便于更理解
    2018-07-29
    42
  • mover
    到目前为止,讲解的内容没有超出周志明老师的 深入理解JAVA虚拟机这本书的内容,老师可以讲解的更深入一点吗?可以介绍一下类加载后在meta区的大概布局吗?class类对象与meta区的类数据结构是什么关系?当我们创建类,使用类时,类实例,类对象,meta区类数据结构是如何交互的?

    作者回复: 谢谢你的建议!前几章不好搞太难,希望后面能够满足你的需求

    2018-07-25
    1
    36
  • 慎独
    1.虚拟机必须知道(加载)有这个类,才能创建这个类的数组(容器),但是这个类并没有被使用到(没有达到初始化的条件),所以不会初始化。
    2.新建数组的时候并不是要使用这个类(只是定义了放这个类的容器),所以不会被链接,调用getInstance(false)的时候约等于告诉虚拟机,我要使用这个类了,你把这个类造好(链接),然后把static修饰的字符赋予变量(初始化)。
    老师看看理解对不对,指点一下。

    作者回复: 对的!

    2018-10-18
    29
  • ruyin
    -XX:+TraceClassLoading 可以看到类加载过程
    2018-07-27
    22
  • conce2018
    为什么叫双亲委派呀,明明只给了父类加载应该是单亲呀

    作者回复: 其实我也有这个疑问,英文中为parent不带s,照理应该翻译为单亲。但既然约定俗成翻译为双亲,就只好这样叫啦

    2018-09-25
    13
  • 恩同
    忍着瞌睡把内容看完了。
    全是复习了一遍。
    作者对 类加载中的 链接(验证、准备、解析)讲解不太到位吧?
    另外,对一个的初始化发生在第一次主动使用该类时,作者列出的几种情况都属于主动使用类。感觉应该有被动使用的举例,并告知大家这样做是不会执行初始化的。

    作者回复: 多谢建议!

    2018-07-27
    12
  • 熊猫酒仙
    有几个疑问,请老师指点迷津。
    1.扩展类加载器的父类,是启动类加载器,而后者是C++实现的,java继承C++的类?不大能理解。
    2.虚方法的概念在C++中有了解过,java中的虚方法该如何定义呢?以前没接触过java虚方法的概念
    3.我以前的理解是,有一个零值(0/null)初始化,针对于类的静态成员变量,如果是final修饰的静态成员变量,也就是常量,是初始化为代码中指定的值比如10。非final修饰的静态成员变量,在clint执行过程中赋值为代码中指定的值,请问老师是这样的吗?

    作者回复: 1. 可能我翻译得有点瑕疵,导致了你的误解。这里我指的是扩展器类的 父-类加载器,而不是父类-(加载器)。
    2. Java中所有的非私有实例方法,都算是虚方法。调用这些方法的指令,也区分直接调用和虚调用。下一篇我会讲到。
    3. 赞一个。被final修饰的静态成员变量,如果不是基本类型或者字符串,也会放在clinit 来做。

    2018-07-25
    1
    11
  • Geek_dde3ac
    请问有什么办法或者工具可以看到类加载的这些过程呢?
    2018-07-25
    1
    9
  • 吴天
    每次new一个类都是一次初始化吧?加载和链接以后生成的是什么样的数据结构?存储在什么地方?

    作者回复: 类的初始化只会发生一次,你可能指的是实例的初始化?

    JVM并不会直接使用.class文件,类加载链接的目的就是在JVM中创建相应的类结构,会存储在元空间(我之前用的老说法”方法区”,感谢某同学指出)。

    2018-07-25
    8
  • 小蛋壳
    加载阶段都加载哪些类呢,那么多类,全部加载吗?

    作者回复: 加载阶段是针对单个类的,一般用到的类才会被加载。大部分情况下,不同类的加载阶段是不同的。

    2018-07-26
    7
  • Super丶X
    老师,你说可以通过不同的类加载器加载同一个类得到类的不同版本,我有个疑问,类是通过包名加类名来使用的,那怎么样区分不同的类加载器加载的类呢?

    作者回复: 你指的是在写代码的时候如何区分对吧?我认为没法区分。如果你有一个类的两个不同版本,而且它们不兼容,那么编译时指向哪个,就按哪个来编译。也就是说,如果要同时使用两个版本,那么你需要分开编译。

    2018-07-25
    7
  • scutware
    您在评论回复说.class在加载后已经写入方法区(元空间),但是我理解在方法区里类代码的方法调用应该是实际的调用地址吧?而取得实际调用地址不是在链接阶段吗?这里不太理解,求解答~

    作者回复: 链接时取得的不是被加载类的地址,而且被加载类所调用的其它方法的地址

    2018-07-26
    1
    5
  • L.B.Q.Y
    从大的方面讲,类加载的结果是把一段字节流变换成Class结构并写方法区,实际写方法区具体是发生在加载、链接、初始化的哪个环节呢?

    作者回复: 在加载阶段就已经生成class结构了,所以我认为应该已经写入了方法区,只是被标记为未链接而暂不能使用。

    2018-07-25
    2
    5
  • airfly
    没明白java虚拟机和类加载器的关系,
    2019-02-28
    4
  • hero
    你该加油了,期待接下来精彩……
    2018-07-26
    2
    4
  • 佑儿
    总结:
    jvm加载java类就是将字节流(如.class文件,网络传输的字节流)文件加入到内存中的过程,分为以下三步:加载、链接、初始化

    加载:查找字节流并且据此创建类的过程,每一种类加载器加载一部分类
            加载规则:双亲委派机制
            类的唯一性:类加载器名称+类全限定名称
            类加载器:
            启动类加载器:无对应的java对象,负责加载最基础的类。如jre/lib下的类以及有虚拟机参数-Xbootclasspath指定的类,
            扩展类加载器:有对应的java对象,父类启动类加载器,负责加载jre/ext下类以及系统变量java.ext.dirs指定的类
                                  该类加载器被启动类加载器加载之后方能加载其他类,
                                   
            应用类加载器:有对应的java对象,父类是扩展类加载器,负责加载应用程序路径下的类/classpath、系统变量java.class.path或者环境变量classpath指定的类。

    链接:验证、准备、解析
            验证:在于确定被加载类满足jvm的约束条件。
           准备:为被加载类的静态字段分配内存。
           解析:将符号引用解析为实际引用,
                    符号引用是在编译阶段由编译器生成,包含目标方法所在类的名字、目标方法的名字、接收参数类型以及返回值类型

    初始化:为标记为常量值的字段(基本类型或字符串且被修饰为final)赋值,以及执行<clinit>方法(其他赋值操作和静态代码块)
                 类的初始化过程是线程安全的,并且只能被初始化一次。jvm会通过加锁来保证<clinit>方法仅被执行一次
                 初始化的时机(对一个类的主动引用)
                   

    被动引用并不会引发类的初始化,如引用类的静态常量,引用父类的静态字段不会初始化子类,数组定义来引用类不会导致初始化。
    2019-05-16
    3
  • Geek_436873
    看完了整篇文章,其实我还是没搞明白,加载-链接-初始化 这三个步骤的关系。首先我理解加载就是把编译好的.class文件读如jvm内存,存放至方法区。至于链接,我觉得暂时不用去深究。初始化则是比较常见的,我们去new操作或者访问静态变量时会触发类的初始化操作。我的问题是:1.什么时候触发类加载?2:加载-链接-初始化一定是三者都发生的吗,会存在某个累只加载,不链接,不初始化的情况吗
    2018-12-22
    1
    3
  • Eric
    关于新建数组是否会链接,第二个实践生成一个不能通过链接的验证阶段的类,但是我不明白怎么确定它是在new LazyHolder[2]时验证还是在return LazyHolder.INSTANCE时验证的?运行了指令后得到:
    Error: A JNI error has occurred, please check your installation and try again
    Exception in thread "main" java.lang.VerifyError: Operand stack overflow
    Exception Details:
      Location:
        Singleton.<init>()V @0: aload_0
    可不可以这样理解:新建数组new LazyHolder[2]不需要初始化,只有return LazyHolder.INSTANCE才会导致初始化。而验证不通过的地方为初始化部分,所以,可以认为新建数组不会链接元素类LazyHolder;在getInstance(false)时才真正链接和初始化。
    2018-09-24
    3
  • 志远
    请问,关于双亲委派类加载器的情况,与周志明的深入理解java虚拟机矛盾啊,到底听哪个呢?周志明书籍中lib/ext是由扩展类加载器加载,你这里是启动类加载器加载,到底哪个是正确的呢?

    作者回复: lib/ext是由扩展类加载器加载的,我文中应该也是这样讲的。

    2018-07-25
    3
收起评论
99+
返回
顶部