写在前面

本篇内容主要介绍从官方文档入手来学习JDK、JRE、JVM,源码到类文件,class文件格式,类加载机制,ClassLoader分类,整体认识Run-Time Data Areas等相关内容。

一、官网定义

1.1. JDK8

官网:https://docs.oracle.com/javase/8/

1.2. 三者之间关系 JDK/JRE/JVM

Java Platform Standard Edition 8 Documentation :https://docs.oracle.com/javase/8/docs/index.html、

JVM:https://docs.oracle.com/javase/specs/jvms/se8/html/index.html

以下是官网中的一张图,说明了Oracle Java SE产品的组件,从下图来看JDK是包含JRE的,JRE又包含JVM。

JDK其实是在我们开发者的层面提供了很多工具,包含Java、javac、javadoc、javap这些工具提供给我们去使用。

JRE提供了很多的类库如Swing、Image/IO、JDBC、JNDI、IDL、RMI、JMX等,提供运行时的支撑。

Java语言之所以能一次编译到处运行,主要是因为各个操作系统的 Jvm 的支持,如docker中的docker engine一样,现在的版本 Java HotSpot Client and Server VM 。

image-20201121153414854

Oracle has two products that implement Java Platform Standard Edition (Java SE) 8: Java SE Development Kit (JDK) 8 and Java SE Runtime Environment (JRE) 8. JDK 8 is a superset of JRE 8, and contains everything that is in JRE 8, plus tools such as the compilers and debuggers necessary for developing applets and applications. JRE 8 provides the libraries, the Java Virtual Machine (JVM), and other components to run applets and applications written in the Java programming language. Note that the JRE includes components not required by the Java SE specification, including both standard and non-standard Java components.

Oracle有两种实现Java Platform Standard Edition(Java SE)8的产品:Java SE Development Kit(JDK)8和Java SE Runtime Environment(JRE)8。JDK 8是JRE 8的超集,并且包含JRE中的所有内容以及开发小程序和应用程序所需的工具,例如编译器和调试器。 JRE 8提供了库,Java虚拟机(JVM)和其他组件,以运行用Java编程语言编写的小程序和应用程序。请注意,JRE包含Java SE规范不需要的组件,包括标准和非标准Java组件。

二、源码到类文件

创建一个 Person.java 文件

/**
 * Created by zhangfeibiao on 2020/11/21.
 */
public class Person {
    private String name;
    private int age;
    private static String address;
    private final String hobby = "Programming";
    public void setName(String name) {
        this.name = name;
    }
    public String getName() {
        return this.name;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public int getAge() {
        return this.age;
    }
    public void setAddress(String address) {
        Person.address = address;
    }
    public String getAddress() {
        return this.address;
    }
    public String getHobby() {
        return this.hobby;
    }
    public static void main(String[] args) {
        Person person = new Person();
        person.setAge(19);
        person.setName("张三");
        person.setAddress("上海市");
        System.out.println("this person age is " + person.getAge() + " and her name is " + person.getName() + " and her hobby is " + person.getHobby() + " and her address is " + person.getAddress());
    }
}

2.1. 编译过程

Person.java -> 词法分析器 -> tokens流 -> 语法分析器 -> 语法树/抽象语法树 -> 语义分析器

-> 注解抽象语法树 -> 字节码生成器 -> Person.class文件

进行javac Person.java 得到如下字节码文件 Person.class

cafe babe 0000 0034 0062 0a00 1e00 3908
003a 0900 0800 3b09 0008 003c 0900 0800
3d09 0008 003e 0a00 1e00 3f07 0040 0a00
0800 390a 0008 0041 0800 420a 0008 0043
0800 440a 0008 0045 0900 4600 4707 0048
0a00 1000 3908 0049 0a00 1000 4a0a 0008
004b 0a00 1000 4c08 004d 0a00 0800 4e08
004f 0a00 0800 5008 0051 0a00 0800 520a
							.......

2.2. 类文件结构

类文件由单个类文件结构组成: https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html

Jdk官方定义一个java .class字节码的文件结构格式如下。

ClassFile {
    u4             magic;
    u2             minor_version;
    u2             major_version;
    u2             constant_pool_count;
    cp_info        constant_pool[constant_pool_count-1];
    u2             access_flags;
    u2             this_class;
    u2             super_class;
    u2             interfaces_count;
    u2             interfaces[interfaces_count];
    u2             fields_count;
    field_info     fields[fields_count];
    u2             methods_count;
    method_info    methods[methods_count];
    u2             attributes_count;
    attribute_info attributes[attributes_count];
}
魔数与class文件版本 
常量池 
访问标志 
类索引、父类索引、接口索引 
字段表集合 
方法表集合 
属性表集合 

结合上面编译得到的Person.class字节码文件,对jdk定义的java字节码文件结构进行部分内容解读:

magic解释:
The `magic` item supplies the magic number identifying the `class` file format; it has the value `0xCAFEBABE`.

u1 u2 u3 u4 每一个表示两位,即U4 前8位是cafe babe,Jvm会认为这是一个java class文件,官方定义。

minor_version, major_version 解释:
0000 最小版本, 0034 对应10进制的52,代表JDK 8中的一个版本 

constant_pool_count 解释:
0062 十进制是98,代表常量池中27个常量 

三、类加载机制(类文件到虚拟机)

https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html

The Java Virtual Machine dynamically loads, links and initializes classes and interfaces. Loading is the process of finding the binary representation of a class or interface type with a particular name and creating a class or interface from that binary representation. Linking is the process of taking a class or interface and combining it into the run-time state of the Java Virtual Machine so that it can be executed. Initialization of a class or interface consists of executing the class or interface initialization method (§2.9).

Java虚拟机动态加载,链接和初始化类和接口。加载是查找具有特定名称的类或接口类型的二进制表示形式并从该二进制表示形式创建类或接口的过程。链接是获取类或接口并将其组合到Java虚拟机的运行时状态以便可以执行的过程。类或接口的初始化包括执行类或接口的初始化方法(第2.9节)。

JVM类加载过程分为三个过程,装载、链接、初始化,使用和卸载不算是类加载过程中的阶段。

image-20201121200448323

3.1. 装载(Loading)

javac编译成class文件后交给JVM,先找到类文件所在的磁盘位置, ClassLoader.find(String name),查找和导入class文件,类加载过程中的第一步装载分为三个步骤:

  1. 通过一个类的全限定名获取定义此类的二进制字节流
  2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
  3. 在Java堆中生成一个代表这个类的 java.lang.Class 对象,作为对方法区中这些数据的访问入口
// jdk中 ClassLoader.loadClass 方法
protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            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();
                    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;
        }
    }

3.2. 链接(Linking)

类加载过程第二步是链接,分为验证、准备、解析三个过程。

3.2.1. 验证

保证被加载类的正确性

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

3.2.2. 准备

如为类的静态变量分配内存,并将其初始化为默认值

如:static int a = 10,首先是对a分配一个空间,且初始化默认值为0。

3.2.3. 解析

把类中的符号引用转换为直接引用

https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.1

如:String str = 地址是什么

3.3. 初始化(Initializing)

类加载过程的第三步初始化主要包括静态变量初始化真正的值,如第二步链接过程中准备阶段a=0,此时会变成a=10。

四、类装载器ClassLoader

在装载(Load)阶段,其中第1部分:通过类的全限定名获取其定义的二进制字节流,需要借助类装载器完成,顾名思义,就是用来装载Class文件的。

4.1. 类装载器分类

  1. Bootstrap ClassLoader

    启动类装载器,负责加载$JAVA_HOME中 jre/lib/rt.jar 里所有的class或 Xbootclassoath选项指定的jar包。由C++实现,不是ClassLoader子类。

  2. Extension ClassLoader

    扩展类装载器,负责加载java平台中扩展功能的一些jar包,包括$JAVA_HOME中 jre/lib/*.jar 或 -Djava.ext.dirs指定目录下的jar包。

  3. App ClassLoader

    应用类装载器,负责加载classpath中指定的jar包及 Djava.class.path 所指定目录下的类和 jar包。

  4. Custom ClassLoader

    自定义装载器,通过java.lang.ClassLoader的子类自定义加载class,属于应用程序根据 自身需要自定义的ClassLoader,如tomcat、jboss都会根据j2ee规范自行实现ClassLoader。

image-20201121201151618

4.2. 加载原则

检查某个类是否已经加载:顺序是自底向上,从Custom ClassLoader到BootStrap ClassLoader逐层检查,只要某个Classloader已加载,就视为已加载此类,保证此类只所有ClassLoader只加载一次。

加载的顺序:加载的顺序是自顶向下,也就是由上层来逐层尝试加载此类。

4.3. 双亲委派机制

定义: 如果一个类加载器在接到加载类的请求时,它首先不会自己尝试去加载这个类,而是把这个请求任务委托给父类加载器去完成,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。

优势: Java类随着加载它的类加载器一起具备了一种带有优先级的层次关系。比如,Java中的Object类,它存放在rt.jar之中,无论哪一个类加载器要加载这个类,最终都是委派给模型最顶端的启动类加载器进行加载,因此Object在各种类加载环境中都是同一个类。如果不采用双亲委派模型,那么由各个类加载器自己取加载的话,那么系统中会存在多种不同的Object类。

破坏: 可以继承ClassLoader类,然后重写其中的loadClass方法,也有其它方式如线程层面。

五、运行时数据区(Run-Time Data Areas)

https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.5

理解运行时数据区是很重要的,随着数据增加就会有不断的内存空间被使用,当内存不够用的时候就需要垃圾回收,就会涉及垃圾回收机制,就会引入垃圾回收器以及垃圾回收算法。

运行时数据区包括五大部分,方法区、堆、Java虚拟机栈、程序计数器、本地方法栈,其中方法区还包括运行时常量池。

The Java Virtual Machine defines various run-time data areas that are used during execution of a program. Some of these data areas are created on Java Virtual Machine start-up and are destroyed only when the Java Virtual Machine exits. Other data areas are per thread. Per-thread data areas are created when a thread is created and destroyed when the thread exits.

Java虚拟机定义了在程序执行期间使用的各种运行时数据区域。其中一些数据区域是在Java虚拟机启动时创建的,仅在Java虚拟机退出时才被销毁。其他数据区域是每个线程的。在创建线程时创建每个线程的数据区域,在线程退出时销毁每个数据区域。

image-20201121204015145

5.1. 方法区(Method Area)

https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.5.4

方法区只有一个,线程共享的内存区域(线程非安全),在虚拟机启动时创建。

用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做Non-Heap(非堆),目的是与Java堆区分开来。

当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。

The Java Virtual Machine has a method area that is shared among all Java Virtual Machine threads. The method area is analogous to the storage area for compiled code of a conventional language or analogous to the "text" segment in an operating system process. It stores per-class structures such as the run-time constant pool, field and method data, and the code for methods and constructors, including the special methods (§2.9) used in class and instance initialization and interface initialization.

Java虚拟机具有一个在所有Java虚拟机线程之间共享的方法区域。该方法区域类似于常规语言的编译代码的存储区域,或者类似于操作系统过程中的“文本”段。它存储每个类的结构,例如运行时常量池,字段和方法数据,以及方法和构造函数的代码,包括用于类和实例初始化以及接口初始化的特殊方法(§2.9)。

The method area is created on virtual machine start-up. Although the method area is logically part of the heap, simple implementations may choose not to either garbage collect or compact it. This specification does not mandate the location of the method area or the policies used to manage compiled code. The method area may be of a fixed size or may be expanded as required by the computation and may be contracted if a larger method area becomes unnecessary. The memory for the method area does not need to be contiguous.

方法区域是在虚拟机启动时创建的。尽管方法区域在逻辑上是堆的一部分,但是简单的实现可以选择不进行垃圾回收或压缩。该规范没有规定方法区域的位置或用于管理已编译代码的策略。方法区域可以是固定大小的,或者可以根据计算的需要进行扩展,如果不需要更大的方法区域,则可以缩小。方法区域的内存不必是连续的。

If memory in the method area cannot be made available to satisfy an allocation request, the Java Virtual Machine throws an OutOfMemoryError.

如果无法提供方法区域中的内存来满足分配请求,则Java虚拟机将引发OutOfMemoryError。

此时回看装载阶段的第2步:将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构

如果这时候把从Class文件到装载的第1和2步合并起来理解的话,可以画下图:

image-20201121201704612

值得说明的是

  1. 方法区在JDK 8中就是Metaspace(元空间),在JDK6或7中就是Perm Space(永久带)

  2. Run-Time Constant Pool

    Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池,用于存放编译时期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。

    Each run-time constant pool is allocated from the Java Virtual Machine's method area (§2.5.4).s

5.2. 堆(Heap)

https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.5.3

堆只有一个,线程共享的内存区域(线程非安全),在虚拟机启动时创建。

Java对象实例以及数组都在堆上分配。

当堆无法满足内存分配需求时,将抛出OutOfMemoryError异常。

The Java Virtual Machine has a heap that is shared among all Java Virtual Machine threads. The heap is the run-time data area from which memory for all class instances and arrays is allocated.
Java虚拟机具有一个在所有Java虚拟机线程之间共享的堆。堆是运行时数据区,从中分配所有类实例和数组的内存。

The heap is created on virtual machine start-up. Heap storage for objects is reclaimed by an automatic storage management system (known as a garbage collector); objects are never explicitly deallocated. The Java Virtual Machine assumes no particular type of automatic storage management system, and the storage management technique may be chosen according to the implementor's system requirements. The heap may be of a fixed size or may be expanded as required by the computation and may be contracted if a larger heap becomes unnecessary. The memory for the heap does not need to be contiguous.

堆是在虚拟机启动时创建的。自动存储管理系统(称为垃圾收集器)可以回收对象的堆存储;对象永远不会显式释放。 Java虚拟机不假定使用特定类型的自动存储管理系统,并且可以根据实现者的系统要求选择存储管理技术。堆的大小可以是固定的,也可以根据计算要求进行扩展,如果不需要更大的堆,则可以将其收缩。堆的内存不必是连续的。

此时回看装载阶段的第3步:在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口:

image-20201121205306967

5.3. Java 虚拟机栈(Java Virtual Machine Stacks)

https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.5.2

经过上面的分析,类加载机制的装载过程已经完成,后续的链接,初始化也会相应的生效。

假如目前的阶段是初始化完成了,后续做啥呢?肯定是使用咯,不用的话这样折腾来折腾去有什么意义?那怎样才能被使用到?换句话说里面内容怎样才能被执行?比如通过主函数main调用其他方法,这种方式实际上是main线程执行之后调用的方法,即要想使用里面的各种内容,得要以线程为单位,执行相应的方法才行。

那一个线程执行的状态如何维护?一个线程可以执行多少个方法?这样的关系怎么维护呢?这就引出了Java 虚拟机栈。

每一个Java虚拟机线程都有自己的Java虚拟机栈,与该线程同时创建,这个栈存储方法frame。

虚拟机栈是一个线程执行的区域,保存着一个线程中方法的调用状态。换句话说,一个Java线程的运行状态,由一个虚拟机栈来保存,所以虚拟机栈肯定是线程私有的,独有的,随着线程的创建而创建。

每一个被线程执行的方法,为该栈中的栈帧,即每个方法对应一个栈帧。

调用一个方法,就会向栈中压入一个栈帧;一个方法调用完成,就会把该栈帧从栈中弹出。

Each Java Virtual Machine thread has a private Java Virtual Machine stack, created at the same time as the thread. A Java Virtual Machine stack stores frames (§2.6). A Java Virtual Machine stack is analogous to the stack of a conventional language such as C: it holds local variables and partial results, and plays a part in method invocation and return. Because the Java Virtual Machine stack is never manipulated directly except to push and pop frames, frames may be heap allocated. The memory for a Java Virtual Machine stack does not need to be contiguous.

每个Java虚拟机线程都有一个私有Java虚拟机栈,与该线程同时创建。 Java虚拟机栈存储frames(第2.6节)。 Java虚拟机堆栈类似于C之类的常规语言的堆栈:它保存局部变量和部分结果,并在方法调用和返回中起作用。因为除了推送和弹出帧外,从不直接操纵Java虚拟机堆栈,所以可以为堆分配帧。 Java虚拟机堆栈的内存不必是连续的。

Frames: https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.6

A frame is used to store data and partial results, as well as to perform dynamic linking, return values for methods, and dispatch exceptions.

frames框架用于存储数据和部分结果,以及执行动态链接,方法的返回值和调度异常。

A new frame is created each time a method is invoked. A frame is destroyed when its method invocation completes, whether that completion is normal or abrupt (it throws an uncaught exception).

每次调用方法时都会创建一个新frame。frame的方法调用完成后,无论该完成是正常的还是突然的(它引发未捕获的异常),它都会被销毁。

If the computation in a thread requires a larger Java Virtual Machine stack than is permitted, the Java Virtual Machine throws a StackOverflowError.

如果线程中的计算所需的Java虚拟机堆栈超出允许的范围,则Java虚拟机将引发StackOverflowError。

If Java Virtual Machine stacks can be dynamically expanded, and expansion is attempted but insufficient memory can be made available to effect the expansion, or if insufficient memory can be made available to create the initial Java Virtual Machine stack for a new thread, the Java Virtual Machine throws an OutOfMemoryError.

如果可以动态扩展Java虚拟机堆栈,并尝试进行扩展,但是可以提供足够的内存来实现扩展,或者如果没有足够的内存来为新线程创建初始Java虚拟机堆栈,Java虚拟机机器抛出OutOfMemoryError。

虚拟机栈执行过程如下图,倘若出现循环调用或者递归次数过多,深度不够超出允许的范围,就会导致着栈不够用,出现栈溢出。

image-20201121205508122

Java虚拟机栈执行方法的时候,经历了什么?上面Person类 通过javap反编译得到字节码指令,其实就是对应虚拟机栈执行过程。

字节码指定解读地址:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#

iconst: Push int constant

istore: Store int into local variable

执行 javap -c Person.class > Person.txt 得到下面反编译文件

Compiled from "Person.java"
public class Person {
  public Person();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: aload_0
       5: ldc           #2                  // String Programming
       7: putfield      #3                  // Field hobby:Ljava/lang/String;
      10: return

  public void setName(java.lang.String);
    Code:
       0: aload_0
       1: aload_1
       2: putfield      #4                  // Field name:Ljava/lang/String;
       5: return

  public java.lang.String getName();
    Code:
       0: aload_0
       1: getfield      #4                  // Field name:Ljava/lang/String;
       4: areturn
	...
  public static void main(java.lang.String[]);
    Code:
       0: new           #8                  // class Person
       3: dup
       4: invokespecial #9                  // Method "<init>":()V
       7: astore_1
       8: aload_1
       9: bipush        19
      11: invokevirtual #10                 // Method setAge:(I)V
      14: aload_1
      15: ldc           #11                 // String 张三
      17: invokevirtual #12                 // Method setName:(Ljava/lang/String;)V
      20: aload_1
      21: ldc           #13                 // String 上海市
      23: invokevirtual #14                 // Method setAddress:(Ljava/lang/String;)V
      26: getstatic     #15                 // Field java/lang/System.out:Ljava/io/PrintStream;
      ...
      90: return
}

那Java虚拟机栈中方法对应的栈桢 frame 包含的内容如下,可以归纳为四个部分,最后两个是正常返回和异常返回归属完成阶段。

2.6.1. Local Variables 局部变量表

2.6.2. Operand Stacks 操作数栈

2.6.3. Dynamic Linking 动态链接

2.6.4. Normal Method Invocation Completion 正常返回地址

2.6.5. Abrupt Method Invocation Completion 异常返回地址

模拟栈溢出

/**
 * 
 * Created by zhangfeibiao on 2020/11/21.
 */
public class StackOverFlowDemo {
    public static long count = 0;
    public static void method(long i) {
        System.out.println(count++);
        method(i);
    }
    public static void main(String[] args) {
        StackOverFlowDemo.method(1);

    }
}

image-20201123154443122

可以看到当执行到7099次的时候出现 StackOverflowError,64位的操作系统栈默认的深度大概是7千多,当然栈的大小可以设置,如果超出栈的内存大小会出现OutOfMemory Metaspace。

5.4. 程序计数器(The pc Register)

https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.5.1

我们都知道一个JVM进程中有多个线程在执行,而线程中的内容是否能够拥有执行权,是根据CPU调度来的。

假如线程A正在执行到某个地方,突然失去了CPU的执行权,切换到线程B了,然后当线程A再获得CPU执行权的时候,怎么能继续执行呢?这就是需要在线程中维护一个变量,记录线程执行到的位置。

每一个线程都有自己的程序计数器,记录执行方法的位置。

程序计数器占用的内存空间很小,由于Java虚拟机的多线程是通过线程轮流切换,并分配处理器执行时间的方式来实现的,在任意时刻,一个处理器只会执行一条线程中的指令。因此,为了线程切换后能够恢复到正确的执行位置,每条线程需要有一个独立的程序计数器(线程私有)。

如果线程正在执行Java方法,则计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Native方法,则这个计数器为空。

The Java Virtual Machine can support many threads of execution at once (JLS §17). Each Java Virtual Machine thread has its own pc (program counter) register. At any point, each Java Virtual Machine thread is executing the code of a single method, namely the current method (§2.6) for that thread. If that method is not native, the pc register contains the address of the Java Virtual Machine instruction currently being executed. If the method currently being executed by the thread is native, the value of the Java Virtual Machine's pc register is undefined. The Java Virtual Machine's pc register is wide enough to hold a returnAddress or a native pointer on the specific platform.

Java虚拟机可以一次支持多个执行线程(JLS§17)。每个Java虚拟机线程都有其自己的pc(程序计数器)寄存器。在任何时候,每个Java虚拟机线程都在执行单个方法的代码,即该线程的当前方法(第2.6节)。如果该方法不是本机方法,则pc寄存器包含当前正在执行的Java虚拟机指令的地址。如果线程当前正在执行的方法是本地方法,则Java虚拟机的pc寄存器的值未定义。 Java虚拟机的pc寄存器足够宽,可以在特定平台上保存返回地址或本机指针。

5.5. 本地方法栈(Native Method Stacks)

https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.5.6

如果当前线程执行的方法是Native类型的,这些方法就会在本地方法栈中执行。

An implementation of the Java Virtual Machine may use conventional stacks, colloquially called "C stacks," to support native methods (methods written in a language other than the Java programming language). Native method stacks may also be used by the implementation of an interpreter for the Java Virtual Machine's instruction set in a language such as C. Java Virtual Machine implementations that cannot load native methods and that do not themselves rely on conventional stacks need not supply native method stacks. If supplied, native method stacks are typically allocated per thread when each thread is created.

Java虚拟机的实现可以使用传统的栈(俗称“ C堆栈”)来支持本机方法(用Java编程语言以外的语言编写的方法)。解释器的实现也可以使用诸如C之类的语言来解释Java虚拟机的指令集,从而使用本机方法栈。Java虚拟机实现无法加载本机方法并且自身不依赖于常规堆栈,因此无需提供本机方法堆栈。如果提供,通常在创建每个线程时为每个线程分配本机方法堆栈。

5.6. 运行时常量池(Run-Time Constant Pool)

https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.5.5

运行时常量池属于方法区中的一部分,用于存放编译时期生成的各种字面量和符号引用.

A run-time constant pool is a per-class or per-interface run-time representation of the constant_pool table in a class file (§4.4). It contains several kinds of constants, ranging from numeric literals known at compile-time to method and field references that must be resolved at run-time. The run-time constant pool serves a function similar to that of a symbol table for a conventional programming language, although it contains a wider range of data than a typical symbol table.

运行时常量池是类文件(第4.4节)中constant_pool表的按类或按接口的运行时表示。它包含多种常量,从编译时已知的数字文字到必须在运行时解析的方法和字段引用。运行时常量池的功能类似于常规编程语言的符号表,尽管它包含的数据范围比典型的符号表还大。

Each run-time constant pool is allocated from the Java Virtual Machine's method area (§2.5.4). The run-time constant pool for a class or interface is constructed when the class or interface is created (§5.3) by the Java Virtual Machine.

每个运行时常量池都是从Java虚拟机的方法区域(第2.5.4节)分配的。 Java虚拟机创建类或接口(第5.3节)时,将构造该类或接口的运行时常量池。

运行时数据区和内存模型区别

没有物理上的关联关系,只有逻辑上的对应。

运行时数据区:程序执行的时候数据运行的状态,属于JVM层面的,考虑线程安全的话主要涉及方法区(metaspace)和堆(heap)。

JVM内存模型:主要描述方法区(Metaspace)和堆(Heap)的内存结构划分。

Java内存模型(JMM):描述物理内存情况,属于Java层面,一般讨论内存模型会说到线程安全的事情,因为会对主内存和工作内存数据进行对比。

Q.E.D.


越努力,越幸运!