《深入理解Java虚拟机》(第二版)学习1:JVM的内存划分
运行时数据区
先来一张图描述一下 JVM 的内存划分
PS:自己画的,丑是难免丑了点…
程序计数器(Program Counter Register)
程序计数器(Program Counter Register)是一块较小的内存空间,它可以看做是当前线程所执行的字节码的行号指示器。
特点:
- 线程私有,各个线程之间的计数器互不影响;
- 它是在Java虚拟机规范中唯一没有规定任何内存溢出(OutOfMemoryError)情况的区域。
使用:如果线程当前执行的是Java方法,则计数器记录的是当前执行的虚拟机字节码指令的地址;如果当前执行的是 Native 方法,则计数器为空(Undefined)。
用途:使线程切换之后能恢复到正确的执行位置。
虚拟机栈(Virtual Machine Stack)
虚拟机栈描述的是 Java 方法执行的内存模型,内部的基本单位是栈帧,一个栈帧对应着一个方法,每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
特点:
- 线程私有,生命周期与线程相同
虚拟机栈有两种异常情况:
- 栈溢出(StackOverflowError):线程请求的栈深度大于虚拟机所允许的深度,多出现于方法递归中;
- 内存溢出(OutOfMemoryError)
栈帧(Stack Frame)
栈帧是用于支持虚拟机进行方法调用和方法执行的数据结构,是虚拟机栈的的栈元素。
如上图,栈帧内部包括局部变量表、操作数栈、动态连接、方法返回地址和额外信息等内容。
在活动栈中,只有位于栈顶的栈帧才是有效的,称为当前栈帧(Current Stack Frame),与这个栈帧相关联的方法称为当前方法(Current Method),虚拟机执行引擎的所有字节码指令都是针对当前栈帧进行操作的。
局部变量表(Local Variable Table)
局部变量表(Local Variable Table)是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。
局部变量表的最大容量在编译期间就写入到 Code 属性的 max_locals 数据项中(编译期间确定大小,不存在溢出现象)。
局部变量表的基本单位
局部变量表的容量以变量槽(Variable Slot,简称 Slot)为最小单位,每一个 Slot 都能存放一个 boolean、byte、char、short、int、float、reference(对象实例的引用)或者 returnAddress 类型的数据,它们在 Java 中都占用小于或者等于32位的内存长度。
对书上的“只要保证即使在64位机中使用64位的物理内存空间去实现一个Slot,虚拟机仍要使用对齐和补白的手段让 Slot 在外观上看起来与32位虚拟机中的一致”这句话存疑。
照这句话的意思岂不是64位 JVM 里 int 虽然占了64位,但剩下32位是一堆空白?
在网上暂时没找到这个问题的答案,先余着。
对于64位的数据类型,虚拟机会以高位对齐的方式为其分配两个连续的 Slot 空间。Java 中明确的(reference 类型可能是32位的也可能是64位的)64位的数据类型只有 long 和 double 两种,这里引出了一个问题,就是** long 和 double 的非原子性协定**,这个我们留到后面再说。
对书上的“reference类型可能是32位的也可能是64位的”存疑,那如何判断reference是32位的还是64位的呢?
也暂时没找到这个的答案,余着。
局部变量表的使用
虚拟机通过索引定位的方式使用局部变量变,索引范围从0开始至局部变量表最大的 Slot 数量。如果访问的是32位数据类型的变量,索引 n 就代表了使用第 n 个 Slot ,如果是64位数据类型的变量,则说明会同时使用 n 和 n+1 两个 Slot 。
对于两个相邻的共同存放一个64位数据的两个 Slot ,不允许采用任何方式单独访问其中的某一个!!!
在方法执行时,虚拟机使用局部变量表完成参数值到参数变量列表的传递过程,如果执行的是实例方法(非 static 方法),那局部变量表中第0位索引的 Slot 默认用于传递方法所属对象实例的引用,在方法中可以通过 this 关键字来访问到这个隐含的参数。其余参数则按照参数表顺序排列,占用从1开始的局部变量 Slot ,参数表分配完之后,再根据方法体内部定义的变量顺序和作用域分配其余的 Slot 。
那如果是类方法(static方法)呢?那局部变量表中第0位索引的 Slot 值是多少?
如果是类方法,那局部变量表中第0位索引的 Slot 值是 0 ,这从虚拟机层面解释了为什么在类方法里面使用不了this关键字。
Slot 的复用机制
为了尽量节省栈帧空间,局部变量表中的 Slot 是可以重用的,方法体中定义的变量,其作用域并不一定会覆盖整个方法体,如果当前字节码PC计数器的值已经超过某个变量的作用域,那这个变量对应的 Slot 就可以交给其他变量使用。
Slot 复用的副作用:某些情况下,Slot 的复用会影响垃圾回收。
例:
/*** @author 小关同学* @create 2021/9/26*/
public class Test1 {public static void main(String[] args) throws Exception{{byte[]placeholder = new byte[64 * 1024 *1024];}System.gc();}
}
内存数据:
[GC 69468K->66368K(249344K), 0.0012025 secs]
[Full GC 66368K->66091K(249344K), 0.0098479 secs]
我们可以看到 placeholder 变量并没有被回收,这是因为原来 placeholder 变量占用 Slot还没有被其他变量所复用,所以作为GC Roots一部分的局部变量表仍然保持着对它的关联。
修改过后
/*** @author 小关同学* @create 2021/9/26*/
public class Test1 {public static void main(String[] args) throws Exception{{byte[]placeholder = new byte[64 * 1024 *1024];}int i = 0;System.gc();}
}
内存数据:
[GC 69468K->66320K(249344K), 0.0011144 secs]
[Full GC 66320K->555K(249344K), 0.0100487 secs]
我们可以明显看到内存被回收了,因为变量 i 对变量 placeholder 原先占用的 Slot 进行复用,所以变量 placeholder 占用的内存被回收了。
操作数栈(Operand Stack)
操作数栈(Operand Stack)也叫操作栈,是一个后进先出(Last In First Out,LIFO)栈。
同局部变量表一样,操作数栈的最大深度也在编译期间就写入到 Code 属性的 max_stacks 数据项中(编译期间确定大小,不存在溢出现象)。
32位数据类型所占的栈容量为1,64位数据类型所占容量为2。
当一个方法刚刚开始执行的时候,这个方法的操作数栈是空的,在方法执行的过程中,会有各种字节码指令往操作数栈中写入和提取内容,也就是出栈 / 入栈操作。
Java虚拟机的执行引擎称为“基于栈的执行引擎”,其中所指的“栈”就是操作数栈。
操作数栈中元素的数据类型必须与字节码指令的序列严格匹配,在编译程序代码的时候,编译器要严格保证这一点,在类校验阶段的数据流分析中还要再次验证这一点。
现代虚拟机一般会对操作数栈进行一些优化处理,如下图:
如上图,虚拟机会令两个栈帧发生部分重叠,让下面栈帧的部分操作数栈与上面栈帧的部分局部变量表重叠在一起(主要体现在方法中有参数传递的情况),这样在进行方法调用时就可以共用一部分数据,无需进行额外的参数复制传递。
动态链接(Dynamic Linking)
动态链接主要就是指向运行时常量池的方法引用。每个栈帧都包含一个指向运行时常量池中该栈所属方法的引用,持有这个引用是为了支持方法调用中的动态链接(Dynamic Linking)。
动态链接跟方法调用息息相关,关于方法调用我们后面再讲。
方法返回地址(Return Address)
当一个方法开始执行以后,只有两种方式可以退出这个方法。
第一种是执行引擎遇到任意一个方法返回的字节码指令,这种退出方法的方式称为正常完成出口(Normal Method Invocation Completion)。
第二种退出方式是,在方法执行过程中遇到异常,并且这个异常没有在方法内部得到处理(在本地方法的异常表中没有搜索到匹配的异常处理器),就会导致方法退出,这种退出方法的方式称为异常完成出口(Abrupt Method Invocation Completion)。一个方法使用异常完成出口的方式退出,是不会给它的上层调用者产生任何返回值的。
无论采取哪种退出方式,在方法退出之后,都需要返回到方法被调用的位置,程序才能继续执行。
方法正常退出时,调用者的 PC 计数器的值可以作为返回地址,栈帧中可能会保存这个计数器值。
方法异常退出时,返回地址是要通过异常处理器表来确定的,栈帧中一般不会保存这部分信息。
方法返回地址和PC计数器的区别:
- 程序计数器指的是处理器在来回切换线程执行的时候,记录当前线程执行到什么地方的;
- 方法的返回地址是记录当前方法指令执行完之后,下一步要执行的方法指令的地址。
本质上:方法的退出就是当前栈帧出栈的过程。此时,需要恢复上层方法的局部变量表、操作数栈、将返回值压调用者栈帧的操作数栈、设置 PC 计数器值等,让调用者方法继续执行下去。
附加信息
这部分信息取决于虚拟机的具体实现,如:调试相关的信息。
本地方法栈(Native Method Stack)
本地方法栈作用和虚拟机栈类似,区别在虚拟机栈是为 Java 方法服务的,而本地方法栈则是为虚拟机使用到的 Native 方法服务。
与虚拟机栈一样,本地方法栈也会抛出 StackOverflowError 和 OutOfMemoryError 异常。
Java堆(Java Heap)
Java 堆是虚拟机所管理的内存中的最大的一块,随虚拟机启动而被创建。它的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。
注意前面这个“几乎”,原来所有对象实例的确都在堆上分配的,但是随着 JIT 编译器的发展和逃逸分析技术逐渐成熟,栈上分配、标量替换优化技术会导致一些变化,一些对象不会再被分配到堆上。
特点:线程共享;
Java堆是垃圾收集器管理的主要区域,由于现在的垃圾收集器基本采用分代算法,所以从内存回收的角度Java堆也可以被细分为:新生代和老年代;再细致一点的有 Eden 空间、From Survivor 空间、To Survivor 空间等。从内存分配的角度来看,线程共享的 Java 堆中可能划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB)。
Java堆可以处于物理上不连续的内存空间中,只要逻辑上使连续的即可。
方法区(Method Area)
方法区(Method Area)用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
特点:线程共享;
在 Hotspot 虚拟机上,方法区也被称为 “永久代”(Permanent Generation),原因是 Hotspot 虚拟机的设计团队把 GC 分代收集扩展至方法区,这样垃圾收集器就可以像管理 Java 堆一样管理这部分内存(现在看来不是个好主意),省去专门为方法区编写内存管理代码的工作(感觉像是在偷懒)。
注意:其他虚拟机上是不存在永久代这个概念的,这个是 Hotspot 虚拟机特有的。
方法区的内存回收目标主要是针对常量池的回收和对类型的卸载。一般来说,方法区的回收率并不高,尤其是类型的卸载,条件相当苛刻。
当方法区无法满足内存分配需求时,将抛出 OutOfMemoryError 异常。
运行时常量池(Runtime Constant Pool)
运行时常量池(Runtime Constant Pool)是方法区的一部分。Class 文件中有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量(Literal)和符号引用(Symbolic References),字面量相当于 Java 语言层面常量的概念,如文本字符串,声明为 final 的常量值等,符号引用则属于编译原理方面的概念,包括了如下三种类型的常量:
- 类和接口的全限定名
- 字段名称和描述符
- 方法名称和描述符
这部分内容在类加载之后就进入到方法区的运行时常量池中存放。一般来说,除了保存 Class 文件中描述的符号引用外,还会把翻译出来的直接引用也存储在运行时常量池中。
运行时常量池相对于 Class 文件常量池的另一个重要特征是具备动态性,Java 语言不要求常量一定是编译期才能产生,运行期间也可能将新的常量放入池中,如 String 类的 intern()方法。
当常量池无法再申请到内存时会抛出 OutOfMemoryError 异常。
直接内存(Direct Memory)
直接内存(Direct Memory)并不是 Java 虚拟机规范中定义的内存区域。在 JDK 1.4 中新加入了 NIO(New Input / Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的 I/O 方式,它可以使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样避免了在 Java 堆和 Native 堆中来回复制数据,在一些场景中提高性能。
这部分区域虽然使用的是本机直接内存,但是也会出现 OutOfMemoryError 异常。
直接内存的回收
需注意堆外内存并不直接控制于JVM,这些内存只有在 DirectByteBuffer 回收掉之后才有机会被回收,而 Young GC 的时候只会将年轻代里不可达的 DirectByteBuffer 对象及其直接内存回收,如果这些对象大部分都晋升到了年老代,那么只能等到 Full GC 的时候才能彻底地回收 DirectByteBuffer 对象及其关联的堆外内存。因此,堆外内存的回收依赖于 Full GC
Full GC 一般发生在老年代垃圾回收或者代码调用 System.gc 的时候,依靠老年代垃圾回收触发 Full GC,进而实现堆外内存的回收显然具有太大的不确定性。如果老年代一直不进行垃圾回收,那么堆外内存就得不到回收,机器的物理内存可能就会被慢慢耗光。为了避免这种情况发生,可以通过参数 -XX:MaxDirectMemorySize 来指定最大的直接内存大小,当其使用达到了阈值的时候将调用 System.gc 来做一次 Full GC,从而完成可控的堆外内存回收。这样做的问题在于,堆外内存的回收依赖于代码调用 System.gc,而 JVM 参数 -XX:+DisableExplicitGC 会导致 System.gc 等于一个空函数,根本不会触发 Full GC,这样在使用 Netty 等 NIO 框架时需注意是否会因为这个参数导致直接内存的泄露。
-XX:MaxDirectMemorySize 参数没有指定的话,那么根据 directMemory = Runtime.getRuntime().maxMemory(),最大直接内存的值和堆内存大小差不多
PS:也可以到我的个人博客查看更多内容
个人博客地址:小关同学的博客
相关文章:

下一个亿万市场:企业级SaaS服务谁能独领风骚
注:SaaS是Software-as-a-Service(软件即服务)的简称,一种完全创新的软件应用模式,简单来说SaaS即为提供商基于互联网为企业提供软件服务。 对中小型企业来说:SaaS是采用先进技术,它消除了企业购买、构建和维护基础设…

inline-block在ie6中的经典bug
众所周知,给元素设置 inline-block ,可以让ie下的元素出发layout:1。 但是,当给元素设置 inline-block 后,在另外一个class 样式(非设置inline-block的class样式)重置为inline或者block。对于ie6下…

各系统QT安装ROS后不显示src问题
刚创建的文件显示如下: 接下来修改这里: 将对勾去掉 之后就可以正常显示,可以添加自己的工作空间以及功能包了

使用脚本完成AutoCAD自动化任务课程
The complete AutoCAD Automation tasks course Using Script MP4 |视频:h264,1280720 |音频:AAC,44.1 KHz,2 Ch 语言:英语中英文字幕(根据原英文字幕机译更准确) |时长:42节课(4h 25m) |大小:3.35 GB 含课…

学生管理系统(C语言版)
学生管理系统 这个是大一学习C语言的时候做的一个小项目,代码部分基本都是自己一人完成,没用到什么高大上的技术,在图形化方面用了EasyX(一个C的图形库),其他都是C语言的基础内容。 项目介绍 项目总体功…

jsp,图片显示
问题:jsp中显示项目中image文件夹中的图片 1,项目中image文件夹中有对应的图片 2,<img ,src"/项目名/image/图片名.jpg">,用其他变量获取这个路径也可以,但本质还是这个路径 3,对于jsp和HTML等文件的修…

Java基础系列——IO流
2019独角兽企业重金招聘Python工程师标准>>> ---恢复内容开始--- Java对数据的操作都是通过流的方式,数据的输入和输出是相对内存来说的,将外设的数据读到内存:输入流;将内存的数据写到外设:输出流。 流按…

Visual Studio Code / Roboware Studio调整字体大小,跳转等操作
调大字体:Ctrl 调小字体:Ctrl _- 跳转:F12 跳转返回:Ctrl Alt _- 打开内部终端:Ctrl ~

C4D和Redshift:2D矢量到三维渲染 Cinema 4D and Redshift: 2D vector to 3D render
C4D和Redshift:2D矢量到三维渲染 时长:1h 4m |视频:. MP4 1280720,30 fps(r) |音频:AAC,48000 Hz,2ch |大小解压后:633 MB 含课程文件 语言:英语中英文字幕(机译) 本课程涵盖了诸如将矢量转换为3d场景的主题。您将了解哪些数据可以…

《深入理解Java虚拟机》(第二版)学习2:垃圾收集算法
对象存活判断 在对堆进行回收之前虚拟机需要判断这些对象中那些是“存活”的。 引用计数算法(Reference Counting) 原理:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加一;当引用失效…

Android 知识杂记(MVP模式)
MVP的模式在于将原来activity中业务逻辑的部分剥离出来,代码示例如下: Accountpublic class Account {private String mUsername;private String mPassword;public String getmUsername() {return mUsername;}public void setmUsername(String mUsername…

玩转百度即用API(2)——身份证查询
2019独角兽企业重金招聘Python工程师标准>>> 第二个即用API,身份证查询 示例代码: #-*- coding: utf-8 -*- #version:0.1 #note:该即用API接口简单,输入身份证只能查到3个信息:大概地址,性别&am…

解决Ubuntu环境下不能QT不能输入中文
参考链接: https://blog.csdn.net/baidu_33850454/article/details/81212026?utm_mediumdistribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase&depth_1-utm_sourcedistribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2…

UE5虚幻引擎5中的实时特效学习 Introduction to real time FX in Unreal Engine 5
MP4 |视频:h264,1280720 |音频:AAC,44.1 KHz,2 Ch 语言:英语中英文字幕(根据原英文字幕机译更准确) |时长:40节课(3h 36m) |大小解压后:2.65 GB 含课程文件 从零开始使用尼亚加拉在虚幻引擎中学习实时外汇…

《深入理解Java虚拟机》(第二版)学习3:垃圾收集器
垃圾收集器 如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。 我们这里讨论的收集器主要是基于JDK 1.7 Update 14之后的 Hotspot VM 。 Serial 收集器 Serial 收集器是最基本、发展历史最悠久的收集器,曾经(在 …

2016寒假训练3
题目链接 A CodeForces 362A Two Semiknights Meet 题意:在一个棋盘中有两个定义了特殊走法的棋子,同时移动他们,问是否会相遇(只能在合法的位置)。 做法:直接暴力dfs处理出这两个棋子到达各个位置的时间&a…

2015化妆品行业如何进行微信公众号营销
对比其他行业发展特点发现化妆品行业具有以下特点: 1、化妆品行业呈现节日、电商促销影响明显、口碑评价关注度高、女性网民占比高冲动消费多、整体用户年轻化等特点。 2、化妆品行业即使在各行业发展低迷期依然保持良好发展势头。 3、化妆品商家的顾客回头率高。 4…

在新建好的ROS空间里面添加功能包
第一步:创建功能包 cd catkin_ws打开src ~/catkin_ws/src新建文件夹名字 catkin_create_pkg (文件加名字) roscpp rospy std_msgs打开新建文件夹中的src ~/catkin_ws/src/(新建文件夹名字)/src输入: gedit 文件.cpp返回工作空间: 执行catkin build 编译后即可执行

Revit的Enscape基本培训(2021) Enscape Essential Training for Revit (2021)
MP4 |视频:h264,1280720 |音频:AAC,44.1 KHz,2 Ch 语言:英语中英文字幕(根据原英文字幕机译更准确) |时长:2h 53m |大小解压后:2.23 GB 含课程练习文件 如果您使用Revit,您可能需要学习Enscape&…

设计模式学习2:单例模式
单例模式 所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法)。 比如Hibernate的SessionFactory,它充当数据存储源的代理…

数据库插入时,标识列插入显式值
当 IDENTITY_INSERT 设置为 OFF 时,不能为表 CU_GiftExchange 中的标识列插入显式值。SET IDENTITY_INSERT [dbo].[CU_GiftExchange] ONGO转载于:https://blog.51cto.com/hezun/1631849

Ubuntu终端显示文本让选择确定,OK等等
问题1:首先按下"TAB"键,会看到选中文本"确定","OK"然后按下回车键,即可!

Unity安卓游戏开发:打造7款2D 3D游戏 Unity Android Game Development : Build 7 2D 3D Games
流派:电子学习| MP4 |视频:h264,1280720 |音频:AAC,44.1 KHz 语言:英语中英文字幕(根据原英文字幕机译更准确) |大小:15.4 GB |时长:32h 55m Unity游戏开发与设计,用C# & Unity学习Unity安卓游戏开发(更…

唯一索引和普通索引的选择
前言:最近在研究阿里的开发手册中关于 MySQL 的一些规定,所以来记录一下学习中的心得 唯一索引和普通索引的选择 【强制】业务上具有唯一特性的字段,即使是组合字段,也必须建成唯一索引。 说明:不要以为唯一索引影响了…

Springboot整合HBase——大数据技术之HBase2.x
Apache HBase 是以hdfs为数据存储的,一种分布式、可扩展的noSql数据库。是一个高可靠性、高性能、面向列、可伸缩的分布式存储系统,利用HBase技术可在廉价PC Server上搭建起大规模结构化存储集群。HBase使用与BigTable(BigTable是一个稀疏的、分布式的、持久化的多维排序map)非常相似的数据模型。用户将数据行存储在带标签的表中。数据行具有可排序的键和任意数量的列。该表存储稀疏,因此如果用户喜欢,同一表中的行可以具有疯狂变化的列。

一个合格的Java选手必须要掌握的并发锁知识
Java内置锁:基于Java语法层面(关键词)实现的锁,主要是根据Java语义来实现,最典型的应用就是synchronized。Java显式锁:基于JDK层面实现的锁,主要是根据基于Lock接口和ReadWriteLock接口,以及统一的AQS基础同步器等来实现,最典型的有ReentrantLock。使用方式:synchronized关键字互斥锁主要有作用于对象方法上面,作用于类静态方法上面,作用于对象方法里面,作用于类静态方法里面等4种方式。

终于有人把Web 3.0和元宇宙讲明白了
分散的数据网络使个人数据(例如个人的健康数据、农民的作物数据或汽车的位置和性能数据)出售或交换成为可能,与此同时,不会失去对数据的所有权控制、放弃数据隐私或依赖第三方平台来管理数据。Web 3.0的目标是在创作者经济中取得更好的平衡。互联网第二次迭代(Web 2.0)的缺陷,加上公有区块链技术的诞生,帮助我们朝着更加去中心化的Web 3.0 迈进,元宇宙和更广泛的去中心化网络都是关于现实世界和虚拟世界的融合。此时的网络中不再是静态内容,而是动态的内容,用户现在可以与发布在网络上的内容进行交互。

ubuntu下安装 python 常用软件
1、用于科学计算的常用包: sudo apt-get install python-numpy python-scipy python-matplotlib ipython ipython-notebook python-pandas python-sympy python-nose 包括,numpy, scipy, matplotlib, ipython, ipython-notebook, pandas, sympy, nose 2、…

《团队项目开发之三对一维环形数组的求解》
《团队项目开发之三对一维环形数组的求解》 设计思想:通过把数组的长度扩大为原来的一倍,相当于新数组是由对原来的数组重复了一遍后而组成的,这样保证了数组以环状的形式,按照数组中每个数字的位序依次对它们可能形成的最大子数组…

PX4修改线程内存大小
当编译时出现错误: 在CMakeLists.txt文件中修改内存大小 px4_add_module(#下面添加文件夹名字MODULE modules__position_control#下面添加线程名字MAIN a#线程内存大小STACK_MAIN 4000SRCS#添加文件夹里面.cpp文件main.cppDEPENDS)修改后,再次编译就不…