JVM(二) --- JVM的内存结构
写在文章前:本系列博客是学习黑马程序员JVM完整教程所做笔记,若有错误希望大佬们评论修正
一.概述
JVM的内存结构包括程序计数器(PC Register),虚拟机栈(JVM Stacks),堆内存(heap),方法区(Method Area),本地方法区(Native Method Stacks)
二.程序计数器
- 定义:Program Counter Register 程序计数器(寄存器)
- 作用:记录下一条JVM指令的内存地址
- 特点:① 线程私有的,每个线程都有自己的程序计数器 ②不会存在内存溢出
三.虚拟机栈
定义:Java Virtual Machine Stacks (Java 虚拟机栈)
- 每个线程运行时所需的内存,称为虚拟机栈
- 每个栈由多个栈帧组成,一个栈帧对应一个方法调用。
- 栈帧主要记录方法参数,局部变量,返回地址等。
- 每个栈只有一个活动栈帧,对应着正在执行的那个方法
public class Demo1_1 { public static void main(String[] args) throws InterruptedException { method1(); } private static void method1() { method2(1, 2); } private static int method2(int a, int b) { int c = a + b; return c; } }
问题辨析:
- 如果方法内局部变量没有逃离方法的作用范围,则线程安全
- 如果局部变量引用了对象,并逃离了作用范围,则需要考虑线程安全
栈内存溢出问题:
java.lang.StackOverflowError
- 栈帧过多导致栈内存溢出(例如方法的递归调用而没有终结点),
-Xss256k 设置JVM栈内存参数
- 栈帧过大导致栈内存溢出(不常见)
线程运行时诊断:
- 用top定位哪个进程对cpu的占用过高
- ps H -eo pid,tid,%cpu | grep 进程id (用ps命令进一步定位是哪个线程引起的cpu占用过高)
- jstack 进程id 可以根据线程id 找到有问题的线程,进一步定位到问题代码的源码行号

四.本地方法栈
- 本地方法 -- 指那些不是用java编写的方法。JVM会为它们申请一些内存
五.堆
定义:heap 堆
- 通过new关键字,创建对象都会使用堆内存
特点:
- 堆内存是线程共享的,堆中对象需要考虑线程安全问题
- 有垃圾回收机制
堆内存溢出:
java.lang.OutOfMemoryError: Java heap space
-
-Xmx8m 设置JVM堆内存大小
堆内存诊断:
jps 工具 查看当前系统中有哪些 java 进程
jmap 工具 查看堆内存占用情况 jmap - heap 进程id (不能连续监测)
jconsole 工具 图形界面的,多功能的监测工具,可以连续监测
jvisualvm工具
六.方法区
The Java Virtual Machine has a method area that is shared among all Java Virtual Machine threads. The method area is analogous to the storage area for compiled code of a conventional language or analogous to the "text" segment in an operating system process. It stores per-class structures such as the run-time constant pool, field and method data, and the code for methods and constructors, including the special methods used in class and instance initialization and interface initialization.
The method area is created on virtual machine start-up. Although the method area is logically part of the heap, simple implementations may choose not to either garbage collect or compact it. This specification does not mandate the location of the method area or the policies used to manage compiled code. The method area may be of a fixed size or may be expanded as required by the computation and may be contracted if a larger method area becomes unnecessary. The memory for the method area does not need to be contiguous.
A Java Virtual Machine implementation may provide the programmer or the user control over the initial size of the method area, as well as, in the case of a varying-size method area, control over the maximum and minimum method area size.
The following exceptional condition is associated with the method area:
-
If memory in the method area cannot be made available to satisfy an allocation request, the Java Virtual Machine throws an
OutOfMemoryError
.
组成:
方法区内存溢出:
- 1.8 以前会导致永久代内存溢出
* 演示永久代内存溢出 java.lang.OutOfMemoryError: PermGen space
* -XX:MaxPermSize=8m
- 1.8 之后会导致元空间内存溢出
* 演示元空间内存溢出 java.lang.OutOfMemoryError: Metaspace
* -XX:MaxMetaspaceSize=8m
二进制字节码的的组成:
类的基本信息、常量池、类的方法定义(包含了虚拟机的指令)
通过反编译查看字节码文件:
类的基本信息:
常量池:
public class HelloWorld { public static void main(String[] args) { System.out.println("hello world"); } }
常量池:
- 就是一张表如上图的(constant pool),虚拟机指令根据这张常量表找到要执行的类名,方法名,参数类型,字面量信息
运行时常量池:
- 常量池是在.class文件中,当这个类被加载以后,它的常量池信息就会被放入运行时的常量池,并把里面的符号地址变为真实地址
常量池与StringTable的关系:
StringTable串池
特性:
- 常量池中的字符串仅是符号,只有在被用到时才会转化为对象
- 利用串池的机制避免重复创建字符串对象
- 字符串变量拼接的原理是StringBuilder
- 字符串常量拼接的原理是编译器优化
- 可以使用intern(),主动将串池中没有的字符串对象放入串池 。1.8 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 会把串池中的对象返回
// 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 在编译期间的优化,结果已经在编译期确定为ab System.out.println(s3 == s5); } }
StringTable垃圾回收
StringTable在内存紧张时会发生内存回收
StringTable调优
- 因为StringTable是由HashTable实现的,所以可以适当增加HashTable中桶的个数,来减少字符串放入串池的事件 (-XX:StringTableSize=xxxx)
- 考虑是否需要字符串对象入池 (可以通过intern方法减少重复入池)
七.直接内存
Direct Memory
- 常见于NIO操作,用于数据缓冲区
- 分配回收成本高,但读写性能强
- 不受JVM内存回收管理
文件读写流程
使用了ByteBuffer
直接内存是java程序和操作系统都可以访问的一块区域,无需再将数据从系统内存复制到java的堆内存,从而提高了效率
释放原理
直接内存的回收不是JVM的垃圾回收来释放的,而是通过unsafe.freeMemory来手动释放
通过
//通过ByteBuffer申请1M的直接内存 ByteBuffer byteBuffer = ByteBuffer.allocateDirect(_1M);
申请直接内存,但JVM并不能直接回收直接内存中的内容,它是如何实现回收的呢?
查看allocateDirect的实现
public static ByteBuffer allocateDirect(int capacity) { return new DirectByteBuffer(capacity); }
DirectByteBuffer类
DirectByteBuffer(int cap) { // package-private super(-1, 0, cap, cap); boolean pa = VM.isDirectMemoryPageAligned(); int ps = Bits.pageSize(); long size = Math.max(1L, (long)cap + (pa ? ps : 0)); Bits.reserveMemory(size, cap); long base = 0; try { base = unsafe.allocateMemory(size); //申请内存 } catch (OutOfMemoryError x) { Bits.unreserveMemory(size, cap); throw x; } unsafe.setMemory(base, size, (byte) 0); if (pa && (base % ps != 0)) { // Round up to page boundary address = base + ps - (base & (ps - 1)); } else { address = base; } cleaner = Cleaner.create(this, new Deallocator(base, size, cap)); //通过虚引用,来实现直接内存的释放,this为虚引用的实际对象 att = null; }
这里调用了Cleaner的create方法,且后台线程还会对虚引用的对象进行监测。如果虚引用的实际对象(这里是ByteBuffer)被回收后,就会调用Cleaner的clean方法,来清除直接内存中占有的内存
public void clean() { if (remove(this)) { try { this.thunk.run(); //调用run方法 } catch (final Throwable var2) { AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { if (System.err != null) { (new Error("Cleaner terminated abnormally", var2)).printStackTrace(); } System.exit(1); return null; } }); }
对应对象的run方法
public void run() { if (address == 0) { // Paranoia return; } unsafe.freeMemory(address); //释放直接内存中占用的内存 address = 0; Bits.unreserveMemory(size, capacity); }
分配和回收原理
- 使用Unsafe类来完成直接内存的分配回收,回收需要主动调用freeMemory方法
- ByteBuffer内部使用了Cleaner(虚引用)来监测ByteBuffer。一旦ByteBuffer被垃圾回收,那么会由ReferenceHandler线程来调用Cleaner的clean方法调用freeMemory来释放内存
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)