JVM-运行时数据区

目录

一、线程私有

1.程序计数器

2.虚拟机栈

(1)局部变量表

(2)操作数栈

(3)动态链接

(4)方法返回地址

(5)一些附加信息

3.本地方法栈

一些相关问题

二、线程共享区域

1.堆

在堆中创建对象的具体流程

动态年龄判定机制

[Thread Local Allocation Buffer (TLAB)](#Thread Local Allocation Buffer (TLAB))

堆是对象分配的唯一选择吗?

2.方法区

关于常量池

jdk1.8之后方法区的内存结构图

学习过程中的代码及一些注释笔记 (Github)


JVM的运行时数据区学完好久了,今天有时间来做个总结

运行时数据区用来存放Java程序运行所需要的绝大多数数据,程序的执行就是建立在运行时数据区之上的,当类加载器将一个类加载到内存中后,它的全部信息就存放在运行时数据区中。

jdk1.8以后运行时数据区的大体内存结构就如上图所示,其中分作用域范围划分可以分为共享数据区和私有数据区,堆和元空间是所有线程共享的两块区域 ,而本地方法栈、虚拟机栈、程序计数器都是线程私有的,每个线程一份。

关于方法区,方法区是Java虚拟机规范中规定的一个区域

jdk1.8之前它的实现是永久代,存储在堆中

jdk1.8及之后它的实现变为元空间,移出堆中,改用本地内存

一、线程私有

1.程序计数器

虚拟机中的程序计数器主要作用就是指示当前线程下一条指令的地址,保证程序正常的执行,它在内存中只有几个字节的大小,这个区域读取执行效率很高。

之所以每个程序计数器设置成私有就是为了保证不同线程之间的安全切换,从一个线程切换到另一个线程时,程序计数器存储要执行的下一条指令地址,等另一个线程执行结束回来时就能正常继续执行了。这也是并发的基础,多个线程切换时需要程序计数器来存储指令地址,相当于保存现场。

2.虚拟机栈

虚拟机栈也是每个线程私有的,当线程创建的时候就会分配一个虚拟机栈。

虚拟机栈中存储的数据单元是栈帧,每个栈帧对应一个方法,当调用执行一个方法时,就会生成一个对应的栈帧,里面存储对应方法的各种信息等等,当方法执行结束后栈帧出栈。

以上就是虚拟机栈与栈帧的结构图,栈帧中主要有局部变量表、操作数栈、方法返回地址、动态链接和一些附加信息。

(1)局部变量表

局部变量表主要存储方法中的局部变量,包括方法参数列表中的变量和方法中定义的变量,主要是基本数据类型,对象引用和方法返回的数据类型。

局部变量表的基本存储单元是slot(槽),一个为4字节,默认存储下标从0开始

如果是非静态方法,局部变量表索引为0的位置会默认存放一个this变量,指向当前方法对应的对象

像普通的基本数据类型int、float等等都是占用一个slot(像char、byte、boolean这种不足四字节的会被转换成int),而long、double就会占用两个slot。

有一点相信大家在平时编程的时候就遇到过,方法中的局部变量使用前必须赋值!!!否则编译器就会提示错误,无法编译。

(2)操作数栈

操作数栈主要是用来存放和提取各类需要进行运算的数据,同时存放各种计算的中间结果,和普通的栈一一样,只能是后进先出。如果当前被调用的方法有返回值的话,返回结果就会被压入对应的栈帧的虚拟机栈中。

(3)动态链接

动态链接主要作用是将方法中的符号引用转换为直接引用。当类被编译成字节码后方法中的各种引用或者调用函数都会被编译成对应的符号,当被加载到运行时数据区执行这个方法的时候,动态链接就会将这些符号转换为实际的直接引用。

每个栈帧中都有一个引用指向运行时常量池中该栈帧对应方法的地址。这是动态链接的基础,依赖运行时常量池进行符号与实际引用的转换。

这里说的常量池主要是用来存储各类符号的,在元空间中,另有一个字符串常量池,存放各种用到的字符串,在堆中。

(4)方法返回地址

这里存储的是调用该方法时,对应的程序计数器的值,程序计数器中存储的是将要执行的下一条指令的地址,当方法执行结束返回时栈帧出栈,执行引擎读取其中方法返回地址的信息,回到调用该方法时的位置。(我自己画了个示意图如下)

(5)一些附加信息

这里主要是一些虚拟机实现相关的一些小东西

3.本地方法栈

本地方法栈和虚拟机栈作用一样,区别就是虚拟机栈是给Java方法用的,本地方法栈是给本地方法用的。本地方法(NativeMethod)就是非Java编程的方法,Java程序的运行有时候需要一些C++编程的方法来与操作系统打交道,这时候就需要本地方法栈来存储对应的栈帧等信息。

一些相关问题

虚拟机栈有可能会发生溢出,当调用的方法越来越多的时候就会新增许多栈帧,如果虚拟机栈不支持自动增长就会报StackOverflowError

如果支持动态扩容,当扩容内存申请失败时就会发生OOM

二、线程共享区域

1.堆

堆是运行时数据区中空间最大的一块区域,也是所有线程共享的一块区域,一个JVM实例只有一份,几所所有的对象都会在这里分配空间。

堆是一块逻辑上连续的内存区域,物理上可以不连续

对象和数组都是存储在堆中的,当我们在方法中使用new关键字生成一个对象,他就会去方法区找到该类对应的类元信息来确定要分配多少空间,然后在堆中申请一块区域分配给该对象,方法对应的局部变量表存储该对象的引用。

假设我们有一个SimpleHeap类,在方法中new了它的对象后存储逻辑如下

现在的垃圾收集器基于分代收集的思想将堆划分为年轻代和老年代

逻辑上还包括方法区,这里我就不放在一起了!

年轻代和老年代基于字面意思就是代指对象生命周期的长短。

幸存者0和幸存者1这两块区域同一时间只能有一个被使用,被使用的那块可以叫做from,没有被使用的那块就做to,当下一次垃圾回收时就会将数据从from移动到to

在堆中创建对象的具体流程

1.当我们new一个对象时,这个对象或数组首先会被考虑放在年轻代的Eden区中,**如果Eden空间不够,就会触发垃圾回收(Minor GC )**将Eden区中不在使用的对象回收掉,然后将该对象或数组放入Eden中。

2.经过MinorGC幸存下来的对象会被放到幸存者0区中,如果再次经历GC,幸存者0区中幸存下来的对象又会移动到幸存者1区,就这样每次垃圾回收后幸存者from中的幸存者都会移动到幸存者to中

(从from到to时如果空间不足会触发MinorGC)

幸存者区的移动是由次数上限的,当一个对象移动到了15次后,就会被放到老年代

这个移动次数(threshold)可以自己设置,默认是15次

不过有个动态年龄判定,如下:

动态年龄判定机制

如果 Survivor 区中相同年龄的所有对象大小的总和超过 Survivor 区的一半,年龄大于或等于该年龄的对象将直接晋升到老年代。

例如:Survivor 区中有多个对象年龄为 5,总大小超过 Survivor 区的 50%,则年龄 ≥5 的对象直接进入老年代。

当老年代空间不足时会触发Major GC,对老年代进行垃圾回收,同时可能会伴随年轻代的回收(Minor GC)这样也就是所谓的Full GC,当老年代的空间也不足的时候就会报错OOM

从上面这个对象分配流程就可以看出新生代的内存回收是很频繁的,老年代的回收相对来说频率较小。

Thread Local Allocation Buffer (TLAB)

前面说到堆是所有线程共享的一块区域,涉及到多线程又要考虑线程安全问题,如果对内存加上锁,无疑会拖累程序的执行效率。所以现在的虚拟机一般都会给每个线程预先在堆中分配一部分空间,短时间内对象在这一块区域中分配就可以了,这样为每个线程在堆中分配一部分区域,就不用时时刻刻的加锁了,只有在某个线程的TLAB内存不够要扩容时加一次锁即可

堆是对象分配的唯一选择吗?

这就要介绍一下逃逸分析技术了,所谓的逃逸分析就是分析方法中的一个局部对象是否在方法外使用,如果经过分析某个对象只在本方法中使用,那么这个对象就能被拆分成多个基本变量(标量)存储在局部变量表中,就不用分配在堆中了。

2.方法区

方法区的实现在jdk1.8之前都是永久代,存储在堆中

jdk1.8之后,方法区由元空间实现,不在存储在堆中,转为使用本地内存

方法区主要存储类的元数据信息,譬如当使用new关键字生成一个对象时,会以方法区中该对象元数据信息为依据,为对象在堆中分配内存。

虚拟机栈、堆和方法区的关系如上图,它们之间是密切合作的,共同协作来作为程序运行的基础,当在堆中生成一个对象时,对象中还包含一个指向方法区中该对象类型信息的指针。

深入理解虚拟机这本书中这样描述方法区的功能:它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等。

方法区的具体细节过于繁琐,这里不在说了。

关于常量池

运行时常量池是方法区的一部分

常量池是Class文件的一部分,用来存放编译时生成的各种字面量和符号引用,当类加载器加载这个类时,会将该类的常量池放到方法区的运行时常量池中。

在jdk1.7以后原本处于运行时常量池中的字符串常量池被移出,放到了堆中。

jdk1.8之后方法区的内存结构图

学习过程中的代码及一些注释笔记 (Github)

lyjs6666/JVMDemo: 学习JVM时的一些测试代码和笔记

到这里运行时数据区已经大体上梳理了一遍,手打了两个多小时,麻烦刷到的家人们点个赞,Thankyou!

相关推荐
孤寂大仙v1 小时前
【Linux笔记】——简单实习一个日志项目
java·linux·笔记
笑鸿的学习笔记3 小时前
虚幻引擎5-Unreal Engine笔记之摄像头camera
笔记·ue5·虚幻
JJ1M83 小时前
MYSQL笔记
数据库·笔记·mysql
IT从业者张某某3 小时前
信奥赛-刷题笔记-栈篇-T2-P1165日志分析0519
android·java·笔记
逼子格4 小时前
硬件工程师笔记——三极管Multisim电路仿真实验汇总
笔记·嵌入式硬件·硬件工程·硬件工程师·三极管·硬件工程师真题·multisim电路仿真
breaksoftware4 小时前
51单片机编程学习笔记——无源蜂鸣器演奏《祝你生日快乐》
笔记·学习·51单片机
huangyuchi.4 小时前
【Linux】初见,基础指令
linux·运维·服务器·笔记·开发工具·指令·基础指令
huangyuchi.5 小时前
【Linux】初见,基础指令(续)
linux·运维·服务器·开发语言·笔记·指令·linux指令
jerry6095 小时前
LLM笔记(九)KV缓存调研
笔记·深度学习·学习·缓存·transformer
Suckerbin6 小时前
Secarmy Village: Grayhat Conference靶场
笔记·安全·网络安全