JVM内存结构
什么是JVM
以下所写JVM内容都是基于hotspot。
定义:Java Virtual Machine - java程序的运行环境(Java 二进制字节码的运行环境)。
好处:
- 一次编写,到处运行。(屏蔽了操作系统底层的差异)
- 自动内存管理,垃圾回收功能。
- 数组下标越界检查
- 多态
比较:
- JVM <————> 操作系统(Windows,MacOS,Linux。。。)
- JRE(JVM + 核心类库)
- JDK(JVM+核心类库+编译工具)
- JAVA SE(JDK + IDE工具)
- JAVA EE (JDK+应用服务器+IDE工具)
JVM指令->解释器->机器码->CPU
解释器 ->程序计数器拿到下一条指令地址
JVM 内存结构
- 虚拟机栈
- 堆
- 方法区
- 本地方法栈
- 程序计数器
1、程序计数器(CPU中的寄存器实现)
定义:Program Counter Register 程序计数器(寄存器)
作用:记住下一条JVM指令执行的地址
特点:
- 线程私有的
- 不会存在内存溢出
程序计数器:是jvm内存区域中一块较小的内存区域,其中记录的是当前线程执行到的字节码的行号,字节码解释器工作时通过改变程序计数器的值来选取下一条需要执行的字节码指令。
在线程切换过程中,程序计数器记录当前线程执行的字节码指令行号,再切换回该线程时,能保证正确运行。所以程序计数器是线程私有的。
注意:程序计数器是唯一一个不会出现 OutOfMemoryError
的内存区域,它的生命周期随着线程的创建而创建,随着线程的结束而死亡。
2、虚拟机栈
栈:线程运行需要的内存空间
栈帧:每个方法运行时需要的内存(参数、局部变量、返回地址...)
定义:Java Virtual Machine Stacks(Java虚拟机栈)
- 每个线程运行时所需要的内存,称为虚拟机栈
- 每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存
- 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法
垃圾回收不需要对栈内存进行处理,栈帧出栈自动释放。
指定栈内存大小 虚拟机参数 -Xss size
栈内存并不是越大越好,栈内存越大,线程数目越少。
局部变量是线程私有的,若不逃离方法的作用范围(作为其他方法入参/作为反参),不会有安全问题。
-
栈内存溢出(Java.lang.StackOverflowError)
- 栈帧过多导致栈内存溢出,常见于递归调用方法。
- 栈帧过大导致栈内存溢出,不太容易出现。
-
线程运行诊断
-
CPU占用过多:
- Linux下top命令定位哪个进程对cpu的占用过高
- ps进一步定位哪个线程引起的cpu占用过高 ps H -eo pid,tid,%cpu | grep 进程号
- jstack 进程ID 展示的nid(native thread id)是16进制,要将10进制转成16进制,可以根据线程ID找到有问题的线程,进一步定位到问题代码的源码行数。
-
程序运行很长时间没有结果:
jstack 排查 (死锁)
-
3、本地方法栈
作用:给本地方法的运行提供运行的内存空间
native method 不是由java代码编写的方法,如不能直接与操作系统底层交互。
Java关键字 native,如Object类里的public native int hashCode();
4、堆
Heap 堆:通过new关键字创建的对象都会使用堆内存
特点:
- 它是线程共享的,堆中对象都需要考虑线程安全的问题
- 有垃圾回收机制
堆内存溢出(java.lang.OutOfMemoryError: Java heap space)
指定堆内存大小 虚拟机参数 -Xmx size
堆内存诊断
-
jps工具
查看当前系统中有哪些java进程
-
jmap工具
jmap -heap 进程id,查看某一时刻堆内存占用情况
-
jconsole工具,jvisualvm(堆dump)
图形界面的,多功能的检测工具,可以连续监测
5、方法区
方法区是规范,永久代(JDK8以前)或元空间是实现,用于存储类的数据
方法去内存溢出:
- 1.8以前会导致永久代内存溢出(java.lang.OutOfMemoryError: PermGen space)
- 1.8之后会导致元空间内存溢出(java.lang.OutOfMemoryError: Metaspace)
指定永久代内存大小 虚拟机参数 -XX:MaxPermSize=size
元空间默认使用系统内存,没有设置上限
指定元空间内存大小 虚拟机参数 -XX:MaxMetaspaceSize=size
运行时常量池
javap -v xxx.class 反编译class文件的详细信息
二进制字节码(类基本信息,常量池,类方法定义,包含了虚拟机指令)
- 常量池,就是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等信息
- 运行时常量池,常量池是*.class文件中的,当该类被加载,它的常量池信息就会放入运行时常量池,并把里面的符号地址变为真实地址
StringTable(俗称:串池)
数据结构上是hashtable结构,不能扩容,取值唯一。
特性:
- 常量池中的字符串仅是符号,第一次用到时才变成对象
- 利用串池的机制,来避免重复创建字符串对象
- 字符串变量拼接的原理是StringBuilder(1.8)
- 字符串常量拼接的原理是编译期间优化
- 可以使用Intern方法,主动将串池中还没有的字符串对象放入串池。
- 1.8 将这个字符串对象尝试放入串池,如果有则不会放入串池,如果没有则放入串池,会把串池中的对象返回
- 1.6 将这个字符串对象尝试放入串池,如果有则不会放入串池,如果没有则会把此对象复制一份放入串池,会把串池中的对象返回
位置:
1.6:永久代 ,垃圾回收需要full GC
1.8:堆,垃圾回收minor GC即可
性能调优:
指定StringTable大小 虚拟机参数 -XX:StringTableSize=桶个数 buckets 桶数调大减少hash冲突
考虑将字符串对象是否入池
6、直接内存
操作系统的内存
Direct Memory
- 常见于NIO操作时,用于数据缓冲区
- 分配回收成本较高,但读写性能高
- 不受JVM内存回收管理
Java里UnSafe对象可以分配(allocateMemory,setMemory方法)、释放(freeMemory方法)直接内存。Java垃圾回收只限于Java内存
分配和回收原理:
- 使用了Unsafe对象完成直接内存的分配回收,并且回收需要主动调用freeMemory方法
- ByteBuffer的实现类内部,使用了Cleaner(虚引用)来检测ByteBuffer对象,一旦ByteBuffer对象被垃圾回收,那么就会由ReferenceHandler线程通过Cleaner的clean方法调用freeMemory来释放直接内存
System.gc()是Full GC 虚拟机参数 -XX:+DisableExplicitGC 可以禁用显示的垃圾回收
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南