JVM_01 内存结构篇
1|0JVM(Java Virtual Machine)
1|1一、前言
1、什么是 JVM ?
1、定义:
- Java Virtual Machine ,Java 程序的运行环境(Java 二进制字节码的运行环境)。
2、好处:
- 一次编译,处处执行
- 自动的内存管理,垃圾回收机制
- 数组下标越界检查
3、比较:
JVM、JRE、JDK 的关系如下图所示
2、学习 JVM 有什么用?
- 面试必备
- 中高级程序员必备
- 想走的长远,就需要懂原理,比如:自动装箱、自动拆箱是怎么实现的,反射是怎么实现的,垃圾回收机制是怎么回事等待,JVM 是必须掌握的。
3、常见的 JVM
这里需要重点了解:JVM是一套规范,而我们也可以遵守这套规范实现自己的JVM(有能力的前提下!)
4、JVM整体预览
先做一个整体预览,然后逐个击破!
1|2二、内存结构
1、程序计数器
1.1、概述:
JVM 中的程序计数器(Program Counter Register)有的时候也被称作PC寄存器,为了避免混淆这里解释一下,这里,并非是广义上所指的
物理寄存器,或许将其翻译为 PC 计数器(或指令计数器)更加贴切(也称为程序钩子),并且也不容易引起误会。
JVM 中 PC 寄存器是堆物理 PC 寄存器的一种抽象模拟。
1.2、特点:
- 是线程私有的
- 不会存在内存溢出(OOM)
1.3、作用:
PC 寄存器用来存储指向下一条指令的地址,即将要执行的指令代码。由执行引擎读取下一条指令。
2、虚拟机栈
栈帧:每个方法执行的时候需要的内存空间,其中占用空间的有(参数、局部变量、返回地址等)
当我们调用一个方法的时候,会在虚拟机栈当中给他开辟一个栈帧大小的空间,然后让栈帧入栈,方法执行完毕栈帧出栈!
定义:
- 每个线程运行需要的内存空间,称为虚拟机栈
- 每个栈由多个栈帧(Frame)组成,对应着每次调用方法时所占用的内存
- 每个线程只能有一个活动栈帧,对应着当前正在执行的方法
相关问题:
1、垃圾回收是否涉及占栈内存 ?
答: 不会,因为我们的栈中的栈帧空间是用完就释放的!
2、栈内存的分配越大越好吗?
答:不是,由于我们的物理内存是有限的,一个栈的内存越大,能开线程越少,并发降低,其作用仅仅是增加了方法的递归调用
3、方法内的局部变量是否为线程安全的?
答:是的,局部变量是在线程私有的,不会与其他线程共享,不会产生线程安全问题(前提是局部变量没有逃离方法的作用范围!)
2.1、栈内存溢出
栈帧过大、过多、或者第三方类库操作,都有可能造成栈内存溢出 java.lang.stackOverflowError ,使用 -Xss256k 指定栈内存大小!
2.2、线程运行诊断
案例一:cpu 占用过多
解决方法:Linux 环境下运行某些程序的时候,可能导致 CPU 的占用过高,这时需要定位占用 CPU 过高的线程
- top 命令,查看是哪个进程占用 CPU 过高
- ps H -eo pid (进程id), tid(线程id),%cpu | grep : 刚才通过 top 查到的进程号 通过 ps 命令进一步查看是哪个线程占用 CPU 过高
- jstack 进程 id : 通过查看进程中的线程的 nid ,刚才通过 ps 命令看到的 tid 来对比定位,注意 jstack 查找出的线程 id 是 16 进制的,需要转换。
案例二:长时间运行未出现结果(可能出现死锁)
解决方法:使用jstack工具 + 进程号,就会定位到死锁问题!
3、本地方法栈
一些带有 native 关键字的方法就是需要 JAVA 去调用本地的C或者C++方法,因为 JAVA 有时候没法直接和操作系统底层交互,所以需要用到本地方法栈,服务于带 native 关键字的方法。
4、堆
Heap 堆
- 通过new关键字创建的对象都会被放在堆内存
特点 :
- 它是线程共享,堆内存中的对象都需要考虑线程安全问题
- 有垃圾回收机制
4.1、堆内存溢出
发生堆内存溢出会抛出 java.lang.OutOfMemoryError
可以使用 -Xmx8m
来指定堆内存大小。将堆内存调小就可以便于我们排查问题!
4.2、堆内存诊断
1、jps工具:查看当前系统中有哪些java进程 : jps
2、jmap工具 :查看堆内存的占用情况 : jmap - heap + 进程id
3、jconsole工具 : 图形化界面多功能的检测工具,可以连续监测
4、jvisualvm工具 : 相对jconsole更加强大的可视化工具
案例 : 垃圾回收后,内存占用仍然很高!
使用jvisualvm,启动可视化工具检测我们的虚拟机内存,找到HeapDump,对堆内存进行一个快照(内存转储),然后获取并且分析此时详细数据,定位原因!
5、方法区
首先看一个定义:我们的JVM规范中对于方法区(Method Area)的定义:
看一下内存结构
5.1、方法区内存溢出
-
1.8 之前会导致永久代内存溢出
-
1.8 之后会导致元空间内存溢出
5.2、运行时常量池
首先先理解什么是常量池
1、我们可以通过堆一下代码得字节码文件进行反编译,拿到我们的反编译信息
2、找到字节码文件执行
3、可以得出我们得字节码文件,反编译后的结果
然后我们对字节码文件进行说明
由此可以总结得出:常量池就是一张表,虚拟机指令根据这张常量表,去找到要执行的类名、方法名、参数类型、字面量等信息
我们一个类的字节码文件包含一个常量池,多个类一起运行的情况下,会将每个类的常量池表汇聚在一起,放在我们的运行时常量池
其中也不会是#1 #2 #3 这种地址,而是真实的内存地址!
5.3、串池 StringTable
- 常量池中的字符串仅是符号,只有在被用到时才会转化为对象【懒加载,只有当JVM指令用到的时候才会创建对象】
- 利用串池的机制,来避免重复创建字符串对象 【主要是因为串池是HashTable实现的,底层是Hash表不可扩容】
- 字符串变量的拼接原理:StringBuilder(1.8)
- 字符串常量的拼接原理是编译期优化
- 可以使用intern方法,主动将串池中还没有的字符串对象放入串池子!
案例一
案例二
intern方法1.7以后
intern方法1.6以前
5.4、StringTable 的位置
- jdk1.6 StringTable 位置是在永久代中
- jdk1.8 StringTable 位置是在堆中
5.5、StringTable 垃圾回收
-Xmx10m
指定堆内存大小-XX:+PrintStringTableStatistics
打印字符串常量池信息-XX:+PrintGCDetails
打印GC信息-verbose
:gc 打印 gc 的次数,耗费时间等信息``
5.6、StringTable 性能调优
- 因为StringTable是由HashTable实现的,所以可以适当增加HashTable桶的个数,来减少字符串放入串池所需要的时间
- 考虑是否需要将字符串对象入池,可以通过 intern 方法减少重复入池
6、直接内存
Direct Memory
- 常见于 NIO 操作时,用于数据缓冲区
- 分配回收成本较高,但读写性能高
- 不受 JVM 内存回收管理
6.1、使用直接内存的好处
文件读写流程:
因为 java 不能直接操作文件管理,需要切换到内核态,使用本地方法进行操作,然后读取磁盘文件,会在系统内存中创建一个缓冲区,将数据读到系统缓冲区, 然后在将系统缓冲区数据,复制到 java 堆内存中。缺点是数据存储了两份,在系统内存中有一份,java 堆中有一份,造成了不必要的复制。
使用了 DirectBuffer 文件读取流程
直接内存是操作系统和 Java 代码都可以访问的一块区域,无需将代码从系统内存复制到 Java 堆内存,从而提高了效率。
减少了不必要的复制操作
6.2、直接内存回收原理
直接内存的回收不是通过 JVM 的垃圾回收来释放的,,而是通过unsafe.freeMemory 来手动释放。
直接内存的回收机制总结
使用了 Unsafe 类来完成直接内存的分配回收,回收需要主动调用freeMemory 方法ByteBuffer 的实现内部使用了 Cleaner(虚引用)来检测 ByteBuffer 。一旦ByteBuffer 被垃圾回收,那么会由 ReferenceHandler(守护线程) 来调用 Cleaner 的 clean 方法freeMemory 来释放内存
简述:一但BtyeBuffer这个这个java类被回收,就会将我们的直接内存释放!
然而我们一般用 jvm 调优时,会加上下面的参数:
意思就是禁止我们手动的 GC,比如手动 System.gc() 无效,它是一种 full gc,会回收新生代、老年代,会造成程序执行的时间比较长。
所以我们就通过 unsafe 对象主动的调用 freeMemory 的方式释放内存。
)
__EOF__

本文链接:https://www.cnblogs.com/qxsong/p/15837248.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)