前言
在正式学习JVM内存模型之前,请注意以下问题:
JVM内存模型和JAVA内存模型不是同一概念。 JVM内存模型是一个从运行时数据区域的结构角度描述的概念。而JAVA内存模型是从主内存和线程私有内存的角度描述的。从以下两张图片可以看出:

JAVA内存模型

JVM内存模型
Java虚拟机由三个主要模块组成:类加载器子系统的运行时数据区域执行引擎。在本文中,我们介绍了第二个主要模块-运行时数据区域(JVM内存模型)。实际上,虚拟机的这些模块不是独立的,它们彼此相关。将该java文件编译成一个类文件,通过类加载子系统加载,该信息被传输到JVM管理的内存中(该操作的一部分将与本地内存进行交互),然后再进行垃圾回收等,这些都是一系列操作。
概述
运行时数据分为几个模块(如上图所示):
线程共享区:
线程私有区域:
在本文中,我们将通过以下方法分析每个区域:
线程私有区域
程序计数器
程序计数器是一个很小的存储空间,它的功能可以看作是当前线程执行的字节码行号的指示器。字节码解释器工作时,它将使用此计数器的值来选择需要执行的下一个字节码指令。分支,循环,跳转,异常处理和线程恢复都需要依赖此区域。
用外行的话来说,该区域存储一个指向方法区域的方法字节码的指针,该指针用于存储指向下一条指令的地址,即要执行的指令代码。
如果线程正在执行Java方法,则此计数器记录正在执行的虚拟机字节码指令的地址;如果执行的是本机方法,则计数器值为空(未定义)。
当执行一行指令代码时,JVM执行引擎将更新程序计数器的值。
由于Java虚拟机的多线程是通过线程交替切换和分配处理器执行时间的方式实现的,因此在任何时候,处理器(对于多核处理器来说就是核心)将仅执行线程中的指令。因此,为了程切换后恢复到正确的执行位置,每个线程都需要具有一个独立的程序计数器。线程之间的计数器不会互相影响,而是独立存储。我们称这种类型的内存区域为“线程专用”内存。 (方法调用,该方法调用另一个方法,该方法正式满足“先进先出,后进先出,后进先出”的堆栈模型。)
OutOfMemoryError:无
虚拟机堆栈
它描述了Java方法执行的内存模型,其生命周期与线程的生命周期相同。
每个方法都会在执行的同时创建一个堆栈框架(StackFrame),每个堆栈框架都包括局部变量表,操作数堆栈,动态链接,方法导出等。正式符合“先进先出,后进先出”的堆栈模型。也就是说,从调用到执行完成的每种方法的过程都与将虚拟机堆栈中的堆栈帧压入弹出框的过程相对应。
以上只是一些非常机械的概念,很难深入理解。下面,我使用一个示例来分析虚拟机堆栈的存储内容。
首先创建一个简单程序:
package com.sunwin.robotcloud.test;
/**
* Created by 追梦1819 on 2019-11-01.
*/
public class CalculateMain {
public int calculate(){
int a = 3;
int b=4;
int c = a+b;
return c;
}
public static void main(String[] args) {
CalculateMain main = new CalculateMain();
int d = main.calculate();
System.out.println(d);
}
对于上述程序,线程启动时,虚拟机会为主线程main分配一个较大的内存空间,然后为main方法分配一个堆栈帧以存储该方法的局部变量;
执行calculate()方法时,将分配一个calculate()的堆栈框架以存储相应方法的局部变量。
应该注意,一种方法分配一个单独的存储区,即堆栈帧。
Java是一种高级语言,很难直接通过代码查看其执行过程。我们使用基础字节码对执行的指令代码进行反向分析,以分析基础执行过程。
输入CalculateMain.class文件目录并执行命令:
将脚本直接输出到文件CalculateMain.txt:
Compiled from "CalculateMain.java"
public class com.sunwin.robotcloud.test.CalculateMain {
public com.sunwin.robotcloud.test.CalculateMain();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: return
public int calculate();
Code:
0: iconst_3
1: istore_1
2: iconst_4
3: istore_2
4: iload_1
5: iload_2
6: iadd
7: istore_3
8: iload_3
9: ireturn
public static void main(java.lang.String[]);
Code:
0: new #2 // class com/sunwin/robotcloud/test/CalculateMain
3: dup
4: invokespecial #3 // Method "":()V
7: astore_1
8: aload_1
9: invokevirtual #4 // Method calculate:()I
12: istore_2
13: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
16: iload_2
17: invokevirtual #6 // Method java/io/PrintStream.println:(I)V
20: return
} 首先看一下calculate()方法。根据以上说明,查询JVM的使用手册,以获取上述程序的执行流程:
0.将int类型常量3推入(操作数)堆栈;
1.将int类型值3存储到局部变量1(1是数组下标)中,也就是说,在局部变量表中为a分配了一块内存(用于存储3);
2.将int类型常量4推入(操作数)堆栈;
3.将int类型值4存储到局部变量2中;
4.从局部变量1加载int类型值,即获取局部变量表的值3并将其加载到操作数堆栈中;
5.从局部变量2加载int类型值;
6.添加两个值;
7.(将数字存储在操作数堆栈中吗?)将int类型值7存储在局部变量3中;
8.从局部变量3加载int类型值;
9.返回计算出的值。
以上是方法执行期间内存中的局部变量流。摘要是:
操作数堆栈等效于操作期间用于数据的临时传输站
局部变量表:局部变量的存储空间。它是一个以字长为单位并从0开始计数的数组。int,float,reference和trueAddress类型的值仅占用一项。 byte,short和char类型的值在存储在数组中之前都将转换为int值。 long和double类型的值占据两个连续的项目。索引指向第一个值。
但是,应该注意的是虚拟机直接支持字节,短型和字符型,但是在本地变量表和操作数栈中它被转换为int值。在堆和方法区域,它仍然是原始类型。
操作数堆栈:数据操作的临时空间。类似于局部变量表。唯一的区别是它不是通过索引访问的,而是通过推入和弹出来访问的。
动态链接:它存储该方法的jvm指令代码的内存地址,该地址在运行时动态生成。
对象具有对象标头,类型指针之一指向方法区域的元信息
方法出口:它存储退出该方法并进入下一个方法的程序计数器的值。

Java堆栈结构
异常:如果线程请求的堆栈深度大于虚拟机允许的深度,则将引发StackOverflowError异常;否则,将抛出异常。如果可以动态扩展虚拟机堆栈(当前的大多数Java虚拟机可以动态扩展,但是Java虚拟机机器规范也允许使用固定长度的虚拟机堆栈),并且在足够时将抛出OutOfMemoryError异常扩展期间无法申请内存。
本地方法堆栈
本地方法堆栈实际上与Java虚拟机堆栈非常相似。唯一的区别是Java虚拟机堆栈为Java方法提供服务,而本地方法堆栈为本地方法提供服务。本地方法堆栈中方法的语言,用法和数据结构在虚拟机规范中不是强制性的,因此特定的虚拟机可以自由地实现它。
StackOverflowError和OutOfMemoryError也将被抛出。
线程共享区
方法区
此区域用于存储虚拟机加载的类信息(字段方法的字节码,某些方法的构造函数),常量,静态变量,编译的代码信息等,以及所有字段和方法字节码班上的。除了一些特殊的方法(例如构造函数)外,接口的代码也在这里定义。简而言之,所有定义的方法信息都存储在该区域中。静态变量+常量+类信息(构造方法/接口定义)+运行时常量池都存在。
可以不连续,可以固定大小,可以扩展,或者不选择垃圾收集器。垃圾收集存在于此区域,但发生频率较低。
方法区域是定义,概念,永久生成或元空间是一种实现机制。

OutOfMemoryError:是
运行时常量池
除了类的版本,字段,方法和接口之类的描述性信息外,Class文件还具有一个常量池(Constant Pool Table),该常量池用于存储在编译过程中生成的各种文字和符号引用。加载类后,这部分内容将存储在方法区域中的运行时常量池中。
OutOfMemoryError:是
JAVA堆
堆是Java虚拟机管理的最大内存,它的唯一功能是存储对象实例。几乎所有对象(包括常量池)都将在堆上分配内存。
如果堆中没有内存来完成实例分配,并且无法再扩展堆,则会抛出OutOfMemoryError。
垃圾收集器的主要管理区域。
从垃圾收集的角度来看,该区域分为新一代和旧一代。新一代分为伊甸园空间和幸存者步伐。幸存者区域分为“幸存者来自”。区域和幸存者到区域。如下图所示:

上述区域的大小分配为:
新一代:堆的1/3
晚年:堆的2/3
伊甸园区:新一代的8/10
幸存者来自地区:新一代的1/10
幸存者到地区:新一代的1/10
从内存分配的角度来看,您可以将专用的分配缓冲区划分为多个线程。
对于堆空间,本质是存储对象实例。但是,如何分区只是为了更好地分配和管理对象实例。下一章将介绍按堆空间管理和恢复对象实例。
同时,它在物理上可以是不连续的,但在逻辑上必须是连续的。
以下是JVM内存模型的总体结构:

对象回收过程
本文来自电脑杂谈,转载请注明本文网址:
http://www.pc-fly.com/a/shoujiruanjian/article-370442-1.html
虽然这次中国有点示弱
您厉害
咱们的新舰连这老的都不如