【JVM】Java虚拟机(一)——内存结构
目录
一、简介
二、程序计数器
三、虚拟机栈
栈帧结构:
特点:
四、本地方法栈
特点:
五、堆
堆结构:
特点:
对象分配过程:
六、方法区
方法区结构:
特点:
运行时常量池
七、StringTable
(一)StringTable 核心概念
(二)核心特性与机制
1. 字符串唯一性(Intern机制)
2. 延迟加载
3. 不可变性
(三)内存位置演变
(四)字符串创建流程
(五)性能优化建议
(六)示例:StringTable 与 GC 交互
(七)总结对比表
(八)面试题
八、直接内存
(一) 基本概念
(二)与堆内存的对比
(三)核心优势
(四)内存分配与回收
(五)潜在问题
(六)最佳实践
(七)典型应用场景
(八)总结
(九)分配和回收原理
九、JVM内存整体结构图
一、简介
Java虚拟机(JVM)在执行Java程序时会将内存划分为不同的区域,每个区域有特定的用途和生命周期。JVM内存结构主要分为线程私有区域(程序计数器、虚拟机栈、本地方法栈)和线程共享区域(堆、方法区)。下面将详细解析每个部分的结构和功能。
二、程序计数器
程序计数器(Program Counter Register) 是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。
作用:记住下一条jvm指令的执行地址
特点:
线程私有:每个线程都有独立的程序计数器
唯一不会OOM的区域:没有内存溢出问题
三、虚拟机栈
虚拟机栈(Java Virtual Machine Stacks) 是描述Java方法执行的内存模型,每个方法执行时都会创建一个栈帧。
- 每个线程运行时所需要的内存,称为虚拟机栈
- 每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存
- 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法
问题辨析
1. 垃圾回收是否涉及栈内存?
不涉及
2. 栈内存分配越大越好吗?
No
3. 方法内的局部变量是否线程安全?
如果方法内局部变量没有逃离方法的作用访问,它是线程安全的
如果是局部变量引用了对象,并逃离方法的作用范围,需要考虑线程安全
栈帧结构:
特点:
线程私有:生命周期与线程相同
FILO结构:方法调用对应栈帧的入栈出栈
可能抛出异常:
StackOverflowError:栈深度超过限制
OutOfMemoryError:栈扩展失败
局部变量表:
-
存储编译期可知的各种基本数据类型
-
对象引用(reference类型)
-
returnAddress类型(指向字节码指令地址)
操作数栈:
-
用于存储计算过程的中间结果
-
工作区,方法执行过程中数据写入和提取
四、本地方法栈
本地方法栈(Native Method Stack) 与虚拟机栈作用相似,区别在于它为Native方法服务。
特点:
线程私有区域
存储Native方法调用的状态
在HotSpot JVM中与虚拟机栈合并
同样会抛出StackOverflowError和OutOfMemoryError
五、堆
堆(Heap) 是JVM管理的最大一块内存区域,被所有线程共享,在虚拟机启动时创建。通过 new 关键字,创建对象都会使用堆内存
堆结构:
特点:
线程共享:所有线程访问同一堆空间
GC主要区域:垃圾收集器管理的主要区域
分代管理:
新生代(Young Generation):新对象创建区域
Eden区:对象初次分配区
Survivor区:经过Minor GC后存活的对象
老年代(Old Generation):长期存活的对象
异常:当堆无法分配内存且无法扩展时,抛出OutOfMemoryError
对象分配过程:
六、方法区
方法区(Method Area) 存储已被虚拟机加载的类型信息、常量、静态变量等数据。
方法区结构:
特点:
线程共享:所有线程共享方法区
永久代→元空间:
JDK7及之前:永久代(PermGen),在堆中
JDK8+:元空间(Metaspace),使用本地内存
包含运行时常量池:
存放编译期生成的各种字面量和符号引用
动态性:运行期间也可以将新的常量放入池中
异常:当方法区无法满足内存分配需求时,抛出OutOfMemoryError
运行时常量池
- 常量池,就是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等信息
- 运行时常量池,常量池是 *.class 文件中的,当该类被加载,它的常量池信息就会放入运行时常量池,并把里面的符号地址变为真实地址
七、StringTable
(一)StringTable 核心概念
-
本质
StringTable(字符串表)是JVM中哈希表(Hash Table) 的实现,用于存储字符串对象的引用。-
键(Key):字符串的哈希值(由字符串内容计算得出)。
-
值(Value):字符串对象在堆中的引用。
-
-
与常量池的关系
-
class文件常量池:存储编译期生成的字面量(Literal)和符号引用(如
"abc"
)。 -
运行时常量池:类加载时,将class常量池加载到方法区中。
-
StringTable:在运行时常量池中的字符串字面量首次被使用时,动态创建实际字符串对象并存入StringTable。
-
(二)核心特性与机制
1. 字符串唯一性(Intern机制)
-
通过
String.intern()
方法,将字符串主动加入StringTable并返回唯一引用。 -
规则:若StringTable中已存在相同内容的字符串,则返回其引用;否则将当前字符串加入表中。
String s1 = new String("hello"); // 在堆中创建对象,未加入StringTable
String s2 = "hello"; // 直接使用StringTable中的引用
String s3 = s1.intern(); // 将s1的字符串内容加入StringTableSystem.out.println(s1 == s2); // false:s1在堆,s2在StringTable
System.out.println(s2 == s3); // true:s2和s3指向StringTable同一对象
2. 延迟加载
-
字符串字面量在首次被引用时才创建对象并加入StringTable。
-
示例:
public class LazyLoadExample {public static void main(String[] args) {// 仅声明字面量,未主动使用,不会加载到StringTableString unused = "unused_string"; // 首次使用字面量时加载System.out.println("hello"); // "hello" 被加入StringTable}
}
3. 不可变性
-
所有存入StringTable的字符串均为不可变对象(由
final char[]
实现)。 -
修改字符串会创建新对象,不影响原StringTable中的引用。
- 常量池中的字符串仅是符号,第一次用到时才变为对象
- 利用串池的机制,来避免重复创建字符串对象
- 字符串变量拼接的原理是 StringBuilder (1.8)
- 字符串常量拼接的原理是编译期优化
- 可以使用 intern 方法,主动将串池中还没有的字符串对象放入串池
- 1.8 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 会把串池中的对象返回
- 1.6 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有会把此对象复制一份, 放入串池, 会把串池中的对象返回
(三)内存位置演变
JVM版本 | 存储位置 | 特点 |
---|---|---|
JDK 1.6及之前 | 永久代(PermGen) | 固定大小,易触发 OutOfMemoryError: PermGen space 。 |
JDK 1.7+ | 堆内存(Heap) | 可动态扩容,受 -Xmx 控制,GC可回收无引用的字符串。 |
(四)字符串创建流程
(五)性能优化建议
-
调整表大小
通过
-XX:StringTableSize=N
设置桶数量(建议设为质数),减少哈希冲突。
java -XX:StringTableSize=10009 MyApp
- 避免重复字符串
使用 intern()
减少重复大字符串的内存占用(适合重复率高的场景)。
List<String> list = new ArrayList<>();
for (int i = 0; i < 10000; i++) {String temp = new String("重复数据").intern(); // 复用StringTable中的对象list.add(temp);
}
-
谨慎使用
intern()
-
高频调用可能引发哈希冲突,导致性能下降。
-
适合长期存活且重复率高的字符串(如数据库字段名)。
-
(六)示例:StringTable 与 GC 交互
public class StringTableGCDemo {public static void main(String[] args) {for (int i = 0; i < 100000; i++) {String temp = "str_" + i; // 字面量加入StringTabletemp = null; // 断开引用}System.gc(); // 触发GC,回收无引用的String对象(JDK1.7+)}
}
-
JDK 1.6:字符串在PermGen中,GC不回收,导致内存泄漏。
-
JDK 1.7+:字符串在堆中,GC可回收无引用的对象。
(七)总结对比表
特性 | 常量池(Constant Pool) | StringTable |
---|---|---|
存储内容 | 字面量、符号引用 | 字符串对象的引用 |
内存位置 | 方法区(元空间) | 堆内存 |
生命周期 | 类加载时生成 | 运行时动态添加 |
垃圾回收 | 不回收 | 可被GC回收(JDK1.7+) |
数据结构 | 表结构(非哈希) | 哈希表 |
关键结论:
StringTable 是 运行时字符串驻留机制 的核心,通过哈希表实现唯一性。
JDK 1.7+ 将其移至堆内存,解决了永久代内存溢出问题,且支持GC回收。
合理使用
intern()
和调整StringTableSize
可优化内存与性能。
(八)面试题
String s1 = "a";
String s2 = "b";
String s3 = "a" + "b";
String s4 = s1 + s2;
String s5 = "ab";
String s6 = s4.intern();
// 问
System.out.println(s3 == s4);
System.out.println(s3 == s5);
System.out.println(s3 == s6);
String x2 = new String("c") + new String("d");
String x1 = "cd";
x2.intern();
// 问,如果调换了【最后两行代码】的位置呢,如果是jdk1.6呢
System.out.println(x1 == x2);
八、直接内存
(一) 基本概念
-
定义:直接内存是 JVM 堆外由操作系统直接管理的内存区域。
-
核心类:通过
java.nio.ByteBuffer.allocateDirect()
分配。 -
数据存储:直接存储原始字节数据,不归 JVM GC 管理。
(二)与堆内存的对比
特性 | 直接内存 | 堆内存 |
---|---|---|
内存位置 | 堆外(操作系统管理) | JVM 堆内 |
分配速度 | 较慢(需系统调用) | 较快(JVM 内部管理) |
访问速度 | 快(少一次数据复制) | 较慢(需复制到堆外) |
内存回收 | 手动或基于 Cleaner 机制 | GC 自动回收 |
容量限制 | 受系统内存限制 | 受 -Xmx 限制 |
适用场景 | 高频 I/O、大文件操作 | 常规对象存储 |
(三)核心优势
-
减少数据复制
-
传统 I/O:数据需从内核缓冲区 → JVM 堆缓冲区 → 用户空间(两次复制)。
-
直接内存:数据直接在内核缓冲区处理(零复制),提升效率。
-
应用场景:网络传输、文件读写(如 NIO 的
FileChannel.transferTo()
)。
-
-
突破堆大小限制
// 分配 1GB 直接内存(不受 -Xmx 限制)
ByteBuffer directBuffer = ByteBuffer.allocateDirect(1024 * 1024 * 1024);
(四)内存分配与回收
-
分配方式:
ByteBuffer buffer = ByteBuffer.allocateDirect(1024); // 分配 1KB 直接内存
回收机制:
-
Cleaner
机制:DirectByteBuffer
被 GC 回收时,触发关联的Cleaner
释放堆外内存。 -
手动释放(不推荐):
// 反射强制释放(仅作演示,实际慎用!)
Method cleanerMethod = buffer.getClass().getMethod("cleaner");
cleanerMethod.setAccessible(true);
Object cleaner = cleanerMethod.invoke(buffer);
Method cleanMethod = cleaner.getClass().getMethod("clean");
cleanMethod.invoke(cleaner);
(五)潜在问题
-
内存泄漏
-
原因:
DirectByteBuffer
对象被回收前,堆外内存不会被释放。 -
风险点:频繁分配大块直接内存且未及时触发 GC。
-
-
OutOfMemoryError
-
错误信息:
Direct buffer memory
-
解决方案:
-
增大 JVM 参数:
-XX:MaxDirectMemorySize=2G
(默认等于-Xmx
)。 -
优化代码:复用
ByteBuffer
或显式调用System.gc()
(不保证立即生效)。
-
-
(六)最佳实践
-
复用缓冲区
// 复用 ByteBuffer 减少分配开销
ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
buffer.clear(); // 重置位置标记,准备复用
- 结合内存映射文件
// 使用内存映射文件操作大文件(基于直接内存)
FileChannel channel = FileChannel.open(Path.of("largefile.bin"));
MappedByteBuffer mappedBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, channel.size()
);
-
监控工具
-
JVM 参数:
-XX:NativeMemoryTracking=detail
-
命令:
jcmd <pid> VM.native_memory
-
(七)典型应用场景
-
Netty 的 ByteBuf
// Netty 的池化直接内存
ByteBuf directBuf = Unpooled.directBuffer(1024);
-
高性能序列化
-
如 Kryo、FST 直接操作堆外内存避免 GC 停顿。
-
(八)总结
关键点 | 说明 |
---|---|
本质 | JVM 堆外内存,由操作系统管理 |
分配方式 | ByteBuffer.allocateDirect() |
性能优势 | 零复制(减少内核-用户态数据拷贝) |
回收风险 | 依赖 Cleaner 机制,可能内存泄漏 |
适用场景 | 高频 I/O、大文件处理、网络通信框架 |
监控手段 | NMT(Native Memory Tracking)、MaxDirectMemorySize 参数 |
(九)分配和回收原理
- 使用了 Unsafe 对象完成直接内存的分配回收,并且回收需要主动调用 freeMemory 方法
- ByteBuffer 的实现类内部,使用了 Cleaner (虚引用)来监测 ByteBuffer 对象,一旦 ByteBuffer 对象被垃圾回收,那么就会由 ReferenceHandler 线程通过 Cleaner 的 clean 方法调用 freeMemory 来释放直接内存
九、JVM内存整体结构图
完结撒花🎉
相关文章:

【JVM】Java虚拟机(一)——内存结构
目录 一、简介 二、程序计数器 三、虚拟机栈 栈帧结构: 特点: 四、本地方法栈 特点: 五、堆 堆结构: 特点: 对象分配过程: 六、方法区 方法区结构: 特点: 运行时常量池…...
从微积分到集合论(1630-1910)(历史简介)——第4章——现代积分理论的起源(Thomas Hawkins)
第 4 章 现代积分理论的起源 (The Origins of Modern Theories of Integration) Thomas Hawkins 目录 4.1 引言(Introduction) 4.2 Fourier分析与任意函数(Fourier analysis and arbitrary functions) 4.3 对Fourier问题的回应(Responses to Fourier)(1821-1854)…...

《Linux运维总结:宝德服务器RAID开启(方式一)》
总结:整理不易,如果对你有帮助,可否点赞关注一下? 更多详细内容请参考:Linux运维实战总结 一、背景信息 说明:从客户那里退回来的一台宝德服务器,硬盘不见了,现在需要用两个2T的硬盘…...

NY118NY120美光固态闪存NY124NY129
NY118NY120美光固态闪存NY124NY129 美光NY系列固态闪存深度解析:技术、性能与行业洞察 技术架构与核心创新 美光NY系列(包括NY118、NY120、NY124、NY129等型号)作为企业级存储解决方案的代表作,延续了品牌在3D NAND技术上的深厚…...

Odoo 19 路线图(新功能)
Odoo 19 路线图(新功能) Odoo 19 路线图是Odoo官方针对下一版本的发布计划,将在自动化、合规性、用户体验、碳排放报告及本地化等领域推出超过16项新功能。本路线图详细阐述了Odoo 19如何在过往版本基础上进一步提升,助力企业优化销售、财务、运营及客户…...

基于NXP例程学习CAN UDS刷写流程
文章目录 前言1.概述1.1 诊断报文 2.协议数据单元(N_PDU)2.1 寻址信息(N_AI)2.1.1 物理寻址2.1.2 功能寻址2.1.3 常规寻址(Normal addressing)2.1.4 常规固定寻址(Normal fixed addressing)2.1.5 扩展寻址&…...
RNN循环网络:给AI装上“记忆“(superior哥AI系列第5期)
🔄 RNN循环网络:给AI装上"记忆"(superior哥AI系列第5期) 嘿!小伙伴们,又见面啦!👋 上期我们学会了让AI"看懂"图片,今天要给AI装上一个更酷的技能——…...
Python训练第四十三天
DAY 43 复习日 作业: kaggle找到一个图像数据集,用cnn网络进行训练并且用grad-cam做可视化 进阶:并拆分成多个文件 import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms, models …...

基于有效集MPC控制算法的直线同步电机simulink建模与仿真,MPC使用S函数实现
目录 1.课题概述 2.系统仿真结果 3.核心程序 4.系统仿真参数 5.系统原理简介 6.参考文献 7.完整工程文件 1.课题概述 有效集算法通过迭代地选择一组 "有效" 约束,将约束优化问题转化为一系列无约束或等式约束优化问题。直线同步电机 (Linear Synch…...

让敏感数据在流转与存储中始终守护在安全范围
在企业数字化运营浪潮中,企业内部应用服务器面临着非法访问、数据泄露等风险,如何全面守护应用服务器文件安全,让敏感数据在流转与存储中始终守护在安全范围? 服务器白名单让数据流转安全又高效 天 锐 蓝盾的服务器白名单功能既…...

【Linux】find 命令详解及使用示例:递归查找文件和目录
【Linux】find 命令详解及使用示例:递归查找文件和目录 引言 find 是 Linux/Unix 系统中强大的文件搜索工具,用于在目录层次结构中递归查找文件和目录。它提供了丰富的搜索条件和灵活的操作选项,可以满足从简单到复杂的各种文件查找需求。 …...
Java转Go日记(五十九):参数验证
1. 结构体验证 用gin框架的数据验证,可以不用解析数据,减少if else,会简洁许多。 package mainimport ("fmt""time""github.com/gin-gonic/gin" )//Person .. type Person struct {//不能为空并且大于10Age …...
机器学习与深度学习14-集成学习
目录 前文回顾1.集成学习的定义2.集成学习中的多样性3.集成学习中的Bagging和Boosting4.集成学习中常见的基本算法5.什么是随机森林6.AdaBoost算法的工作原理7.如何选择集成学习中的基础学习器或弱分类器8.集成学习中常见的组合策略9.集成学习中袋外误差和交叉验证的作用10.集成…...
MySQL数据库表设计与索引优化终极指南
MySQL数据库表设计与索引优化终极指南 标签:MySQL 数据库设计 索引优化 性能调优 一、前言:为什么表设计和索引如此重要? 在数据库系统中,良好的表设计和高效的索引策略是保证系统性能的关键。据统计,约70%的数据库性…...

【论文阅读笔记】万花筒:用于异构多智能体强化学习的可学习掩码
摘要 在多智能体强化学习(MARL)中,通常采用参数共享来提高样本效率。然而,全参数共享的流行方法通常会导致智能体之间的策略同质,这可能会限制从策略多样性中获得的性能优势。为了解决这一关键限制,我们提出…...

负载均衡LB》》HAproxy
Ubuntu 22.04 安装HA-proxy 官网 资料 # 更新系统包列表: sudo apt update # 安装 HAproxy sudo apt install haproxy -y # 验证安装 haproxy -v # 如下图配置 Haproxy 在这里插入代码片》》》配置完之后 重启 Haproxy sudo systemctl restart haproxy 补充几…...
Vue 中组件命名与引用
Vue 中组件命名与引用 前言 在 vue 项目中,我们会发现在代码中,import 组件 和 components 组件注册中得命名方式与组件引用时的命名方式不一样,这种现象是由组件名的大小写转换规则造成的。如下示例: 组件引入与注册ÿ…...

UE 5 和simulink联合仿真,如果先在UE5这一端结束Play,过一段时间以后**Unreal Engine 5** 中会出现显存不足错误
提问 UE5报错如图。解析原因 回答 你遇到的这个错误提示是: “Out of video memory trying to allocate a rendering resource. Make sure your video card has the minimum required memory, try lowering the resolution and/or closing other applications tha…...
在uni-app中如何从Options API迁移到Composition API?
uni-app 从 Options API 迁移到 Composition API 的详细指南 一、迁移前的准备 升级环境: 确保 HBuilderX 版本 ≥ 3.2.0项目 uni-app 版本 ≥ 3.0.0 了解 Composition API 基础: 响应式系统:ref、reactive生命周期钩子:onMount…...

Rust 控制流
文章目录 Rust 控制流if 表达式循环实现重复用 loop 重复代码从循环返回值循环标签用于区分多层循环while 条件循环用 for 循环遍历集合 Rust 控制流 在大多数编程语言中,根据条件是否为真来运行某些代码,以及在条件为真时重复运行某些代码,是…...
【Linux基础知识系列】第十三篇-Cron与定时任务管理
在Linux系统中,任务自动化是提高效率和确保服务连续性的关键。Cron是一个强大的定时任务管理工具,它允许用户设置定期执行的命令或脚本。通过Cron,用户可以自动化系统维护、备份、报告生成等多种任务。本文将详细介绍如何使用Cron工具创建和管…...
Visual Studio 中的 MD、MTD、MDD、MT 选项详解
在Visual Studio中开发C++项目时,正确选择运行时库(runtime library)对于确保应用程序的性能、稳定性和兼容性至关重要。本文将详细介绍/MD, /MT, /MDd, 和 /MTd这些编译器选项的意义、应用场景及其区别。 MSVCRT.dll MSVCRT.dll 是 Microsoft Visual C++ Runtime Library …...

Python 3.11.9 安装教程
前言 记录一下Windows环境下Python解释器的安装过程。 安装过程 1、安装程序下载 打开Python官网: 点击Downloads,选择Windows: 页面中找到需要的3.11.9版本,点击Download Windows installer (64-bit)下载: 2、…...

【各种主流消息队列(MQ)对比指南】
主流消息队列对比分析 一、核心指标对比 特性/消息队列RabbitMQKafkaRocketMQActiveMQPulsar协议支持AMQP, MQTT, STOMP自定义协议JMS/自定义协议JMS, AMQP, MQTT, STOMPMQTT, AMQP, STOMP单机吞吐量万级百万级十万级万级百万级延迟微秒级(低吞吐)毫秒…...

PySpark、Plotly全球重大地震数据挖掘交互式分析及动态可视化研究
全文链接:https://tecdat.cn/?p42455 分析师:Yapeng Zhao 在数字化防灾减灾的时代背景下,地震数据的深度解析成为公共安全领域的关键议题。作为数据科学工作者,我们始终致力于通过技术整合提升灾害数据的应用价值(点击…...
代码训练LeetCode(24)数组乘积
代码训练(24)LeetCode之数组乘积 Author: Once Day Date: 2025年6月5日 漫漫长路,才刚刚开始… 全系列文章可参考专栏: 十年代码训练_Once-Day的博客-CSDN博客 参考文章: 238. 除自身以外数组的乘积 - 力扣(LeetCode)力扣 (LeetCode) 全…...

如何让AI自己检查全文?使用OCR和LLM实现自动“全文校订”(可DIY校订规则)
详细流程及描述参见仓库(如果有用的话,请给个收藏): GitHub - xurongtang/DocRevision_Proj: A simple project about how to revist docment (such as your academic paper) in a automatic way with the help of OCR and LLM.A…...
volka 25个短语动词
以下是分句分段后的内容: 3,000. Thats 95% of spoken English. And I am teaching you all of these words. First, Ill teach you todays words. And then youll hear them in real conversations. With my brother. Stick around until the end, because witho…...
Java观察者模式深度解析:构建松耦合事件驱动系统的艺术
目录 观察者模式基础解析核心结构与实现原理Java内置观察者实现Spring框架中的高级应用典型应用场景与实战案例观察者模式变体与优化常见问题与最佳实践总结与未来展望1. 观察者模式基础解析 1.1 模式定义与核心思想 观察者模式(Observer Pattern)是一种行为型设计模式,它…...

DFT测试之TAP/SIB/TDR
TAP的作用 tap全称是test access port,是将jtag接口转为reset、sel、ce、ue、se、si、tck和so这一系列测试组件接口的模块。 jtag的接口主要是下面几个信号: 信号名称信号方向信号描述TCK(测试时钟)输入测试时钟,同…...