• Java源代码是如何编译,加载到内存中的?


    1.前言

    相信许多开发同学看过《深入理解java虚拟机》,也阅读过java虚拟机规范,书籍和文档给人的感觉不够直观,本文从一个简单的例子来看看jvm是如何工作的吧。

    本文所有操作均在mac上进行。

    2.示例代码

    示例代码采用最常见的双重检索单例模式:

    package interview.desginpattern.singletion.doublecheck;
    
    import java.io.Serializable;
    
    public class Singleton implements Serializable {
        private static volatile Singleton instance = null;
    
        private Singleton() {
        }
    
        public static Singleton getInstance() {
            if (instance == null) {
                synchronized (Singleton.class) {
                    if (instance == null) {
                        instance = new Singleton();
                    }
                }
            }
    
            return instance;
        }
    }

    3.指令

    经过编译后,我们得到class文件,然后用javap命令查看相关指令

    javap -c Singleton:

    public class interview.desginpattern.singletion.doublecheck.Singleton implements java.io.Serializable {
      public static interview.desginpattern.singletion.doublecheck.Singleton getInstance();
        Code:
           0: getstatic     #2                  // get static instance
           3: ifnonnull     37                  // instance is null,jump 37
           6: ldc           #3                  // push class Singleton to operand stack
           8: dup                               // Duplicate the top (class Singleton) operand stack value
           9: astore_0                          // Store reference(class Singleton) into local variable
          10: monitorenter                      // synchronized start
          11: getstatic     #2                  
          14: ifnonnull     27                  // instance is null,jump 27
          17: new           #3                  // new class interview/desginpattern/singletion/doublecheck/Singleton
          20: dup                               
          21: invokespecial #4                  // Method "":()V
          24: putstatic     #2                  
          27: aload_0                           // Load reference (class Singleton) from local variable
          28: monitorexit                       // synchronized end
          29: goto          37
          32: astore_1
          33: aload_0
          34: monitorexit                       // synchronized end (exception)
          35: aload_1
          36: athrow                            // throw exception
          37: getstatic     #2                  
          40: areturn
        Exception table:
           from    to  target type
              11    29    32   any
              32    35    32   any
    
      static {};                                // static code
        Code:
           0: aconst_null                       // Push the null object reference onto the operand stack
           1: putstatic     #2                  // set static instance
           4: return
    }

    有以下几点需要说明:

    • operand stack 即操作数栈,区别于jvm虚拟机栈,是属于栈帧(frame)中的数据结构
    • local variable 即本地变量,也属于栈帧中的结构
    • synchronized 被解析成monitorenter 和 monitorexit两条指令,并且需要处理异常情形

    我们知道一般来讲jvm 运行时数据包括,pc寄存器,stack(虚拟机栈),heap,method area, 运行时常量池,本地方法栈。stack 中的每一帧包括,operand stack, local variable , 和指向常量池的指针。

    4.class文件

    上一节中只是展示了java代码编译成class文件,包含了哪些指令,但是class文件包含的信息远远不止这些。

    我们在IDEA 中使用插件 jclasslib bytecode viewer 查看 class文件具体包含哪些信息:

    4.1.一般信息

    这里省略了class 文件的魔法数标志,0xCAFEBABE

    4.2.接口和字段

    4.3.方法

    • 方法对应Singletion的构造方法
    • 方法对应Singletion.class的构造方法

    我们还可以查看getInstatnce编译后的指令 (chapter 3):

    4.4.常量池

    上述数据结构(4.1, 4.2, 4.3)不会存字符串字面量,而是指向常量池的引用:

    4.5.属性

    5.加载

    加载即根据class文件创建对象/接口.

    有两种加载器,分别是bootstrap class loader,和user-defined class cloader,我们可以自定义加载器,比如从网络,或者从加密的class文件中加载对象。

    任何一个class/inteface 被限定名 + 类加载器唯一确定,所以jvm实现者在并发的情况下需要确保此唯一性约束。

    一般而言,加载流程如下

    1. 看该文件有没有被对应的加载器进行加载
    2. 验证该文件是不是class文件,major or minor version 是否被支持
    3. 检查父类是否被加载
    4. 检查接口是否被加载

    6.链接(验证,准备,解析)

    验证:确保class文件结构是否正确,是否打破jvm规范

    准备:给class或者interface的静态属性设置默认值(区别于显式赋值)

    解析:给 symbolic references 赋予确定的值(除了 invokedynamic,其他都可以唯一确定

    7.初始化

    初始化:调用class或者interface的, 方法 (4.3)

    8.总结

    本文概述了java代码是如何加载到jvm中的,jvm有各种不同的实现(我们最熟悉的hotspot虚拟机),其中细节可能不尽相同。加载到jvm中并不代表程序生命周期的结束,运行时的情况也值得关注。

    9.参考

    https://docs.oracle.com/javase/specs/jvms/se8/jvms8.pdf

  • 相关阅读:
    人工智能 AI 概念梳理
    如何搭建npm私服以及发布包
    微软疑断自由软件开发者“活路”,禁止在微软商店发布商业开源
    [源码分享]基于Unity的Live2D虚拟人物——结合了GPT、Azure、情绪识别和口型同步,也可以集合苹果Vision Pro做成3D的形象
    Python异步爬虫(aiohttp版)
    看过来——用Python探索《红楼梦》的人物关系
    【算法集训专题攻克篇】第十四篇之栈
    Heartbleed Vulnerability-心脏滴血漏洞
    物联网电池产品硬件电路设计思维
    JAVA编程思想N刷
  • 原文地址:https://www.cnblogs.com/darcy-yuan/p/17616279.html