# JVM

## Java从编码到执行

![image-20220328105816911](/files/VpJjzKTdB9lxhltOb8nQ)

Java是一种编译与解释都有的编程语言，其中大部分的代码时通过编译为字节码执行的，而一些特定的次数较多的代码将使用JIT做本地编译，直接交给操作系统调用。

编译器可以修改编译模式，共三种：

1. `-Xint`，纯解释模式，编译极快，启动较慢
2. `-Comp`，纯编译模式，编译很慢，启动很快
3. `-Xmixed`，混合模，开始解释执行，启动速度较快，对热点代码实时检测和编译
   1. 多次被调用的方法
   2. 多次被调用的循环

## 什么是JVM

JVM是一种跨语言的平台，是一种规范。可以在JVM上运行的语言多大一百多种。JVM本身不是跨平台的，每种操作系统都有对应的实现。只要编程语言可以编译为class文件，就可以运行在JVM中。

![image-20220328110245015](/files/TJeAjLMi7yT6LyMDkFmN)

可以使用 `java -version` 命令查看当前使用的jvm类型，目前市面上主要的集中jvm实现有：

* Hotspot，Oracle官方提供
* Jrockit，BEA公司，曾号称最快的JVM，现被Oracle收购，合并到Hotspot中
* J9，IBM公司
* Microsoft VM
* TaobaoVM
* LiquidVM，普通虚拟机运行于操作系统上，而LiquidVM直接运行在物理机上，作为一个操作系统
* zing，azul 公司，商业软件，垃圾回收业界标杆（Hotspot参考了该虚拟机，提供了新的ZGC）

## Class文件格式

查看Class文件的16进制形式，使用IDEA插件BinEd，打开.class文件并使用如下操作：

![image-20220328114011149](/files/7WUqmypcxHTcHbf7iHdu)

有如下类：

```java
package apache;

public class Main {
    public Main() {
    }
}
```

他的字节码信息如下；

```
CA FE BA BE 00 00 00 34 00 10 0A 00 03 00 0D 07 00 0E 07 00 0F 01 00 06 3C 69 6E 69 74 3E 01 00 03 28 29 56 01 00 04 43 6F 64 65 01 00 0F 4C 69 6E 65 4E 75 6D 62 65 72 54 61 62 6C 65 01 00 12 4C 6F 63 61 6C 56 61 72 69 61 62 6C 65 54 61 62 6C 65 01 00 04 74 68 69 73 01 00 0D 4C 61 70 61 63 68 65 2F 4D 61 69 6E 3B 01 00 0A 53 6F 75 72 63 65 46 69 6C 65 01 00 09 4D 61 69 6E 2E 6A 61 76 61 0C 00 04 00 05 01 00 0B 61 70 61 63 68 65 2F 4D 61 69 6E 01 00 10 6A 61 76 61 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74 00 21 00 02 00 03 00 00 00 00 00 01 00 01 00 04 00 05 00 01 00 06 00 00 00 2F 00 01 00 01 00 00 00 05 2A B7 00 01 B1 00 00 00 02 00 07 00 00 00 06 00 01 00 00 00 07 00 08 00 00 00 0C 00 01 00 00 00 05 00 09 00 0A 00 00 00 01 00 0B 00 00 00 02 00 0C
```

!\[马士兵教育 java1.8类文件格式第一版]\(README.assets/马士兵教育 java1.8类文件格式第一版.png)

### 使用javap命令查看class文件信息

```
javap -help
用法: javap <options> <classes>
其中, 可能的选项包括:
  -help  --help  -?        输出此用法消息
  -version                 版本信息
  -v  -verbose             输出附加信息  #(常用)#
  -l                       输出行号和本地变量表
  -public                  仅显示公共类和成员
  -protected               显示受保护的/公共类和成员
  -package                 显示程序包/受保护的/公共类
                           和成员 (默认)
  -p  -private             显示所有类和成员
  -c                       对代码进行反汇编
  -s                       输出内部类型签名
  -sysinfo                 显示正在处理的类的
                           系统信息 (路径, 大小, 日期, MD5 散列)
  -constants               显示最终常量
  -classpath <path>        指定查找用户类文件的位置
  -cp <path>               指定查找用户类文件的位置
  -bootclasspath <path>    覆盖引导类文件的位置
```

```
$ javap -v Main.class
Classfile /C:/Users/xxx/Documents/GitHub/notes-java/zookeeper/target/test-classes/apache/Main.class
  Last modified 2022-3-28; size 251 bytes
  MD5 checksum 85c5689a1e68f826681fab80150694ee
  Compiled from "Main.java"
public class apache.Main
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #3.#13         // java/lang/Object."<init>":()V
   #2 = Class              #14            // apache/Main
   #3 = Class              #15            // java/lang/Object
   #4 = Utf8               <init>
   #5 = Utf8               ()V
   #6 = Utf8               Code
   #7 = Utf8               LineNumberTable
   #8 = Utf8               LocalVariableTable
   #9 = Utf8               this
  #10 = Utf8               Lapache/Main;
  #11 = Utf8               SourceFile
  #12 = Utf8               Main.java
  #13 = NameAndType        #4:#5          // "<init>":()V
  #14 = Utf8               apache/Main
  #15 = Utf8               java/lang/Object
{
  public apache.Main();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 7: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lapache/Main;
}
SourceFile: "Main.java"
```

### 使用IDEA Show ByteCode 查看class文件信息

![image-20220328135943224](/files/DkmE9bSVvEa7BbtHPmRY)

![image-20220328140032276](/files/IZiDjyE4Rcs3FzKeyZe5)

### 在IDEA内部配置javap工具

![image-20220328151851908](/files/uwTYiTUUofb7TlwYSeRO)

重启IDEA：

![image-20220328151152648](/files/F69ZPnnulwhoCkDTB7eT)

## 类加载机制

![image-20220328154802361](/files/Iuaph5vZo3ZkGEVeKqfn)

一个class文件被初始化到JVM通常要经过下面几个过程：

1. Loading，将class加载到内存中
2. Linking，链接
   1. Verification，校验字节码
   2. Preparation，将class文件的静态变量赋默认值（注意不是代码设置的初始值）
   3. Resolution，class文件中使用的常量池的符号引用转换为内存地址
3. Initializing，静态变量赋代码设置的初始值，调用静态代码块

### 1. Loading：ClassLoader

加载Class文件到内存的步骤由ClassLoader来完成：

![image-20220328161801013](/files/iPwxtgtTgEAZfwZug7w6)

可以在java代码中通过如下方式查询类加载器之间的关系，以及当前类是由哪个类加载器加载进来的：

```java
HelloWorld h = new HelloWorld();

// 三种类加载器之间的关系
System.out.println(h.getClass().getClassLoader()); //sun.misc.Launcher$AppClassLoader@e2f2a
System.out.println(h.getClass().getClassLoader().getParent()); //sun.misc.Launcher$ExtClassLoader@e7d9f1
System.out.println(h.getClass().getClassLoader().getParent().getParent()); // null，因为BootstrapClassLoader是由C语言实现的

// 获取三种类加载器的加载路径
System.out.println(System.getProperty("sun.boot.class.path")); // BootstrapClassLoader 扫描路径
System.out.println(System.getProperty("java.ext.dirs")); // ExtensionClassLoader 扫描路径
System.out.println(System.getProperty("java.class.path")); // AppClassLoader 扫描路径
```

#### 双亲委派机制

1. 类加载符合双亲委派机制
2. 当一个class需要被load到内存时，会自顶向上寻找class是否已经被load到内存了：CustomClassLoader、AppClassLoader、ExtensionClassLoader、BootstrapClassLoader的顺序
3. 如果某个ClassLoader已经加载这个类，那么会直接返回
4. 如果直到BootstrapClassLoader都没有加载这个类，那么Bootstrap会尝试load这个class
5. 这个load的操作会自顶向上的进行，会以 BootstrapClassLoader、ExtensionClassLoader、AppClassLoader、CustomClassLoader的顺序进行load

> 双亲委派就是从子到父的查找过程，又有一个从父到子的load过程

为什么需要双亲委派？

1. 主要是为了安全问题：如果没有双亲委派，假设第三方代码重写JDK中的核心类，对引用第三方代码的java程序造成破坏
2. 次要问题：可以解决类加载缓存的问题，放置二次加载损耗资源

#### 使用类加载器

什么时候需要手动加载一个类：

1. 动态代理生成了一个新的class，此时需要程序手动load
2. 目标class在网络上，需要先下载，再手动load
3. 热部署加载，class文件修改后，手动load

使用加载器加载一个资源为流：

```java
InputStream xx = Main.class.getClassLoader().getResourceAsStream("xx");
```

#### loadClass的源码

> findInCache -> parent.loadClass -> findClass()

```java
protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        // 检查目标是否已经被加载，这个过程是调用的native方法
        Class<?> c = findLoadedClass(name);
        
        // 如果未被加载
        if (c == null) {
            long t0 = System.nanoTime();
            // 使用父加载器加载
            try {
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }

            // 如果上面的所有父类都没有加载到，就自己加载
            if (c == null) {
                // If still not found, then invoke findClass in order
                // to find the class.
                long t1 = System.nanoTime();
                // findClass是一个protected，不同的类加载器有不同的实现
                c = findClass(name);

                // this is the defining class loader; record the stats
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

protected final Class<?> findLoadedClass(String name) {
    if (!checkName(name))
        return null;
    return findLoadedClass0(name);
}

protected Class<?> findClass(String name) throws ClassNotFoundException {
    throw new ClassNotFoundException(name);
}
```

#### 自定义类加载器

继承`ClassLoader`类并重写`loadClass`方法， 参考类`sun.misc.Launcher.AppClassLoader`：

```java
public class MyClassLoader extends ClassLoader {
    @Override
    protected Class<?> findClass(String name) {
        String myPath = "file:///Users/xxx/Desktop/" + name.replace(".","/") + ".class";
        System.out.println(myPath);
        byte[] cLassBytes = null;
        Path path = null;
        try {
            path = Paths.get(new URI(myPath));
            cLassBytes = Files.readAllBytes(path);
        } catch (IOException | URISyntaxException e) {
            e.printStackTrace();
        }
        Class clazz = defineClass(name, cLassBytes, 0, cLassBytes.length);
        return clazz;
    }
}
```

### 2. Linking

#### 2.1 Verification

验证文件是否符合JVM规定

#### 2.2 Preparation

静态成员变量赋默认值

```java
public class A {
    public static int count = 2; // 在该阶段，count的值为0
    public static T t = new T(); // 在该阶段，t的值为null
    
    private T(){
        count++;
    }
}
```

#### 2.3 Resolution

将类、方法、属性等符号引用解析为直接引用 常量池中的各种符号引用解析为指针、偏移量等内存地址的直接引用

### 3. Initialzing

调用初始化代码

```java
public class A {
    public static int count = 2; // 在该阶段count的值为3
    public static T t = new T(); // 在该阶段t的值已经被初始化为对象
    
    private T(){
        count++;
    }
}
```

## 指令重排序

有如下代码：

```java
public class Main implements Serializable {

    public static void main(String[] args) {
        Main m = new Main();
    }

}
```

他的main方法生成的指令如下：

```java
0: new           #2                  // class apache/Main
3: dup
4: invokespecial #3                  // Method "<init>":()V      调用构造器
7: astore_1                          // 将内存地址赋予给变量 m
8: return
```

因为有可能发生指令重排序，他的字节码指令有可能是如下这样的（astore\_1 和 invokespecial有可能被重排）：

```java
0: new           #2                  // class apache/Main
3: dup
7: astore_1                          // 将内存地址赋予给变量 m
4: invokespecial #3                  // Method "<init>":()V      调用构造器
8: return
```

所以也就有了双重检查锁的问题：

```java
public class S5_DoubleCheckLockImplement {

    private static  S5_DoubleCheckLockImplement INSTANCE;

    private S5_DoubleCheckLockImplement() {
    }

    public static S5_DoubleCheckLockImplement getInstance() {
        if (INSTANCE == null) {
            synchronized(S5_DoubleCheckLockImplement.class) {
                if (INSTANCE == null) {
                    INSTANCE = new S5_DoubleCheckLockImplement();
                    // astore_1     先执行该指令，此时instance已经不是null了
                    // 其他线程进入该方法，判断INSTANCE != null，直接返回了一个没有调用构造的空对象
                    // invokespecial 最后调用构造
                  	// 所以需要 volatile 关键字，防止指令重排序
                }
            }
        }
        return INSTANCE;
    }

}
```

## JMM

Java Memory Model，Java内存模型。

### 硬件层的数据一致性

![image-20220329145137738](/files/h1Hid764gxmGTUXaaJV9)

因为CPU、内存、磁盘的读取速度各不相同，所以存储器按照他们的速度将其划分为了6个层次。当CPU想要读取某个数据时，会先从寄存器开始读取，依次向下查找；如果找到了，会从下到上依次缓存到更高级别的缓存。是一种多级缓存的机制。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://yangsx95.gitbook.io/notes/programming-language/java/jvm.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
