深入解析:JVM 内存结构

news/2025/9/30 21:41:56/文章来源:https://www.cnblogs.com/wzzkaifa/p/19121823

目录

JVM 内存结构

一. 程序计数器

二. 虚拟机栈

1. 基本概念

2. 常见问题辨析

3. 栈内存溢出问题

4. 线程运行诊断

(1) CPU 占用过高

(2) 程序运行很长时间没有结果

三. 本地方法栈

四. 堆

1. 定义

2. 堆内存溢出

3. 堆内存诊断工具

4.案例

五. 方法区

1. 定义

2. 组成

3. 方法区内存溢出

4. 运行时常量池

5. StringTable (字符串常量池)

(1) StringTable 的特性

(2) StringTable 位置

(3) StringTable 垃圾回收

(4) StringTable 性能调优

六. 直接内存

1. 定义

2. 分配和回收原理


JVM 内存结构

image-20250526213626284

一. 程序计数器

java 源代码 --(javac)--> 字节码 --(类加载器)--> 机器码

程序计数器是线程私有的, 用于记录当前线程下一条 jvm 指令的执行地址.

  • 每个线程都有自己的程序计数器.

  • 程序计数器不存在内存溢出问题 (jvm 规范).

二. 虚拟机栈

1. 基本概念

虚拟机栈 (简称"栈").

  • 每个线程运行时所需要的内存, 就称为虚拟机栈.

  • 每个栈由多个栈帧 (Frame) 组成, 一个栈帧对应一次方法的调用, (每个方法执行所需要的内存, 就称为栈帧).

  • 每个线程只能有一个活动栈帧, 对饮当前正在执行的那个方法.

2. 常见问题辨析
  1. 栈内存是否与垃圾回收有关?

    不涉及. 方法调用时创建栈帧入栈, 方法执行完毕后栈帧自动出栈并释放内存.

  1. 栈内存分配越大越好吗?

不是.

栈内存过大 可能会导致同时执行的线程数量减少, 程序运行效率降低 ; 内存资源浪费.

栈内存过小 可能会导致栈溢出问题.

所以我们应当合理设置栈内存大小.

  1. 方法内的局部变量是否存在线程安全问题?

不存在. 局部变量存储在线程私有的栈内存中, 不同线程的栈内存完全隔离, 互不干扰.

:

  • 如果方法内的局部变量(对象) 没有超出方法的作用范围, 则是线程安全的.

  • 如果方法内的局部变量(对象) 超出了方法的作用范围, 则不是线程安全的.

3. 栈内存溢出问题
  1. 栈帧过多导致栈内存溢出 (eg: 方法调用层级过深, 深度递归调用, 两个类之间的循环引用).

  2. 栈帧过大导致栈内存溢出 (eg: 栈内存设置过小, 局部变量占用空间过大) .

4. 线程运行诊断
(1) CPU 占用过高

定位:

  • 先用 top 命令定位哪个进程对 CPU 的占用过高.

  • 用 ps 命令进一步定位是哪个线程引起的 CPU 占用过高 (ps H -eo pid,tid,%cpu | grep 进程id).

  • jstack 进程id: 根据线程id找到有问题的线程, 进一步定位到有问题的源代码行数.

(2) 程序运行很长时间没有结果

有可能是多个线程发生死锁 导致程序运行不出来结果. 可以通过 jstack 进程id 来定位问题.

三. 本地方法栈

本地方法的调用 提供 内存空间.

四. 堆

1. 定义

使用 new 关键字创建的对象会存放到堆内存中.

特点:

  • 堆是线程共享的, 堆中的对象都需要考虑线程安全问题.

  • 堆有垃圾回收机制.

2. 堆内存溢出

java.lang.OutOfMemoryError: Java heap space

循环创建大量对象.

业务逻辑导致对象占用持续增长.

3. 堆内存诊断工具
  1. jps 工具

    • 查看当前 java 进程中 有哪些 java 进程.

  2. jmap 工具

    • 查看 (某时刻) 堆内存占用情况. (无法连续监测)

      jmap -heap 进程ID

  3. jconsole 工具

    • 图形界面的 多功能监测工具 (可连续监测)

      image-20250909115124111

  4. jvisualvm

    java可视化虚拟机. 用可视化的方式展现虚拟机信息.

4.案例
  • 垃圾回收之后, 内存占用仍然很高, 是什么情况?

五. 方法区

1. 定义

方法区只是一个规范, 不同的厂商有不同的实现方式. (对于 Oracle HotSpot 虚拟机: 方法区在jdk1.8之前的实现是永久代, 在jdk1.8之后的实现是元空间)

方法区是一块线程共享的区域, 主要用于存储 类的元数据信息, 常量池 和 静态变量.

image-20250526213626284

2. 组成

方法区: 类 (class) + 类加载器 (ClassLoader) + 运行时常量池 (StringTable).

image-20250909122715351

3. 方法区内存溢出
  • jdk1.8 之前: 永久代内存溢出.

    # 演示永久代内存溢出
    -XX:MaxPermSize=8m
    java.lang.OutOfMemoryError: PermGen space
  • jdk 1.8 之后: 元空间内存溢出.

    # 演示元空间内存溢出 
    -XX:MaxMetaspaceSize=8m
    java.lang.OutOfMemoryError: Metaspace

类加载器 用于 加载类的二进制字节码.

如果加载的类过多, 会导致方法区内存溢出. 如下代码, 加载 10000 个类, 同时元空间最大容量是8M, 就会导致方法区内存溢出.

/*** 演示元空间内存溢出* -XX:MaxMetaspaceSize=8m*/
public class Demo1_8 extends ClassLoader { // 可以用来加载类的二进制字节码   public static void main(String[] args) {       int j = 0;       try {           Demo1_8 test = new Demo1_8();           for (int i = 0; i < 10000; i++, j++) {               // ClassWriter 作用是生成类的二进制字节码               ClassWriter cw = new ClassWriter(0);               // 版本号, public, 类名, 包名, 父类, 接口               cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "Class" + i, null, "java/lang/Object", null);               // 返回 byte[]               byte[] code = cw.toByteArray();               // 执行了类的加载               test.defineClass("Class" + i, code, 0, code.length);           }       } finally {           System.out.println(j);       }   }
}

4. 运行时常量池

类基本信息, 常量池, 类方法定义.

  • 常量池: 就是一张表, 虚拟机指令根据这张常量表找到要执行的 类名, 方法名, 参数类型, 字面量 等信息.

  • 运行时常量池: 常量池是在 .class 文件 中的. 当该类被加载, 它的常量池信息就会放入运行时常量池, 并把里面的符号地址变为真实地址.

5. StringTable (字符串常量池)
(1) StringTable 的特性
  1. 常量池中的字符串仅是符号, 第一次用到时才变为对象.

    当 Java 代码被编译成 .class 文件时, 字符串字面量 (如 "abc") 会以符号形式存储在 .class 文件的常量池中. 此时它们还不是真正的 Java 对象, 只是一种标记或引用符号, 用于表示这段字符串的内容.

    只有当类被加载到 JVM 中, 并且程序第一次使用到这个字符串时 (比如 赋值给变量, 作为方法参数等): 此时 JVM 才会在堆中创建对应的字符串对象, 并将该对象的引用存入字符串常量池 (StringTable).

    // StringTable [ "a", "b", "ab" ] hashtable结构,不能扩容
    public class Demo1_22 {   // 常量池中的信息,都会被加载到运行时常量池中. 这时 a b ab 都是常量池中的符号, 还没有变为 java 字符串对象   // ldc #2 会把 a 符号变为 "a" 字符串对象   // ldc #3 会把 b 符号变为 "b" 字符串对象   // ldc #4 会把 ab 符号变为 "ab" 字符串对象
    ​   public static void main(String[] args) {       String s1 = "a"; // 懒惰的       String s2 = "b";       String s3 = "ab";       String s4 = s1 + s2; // new StringBuilder().append("a").append("b").toString() new String("ab")       String s5 = "a" + "b"; // javac 在编译期间的优化
    ​       System.out.println(s3 == s5);   }
    }

  2. 利用串池的机制, 来避免重复创建字符串对象.

  3. 字符串变量拼接的原理是 StringBuilder (jdk 1.8).

  4. 字符串常量拼接的原理是编译期优化.

  5. 可以使用 intern 方法, 主动将串池中还没有的字符串对象放入串池.

    • jdk1.8: intern 方法将字符串对象尝试放入字符串常量池. 如果串池中有则不会放入 ; 如果串池中没有则放入串池, 最后返回串池中的对象.

      public class Demo23 {   // ["a", "b", "ab"]   public static void main(String[] args) {       // 堆 new String("a") new String("b") new String("ab")       String s = new String("a") + new String("b");              // intern: 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池,最后把串池中的对象返回       String s2 = s.intern();               System.out.println( s2 == x); //true       System.out.println( s == "ab"); //false   }
      }
      • jdk1.6: intern 方法将字符串对象尝试放入字符串常量池. 如果串池中有则不会放入 ; 如果串池中没有, 则将此对象复制一份 再放入串池, 最后返回串池中的对象.

(2) StringTable 位置
  • 在 jdk1.6 及以前: 字符串常量池位于永久代 (方法区) 中.

  • 在 jdk1.7 及以后: 字符串常量池位于中.

做此迁移的原因: 永久代内存的回收效率很低, 在 FullGC 时才会触发永久代的垃圾回收 (触发时机晚, 触发频率太少), 所以就导致 StringTable 的回收效率不高, 当字符串常量过多时, 很容易触发永久代内存溢出 ; 而在堆中, MinorGC 就会触发垃圾回收, 回收频率提高, 大大降低了内存溢出的风险.

image-20250909202717986

(3) StringTable 垃圾回收
  • 在 jdk1.6 前: StringTable 位于永久代中, 基本不涉及垃圾回收.

  • 在 jdk1.7 后: StringTable 位于堆中, 会随着堆区进行垃圾回收.

(4) StringTable 性能调优
  • 如果有字符串数量很大 --> 可以通过 -XX:StringTableSize=xxx 调整底层 HashTable 的桶的个数. 可以减少哈希冲突,提升字符串的查找效率加快读取速度.

  • 如果有大量字符串 且 字符串存在重复的问题 --> 可以考虑将字符串对象是否入池 (避免重复的字符串入池) ---> 使用 intern().

  • 减少不必要的字符串创建. 创建过多不必要的字符串会增加 StringTable 的负担, 占用更多内存, 还可能导致哈希冲突增多.

六. 直接内存

1. 定义

直接内存 (Direct Memory) 是 JVM 向操作系统"申请" 后, 由操作系统直接分配的内存. 不占用 JVM 堆内存空间, 也不占用系统内存空间. Java 代码可以直接访问, 操作系统也可以直接访问.

直接内存不受 JVM 垃圾回收的管理 (回收需显式调用或依赖操作系统机制).

  • 常见于 NIO 操作时, 用于数据缓冲区

  • 分配回收成本较高, 但读写性能高

  • 不受 JVM 内存回收管理

为什么使用了直接内存, 大文件的读写效率就会非常高?

  • 不使用直接内存的文件读写过程:

  1. Java 本身不具备磁盘读写的能力, 想要进行磁盘读写 必须调用操作系统提供函数 (调用本地方法). [状态切换: 用户态 --> 内核态].

  2. 在内核态下去真正读取磁盘文件内容, 磁盘内容会先读入到系统缓冲区中, 然后再将数据从系统缓冲区读到 java 缓冲区中. 这样就导致了一次不必要的数据复制, 使得读写效率下降.

image-20250910091229179

  • 使用直接内存的文件读写过程:

  1. Java 程序从用户态切换到内核态.

  2. 在内核态下读取磁盘文件内容到直接内存 (direct memory) 中, java 代码和系统都可以直接从 direct memory 中读取数据, 减少了一步缓冲区复制操作, 提高了效率.

image-20250910092203710

2. 分配和回收原理
  • 使用 Unsafe 对象 完成直接内存的 分配和回收, 并且回收需要主动调用 freeMemory() 方法.

  • ByteBuffer 的实现类内部使用了 Cleaner (虚引用) 来监测 ByteBuffer 对象, 一旦 ByteBuffer 对象被垃圾回收, 就会由 ReferenceHandler 线程通过 Cleaner 的 clean 方法调用 freeMemory 来释放直接内存.

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

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

相关文章

gudao网站建设闸北东莞网站建设

7-13 p070找出全部子串位置 分数 5 作者 吴敏华 单位 首都师范大学 输入两个串s1,s2&#xff0c;找出s2在s1中所有出现的位置。 前后两个子串的出现不能重叠。例如’aa’在 aaaa 里出现的位置只有0,2 输入格式: 第一行是整数n 接下来有n行&#xff0c;每行两个不带空格的字符…

深圳微信网站建设报价九游手游平台app

一、利用webapps文件夹自动部署这是最简单的方式&#xff0c;只要将网站直接拷贝到&#xff1a;tomcat根目录下的webapps文件夹里举例&#xff1a;helloworld文件夹下创建里index.html文件&#xff0c;然后把helloworld文件夹移动到tomcat根目录下webapps文件夹里&#xff0c;重…

9/30

今天上午上了一节工程实训的课,其中小车红外线测距系统用到了C语言,第一次真正的用到了所学知识,感觉不错,四节课很快过去了,明天就是国庆,开心

rhel8无法输入中文问题(红帽8安装中文输入法)

问题:明明在设置里添加了汉语拼音输入法缺无法输入中文解决方法: 通过yum安装中文输入法 [root@server01 ~]# dnf install ibus-libpinyin -y到设置里添加新安装的输入法发现还没有新安装的输入法,这时需要重启系统…

威佐夫博弈(Wythoff‘s Game)

威佐夫博弈(Wythoff‘s Game) 1. Beatty 定理 设 无理数 \(r\) 对应的 Beatty 数列 \(B_r\) 为 \((B_r)_i=\lfloor i\times r\rfloor\)。 定理:若无理数 \(r,s\) 满足 \(\frac 1 r+\frac 1 s=1\),则 \(B_r,B_s\) 为…

外贸网站收到询盘淘客网站建设视频

目录 一、modbus简介 二、功能码01、02 三、modbus解析 四、功能码03、04 五、功能码05 六、功能码06 七、功能码16 一、modbus简介 我们在网上查阅modbus的资料发现很多很杂&#xff0c;modbus-RTU ASCII TCP等等&#xff0c;还有跟PLC结合的&#xff0c;地址还分1开…

PWN手成长之路-05-ROP

与远程环境进行交互,可以进行输入,但是输入之后无任何回显。file 查看文件。64位 ELF 。checksec 查看文件安全属性。IDA 打开文件。查看 main 函数的反编译代码。查看 buf 这个字符数组(栈上的缓冲区),本身 buf …

随机采样研究随笔

\(y=x^n\) 在 O(n) 复杂度随机 \(y=(1-x)^a x^b\) 在 O(n) 复杂度随机 线性时间随机 \(n\) 个有序的 \([0,1]\) 实数。 线性时间随机 \(sum=0\) 的一个 1,-1 序列 \(O(n\sqrt n)\) 时间随机一个合法括号序列

Python 正则表达式实战:一文搞定文本处理

在 Python 中,正则表达式(Regular Expression)是一种强大的文本处理工具,用于匹配、搜索、替换等操作。无论是数据清洗、文本解析还是复杂的文本处理任务,正则表达式都能轻松应对。今天,就让我们一起深入学习 Py…

2025-2026-1 20231301 《信息安全设计》第八周学习总结

View Post2025-2026-1 20231301 《信息安全设计》第八周学习总结2025-2026-1 20231301 《信息安全设计》第八周学习总结 目录作业信息学习内容总结一、TLS协议深度解析二、OpenSSL SSL/TLS编程实战三、OpenSSL命令行TL…

太月星网站建设程序开发一键生成微信小程序

你知道吗&#xff0c;C类是编程世界中的一种强大工具&#xff0c;它可以帮助我们更好地组织和管理代码。接下来&#xff0c;我将为你呈现一篇近万字的C类的教程&#xff0c;希望能帮助你熟悉这个概念。 首先&#xff0c;让我们从C类的定义开始。类是一个模板&#xff0c;它描述…

2025-2026-1 20231301 《信息安全设计》第七周学习总结

View Post2025-2026-1 20231301 《信息安全设计》第七周学习总结2025-2026-1 20231301 《信息安全设计》第七周学习总结 目录作业信息学习内容总结第十章:身份认证和PKI理论基础一、PKI体系架构深度解析二、证书处理实…

springboot+vue心理健康服务小程序(源码+文档+调试+基础修改+答疑) - 详解

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

肉山谷英雄传说新手任务登录英文网站怎么做装修网站建设网

文章目录 前言UUID 处理的更改正则表达式的更改结束 前言 Android 14 已经出来好久好久了… 今天其他的暂且不论&#xff0c;单纯的讲一下 OpenJDK 17 更新的两点变更&#xff08;扒源代码&#xff09;~ 对正则表达式的更改UUID 处理 首先&#xff0c;正则表达式的更改&…

网站备案查询 工信部wordpress 删除标签页

导语 如果之前的单机版hadoop环境安装满足不了你&#xff0c;集群版hadoop一定合你胃口&#xff0c;轻松入手。目录 集群规划前置条件配置免密登录 3.1 生成密匙 3.2 免密登录 3.3 验证免密登录集群搭建 4.1 下载并解压 4.2 配置环境变量 4.4 修改配置 4.4 分发程序 4.5 初始化…

湛江企业自助建站时尚网站设计案例

1. 模型旋转角度尽量取整数&#xff0c;保证线条不会出现锯齿 2. 修改反锯齿模型为FXAA方式&#xff0c;默认的TemporalAA方式会闪烁 3. 动态更新的纹理尺寸一般都不会是2的N次方&#xff0c;比如401X518。 解决方案是 动态更新一张1024x1024的贴图的其中401X518&#xff0c;…

南宁网站建设招聘建设工程合同备案在什么网站上

2016只剩下不到百分之一的时间了&#xff0c;网上陆续看到各种企业或个人的总结或盘点&#xff0c;公司也必须规定每个员工要做年度工作总结&#xff0c;或许是环境释然&#xff0c;心里也有无数次要做总结的念头&#xff0c;尤其是月末年末这种感觉更重&#xff0c;但却没静下…

没用的博客园页面的要素介绍(待更新)

1. 关于那几行字点击查看"<b style=color:rgb(119, 248, 255)>又是一年雨季</b>","<b style=color:rgb(119, 248, 255)>青苔悄悄爬满缝隙</b>","<b style=color:…

详细介绍:Music Tag Web 怎么安装 ffmpeg?

详细介绍:Music Tag Web 怎么安装 ffmpeg?pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", &quo…

2025-2026-1 20231301 《信息安全设计》第六周学习总结

View Post2025-2026-1 20231301 《信息安全设计》第六周学习总结2025-2026-1 20231301 《信息安全设计》第六周学习总结 目录作业信息学习内容总结一、Windows密码体系架构深度解析二、CryptoAPI核心编程实战三、CSP服…