【JVM】Java虚拟机(一)——内存结构

news2025/6/7 10:33:34

目录

一、简介

二、程序计数器

三、虚拟机栈

栈帧结构:

特点:

四、本地方法栈

特点:

五、堆

堆结构:

特点:

对象分配过程:

六、方法区

方法区结构:

特点:

运行时常量池

七、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 核心概念

  1. 本质
    StringTable(字符串表)是JVM中哈希表(Hash Table) 的实现,用于存储字符串对象的引用

    • 键(Key):字符串的哈希值(由字符串内容计算得出)。

    • 值(Value):字符串对象在堆中的引用。

  2. 与常量池的关系

    • 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的字符串内容加入StringTable

System.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) {
        // 仅声明字面量,未主动使用,不会加载到StringTable
        String 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; // 字面量加入StringTable
            temp = 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、大文件操作常规对象存储

(三)核心优势

  1. 减少数据复制

    • 传统 I/O:数据需从内核缓冲区 → JVM 堆缓冲区 → 用户空间(两次复制)。

    • 直接内存:数据直接在内核缓冲区处理(零复制),提升效率。

    • 应用场景:网络传输、文件读写(如 NIO 的 FileChannel.transferTo())。

  2. 突破堆大小限制

// 分配 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);

(五)潜在问题

  1. 内存泄漏

    • 原因:DirectByteBuffer 对象被回收前,堆外内存不会被释放。

    • 风险点:频繁分配大块直接内存且未及时触发 GC。

  2. 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内存整体结构图

完结撒花🎉

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2402785.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

从微积分到集合论(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开启(方式一)》

总结&#xff1a;整理不易&#xff0c;如果对你有帮助&#xff0c;可否点赞关注一下&#xff1f; 更多详细内容请参考&#xff1a;Linux运维实战总结 一、背景信息 说明&#xff1a;从客户那里退回来的一台宝德服务器&#xff0c;硬盘不见了&#xff0c;现在需要用两个2T的硬盘…

NY118NY120美光固态闪存NY124NY129

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

Odoo 19 路线图(新功能)

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

基于NXP例程学习CAN UDS刷写流程

文章目录 前言1.概述1.1 诊断报文 2.协议数据单元(N_PDU)2.1 寻址信息&#xff08;N_AI&#xff09;2.1.1 物理寻址2.1.2 功能寻址2.1.3 常规寻址&#xff08;Normal addressing&#xff09;2.1.4 常规固定寻址&#xff08;Normal fixed addressing&#xff09;2.1.5 扩展寻址&…

基于有效集MPC控制算法的直线同步电机simulink建模与仿真,MPC使用S函数实现

目录 1.课题概述 2.系统仿真结果 3.核心程序 4.系统仿真参数 5.系统原理简介 6.参考文献 7.完整工程文件 1.课题概述 有效集算法通过迭代地选择一组 "有效" 约束&#xff0c;将约束优化问题转化为一系列无约束或等式约束优化问题。直线同步电机 (Linear Synch…

让敏感数据在流转与存储中始终守护在安全范围

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

【Linux】find 命令详解及使用示例:递归查找文件和目录

【Linux】find 命令详解及使用示例&#xff1a;递归查找文件和目录 引言 find 是 Linux/Unix 系统中强大的文件搜索工具&#xff0c;用于在目录层次结构中递归查找文件和目录。它提供了丰富的搜索条件和灵活的操作选项&#xff0c;可以满足从简单到复杂的各种文件查找需求。 …

【论文阅读笔记】万花筒:用于异构多智能体强化学习的可学习掩码

摘要 在多智能体强化学习&#xff08;MARL&#xff09;中&#xff0c;通常采用参数共享来提高样本效率。然而&#xff0c;全参数共享的流行方法通常会导致智能体之间的策略同质&#xff0c;这可能会限制从策略多样性中获得的性能优势。为了解决这一关键限制&#xff0c;我们提出…

负载均衡LB》》HAproxy

Ubuntu 22.04 安装HA-proxy 官网 资料 # 更新系统包列表&#xff1a; sudo apt update # 安装 HAproxy sudo apt install haproxy -y # 验证安装 haproxy -v # 如下图配置 Haproxy 在这里插入代码片》》》配置完之后 重启 Haproxy sudo systemctl restart haproxy 补充几…

UE 5 和simulink联合仿真,如果先在UE5这一端结束Play,过一段时间以后**Unreal Engine 5** 中会出现显存不足错误

提问 UE5报错如图。解析原因 回答 你遇到的这个错误提示是&#xff1a; “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…

Rust 控制流

文章目录 Rust 控制流if 表达式循环实现重复用 loop 重复代码从循环返回值循环标签用于区分多层循环while 条件循环用 for 循环遍历集合 Rust 控制流 在大多数编程语言中&#xff0c;根据条件是否为真来运行某些代码&#xff0c;以及在条件为真时重复运行某些代码&#xff0c;是…

Python 3.11.9 安装教程

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

【各种主流消息队列(MQ)对比指南】

主流消息队列对比分析 一、核心指标对比 特性/消息队列RabbitMQKafkaRocketMQActiveMQPulsar协议支持AMQP, MQTT, STOMP自定义协议JMS/自定义协议JMS, AMQP, MQTT, STOMPMQTT, AMQP, STOMP单机吞吐量万级百万级十万级万级百万级延迟微秒级&#xff08;低吞吐&#xff09;毫秒…

PySpark、Plotly全球重大地震数据挖掘交互式分析及动态可视化研究

全文链接&#xff1a;https://tecdat.cn/?p42455 分析师&#xff1a;Yapeng Zhao 在数字化防灾减灾的时代背景下&#xff0c;地震数据的深度解析成为公共安全领域的关键议题。作为数据科学工作者&#xff0c;我们始终致力于通过技术整合提升灾害数据的应用价值&#xff08;点击…

如何让AI自己检查全文?使用OCR和LLM实现自动“全文校订”(可DIY校订规则)

详细流程及描述参见仓库&#xff08;如果有用的话&#xff0c;请给个收藏&#xff09;&#xff1a; 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…

DFT测试之TAP/SIB/TDR

TAP的作用 tap全称是test access port&#xff0c;是将jtag接口转为reset、sel、ce、ue、se、si、tck和so这一系列测试组件接口的模块。 jtag的接口主要是下面几个信号&#xff1a; 信号名称信号方向信号描述TCK&#xff08;测试时钟&#xff09;输入测试时钟&#xff0c;同…

【推荐算法】DeepFM:特征交叉建模的革命性架构

DeepFM&#xff1a;特征交叉建模的革命性架构 一、算法背景知识&#xff1a;特征交叉的演进困境1.1 特征交叉的核心价值1.2 传统方法的局限性 二、算法理论/结构&#xff1a;双路并行架构2.1 FM组件&#xff1a;显式特征交叉专家2.2 Deep组件&#xff1a;隐式高阶交叉挖掘机2.3…

数据库表中「不是 null」的含义

例图&#xff1a; 1.勾选了「不是 null」&#xff08;NOT NULL&#xff09;&#xff1a; 这个字段在数据库中必须有值&#xff0c;不能为空。也就是说&#xff0c;你插入数据的时候&#xff0c;必须给它赋值&#xff0c;否则插入会报错。 2.没有勾选「不是 null」&#xff…

Visual Studio问题记录

程序"xxx dotnet.exe"已退出&#xff0c;返回值为-2147450730 问deepseek&#xff1a;visual studio输出程序dotnet.exe已退出&#xff0c;返回值为-2147450730 dotnet.exe 编译时退出并返回错误代码 **-2147450730**&#xff08;十六进制 0x80008076&#xff09;&…