内存结构
程序计数器
1、Program Counter Register:程序计数器(寄存器)
(1)程序计数器是 Java 对物理硬件的屏蔽与抽象
(2)物理上,通过寄存器实现程序计数器
2、作用:记录下一条 JVM 指令的执行地址
(1)解释器会解释指令为机器码,交给 CPU 执行,程序计数器记录下一条指令的地址行号,下一次解释器会从程序计数器拿到指令,然后进行解释执行
(2)多线程的环境下,如果两个线程发生上下文切换,则程序计数器记录线程下一行指令的地址行号,便于继续往下执行
4、特点
(1)线程私有
(2)在 JVM 中,唯一一个,不会出现内存溢出的部分
虚拟机栈
1、定义
(1)每个线程运行需要的内存空间,称为虚拟机栈
(2)每个栈由多个栈帧(Frame)组成,对应着每次调用方法时,所占用的内存(参数、局部变量、返回地址等)
(3)每个线程只能有一个活动栈帧,对应着当前正在执行的方法
2、解析
(1)垃圾回收不涉及栈内存:栈内存由方法调用产生,方法调用结束后会自动弹出栈
(2)栈内存并非分配越大越好:因为物理内存是一定的,栈内存越大,可以支持更多的递归调用,但是可执行的线程数就会越少,一般采用系统默认大小
(3)如果方法局部变量没有逃离方法的作用范围,它是线程安全的
(4)需要考虑线程安全:局部变量引用对象(引用传参)、逃离方法的作用范围(作为返回值)
3、栈内存溢出(java.lang.stackOverflowError)
(1)栈帧过多
(2)栈帧过大:较少出现
(3)第三方类库操作
(4)-Xss:指定栈内存大小
4、本地方法栈
(1)带有 native 关键字的方法
(2)需要 Java 调用本地 C / C++ 方法,因为 Java 有时无法直接与操作系统底层交互,所以需要用到本地方法栈,服务于带 native 关键字的方法
堆
1、Heap
2、定义:通过 new 创建的对象,都会存放在堆内存
3、特点
(1)它是线程共享的,堆中对象都需要考虑线程安全的问题
(2)拥有垃圾回收机制
4、堆内存溢出
(1)java.lang.OutofMemoryError: java heap space
(2)-Xmx:指定堆内存大小
5、堆内存诊断工具
(1)jps:命令行,查看当前系统中的 Java 进程
(2)jmap:命令行,jmap - heap 进程 id,查看堆内存占用情况
(3)jconsole:图形界面,多功能的监测工具,可以连续监测
(4)jvisualvm:图形界面,类似 jconsole,可以进行堆转储(Dump)
方法区
1、定义(Java 1.8)
(1)JVM 有一个方法区,在所有 JVM 线程之间共享
(2)方法区类似于传统语言的编译代码的存储区,或类似于操作系统进程中的“text”段
(3)存储每个类的结构,如:运行时常量池、字段、方法数据、方法和构造函数的代码,包括用于类和实例初始化,以及接口初始化的特殊方法
(4)方法区在虚拟机启动时被创建,尽管方法区在逻辑上是堆的一部分,但简单的实现可以选择不对其进行垃圾收集或压缩
(5)规范没有规定方法区的位置,或用于管理编译代码的策略
(6)方法区可以是一个固定的大小,也可以根据计算的需要进行扩展,如果不需要更大的方法区,也可以进行收缩
(7)方法区的内存不需要是连续的
(8)JVM 的实现可以为程序员或用户提供对方法区初始大小的控制,以及在不同大小的方法区中,控制最大、最小方法区大小
(9)如果方法区的内存不能被用来满足分配请求,JVM 会抛出 OutOfMemoryError
2、方法区为逻辑上的概念
(1)Java 8 之前,永久代实现方法区,属于 JVM 内存结构
(2)Java 8 之后(包括 8),元空间实现方法区,属于本地内存
3、方法区内存溢出
(1)1.8 之前,导致永久代内存溢出
(2)-XX:MaxPermSize=:指定永久代内存大小
(3)1.8 之后,导致元空间内存溢出
(4)-XX:MaxMetaspaceSize=:指定元空间大小
(5)实际场景:动态加载类,如:Spring、MyBaits
4、运行时常量池
(1)二进制字节码:类的基本信息、常量池、类方法定义(包含虚拟机的指令)
(2)每条指令都对应常量池表中的一个地址,常量池表中的地址可能对应着一个类名、方法名、参数类型等信息
(3)常量池即为一张表,虚拟机指令根据常量池表,查找执行的类名、方法名、参数类型、字面量等信息
(4)常量池在 .class 文件中,当该类被加载,其常量池信息被放入运行时常量池,并把其中符号地址转换为真实地址
(5)方法区内部包含运行时常量池(Runtime Constant Pool)
字符串常量池
1、StringTable
2、常量池、字符串常量池的关系
(1)常量池中的信息,加载到运行时常量池中,为常量池中的符号,未转变为 Java 字符串对象
(2)虚拟机指令 ldc,会将符号转变为字符串对象,并且为延迟加载
3、特性
(1)常量池中的字符串仅是符号,第一次使用时才转换为字符串对象
(2)利用串池的机制,来避免重复创建字符串对象
(3)字符串变量拼接原理(JDK 1.8):StringBuilder、append()、toString()
(4)字符串常量拼接原理:Java 编译器(前端编译器)在编译期优化
(5)使用 intern(),把有新字面值的字符串地址驻留到常量池里
4、位置
(1)JDK 1.6 之前:在永久代中的运行时常量池
(2)JDK 1.7 之后:在堆中
5、性能调优
(1)StringTable 由 HashTable 实现,可以适当增加 HashTable 桶的个数,以减少字符串放入串池所需时间
(2)-XX:StringTableSize=桶个数(最少设置为 1009 以上)
(3)考虑是否需要将字符串对象入池
(4)通过 intern() 减少重复入池
直接内存
1、Direct Memory
(1)不属于 JVM 组成
(2)由操作系统直接管理
2、定义
(1)常见于 NIO 操作时,用于数据缓冲区
(2)分配回收成本较高,但读写性能高
(3)不受 JVM GC 管理,存在内存泄漏、内存溢出
3、文件 I/O 过程
(1)Java 不具备磁盘读写能力,调用磁盘读写,需要调用操作系统提供的函数
(2)CPU:用户态(Java 方法)-> 内核态(System 本地方法)
(3)内存:读取磁盘文件 -> 在操作系统中,划出系统缓冲区,多次分批读取磁盘 -> Java 堆内存中,划分出 Java 缓冲区,多次分批读取内存
(4)Java 不能直接操作文件管理,需要切换到内核态,使用本地方法进行操作,然后读取磁盘文件,会在系统内存中创建一个缓冲区,将数据读到系统缓冲区, 然后在将系统缓冲区数据,复制到 Java 堆内存中
(5)缺点:数据存储两份,分别在系统内存、Java 堆,造成了不必要的复制
4、使用直接内存,文件 I/O 过程
(1)CPU:用户态(Java 方法)-> 内核态(System 本地方法)
(2)内存:在操作系统中,划分出直接内存,如:ByteBuffer 中的 public static ByteBuffer allocateDirect(int capacity)
(3)直接内存:操作系统、Java 代码都可以访问,无需将代码从系统内存,复制到 Java 堆内存,从而提高效率
直接内存回收原理
1、ByteBuffer 类
(1)allocateDirect 方法
public static ByteBuffer allocateDirect(int capacity) {
//allocateDirect的实现,底层创建一个DirectByteBuffer对象
return new DirectByteBuffer(capacity);
}
2、DirectByteBuffer 类
(1)构造器
// Primary constructor
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;
//调用Unsafe类的allocateMemory、setMemory,完成直接内存的分配
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;
}
//ByteBuffer实现内部,使用Cleaner(虚引用)检测ByteBuffer是否被回收
//通过虚引用,实现直接内存的释放,this为虚引用的实际对象, 第二个参数是一个回调,实现Runnable接口,run方法中通过unsafe释放内存
//后台线程监测虚引用的对象,如果虚引用的实际对象(此处为DirectByteBuffer)被回收后,则调用Cleaner的clean方法,清除直接内存中占用的内存
cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
att = null;
}
3、Clean 类
(1)继承 PhantomReference,为虚引用类型
public class Cleaner extends PhantomReference<Object>
(2)create 方法
//通过虚引用,实现直接内存的释放,var0为虚引用的实际对象, var1是一个回调,实现Runnable接口,run方法中通过unsafe释放内存
public static Cleaner create(Object var0, Runnable var1) {
return var1 == null ? null : add(new Cleaner(var0, var1));
}
(3)clean 方法
//ReferenceHandler(守护线程)监测虚引用的对象,如果虚引用的实际对象被回收后,则调用Cleaner的clean方法
public void clean() {
if (remove(this)) {
try {
//在ByteBuffer中,thunk为Deallocator对象,其run方法会释放直接内存
this.thunk.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;
}
});
}
}
}
(4)thunk 属性
private final Runnable thunk;
4、Deallocator 类
(1)DirectByteBuffer 的静态内部类
(2)实现 Runnable 接口,为回调任务对象
private static class Deallocator implements Runnable {
private static Unsafe unsafe = Unsafe.getUnsafe();
private long address;
private long size;
private int capacity;
private Deallocator(long address, long size, int capacity) {
assert (address != 0);
this.address = address;
this.size = size;
this.capacity = capacity;
}
public void run() {
if (address == 0) {
// Paranoia
return;
}
//主动调用freeMemory,释放直接内存
unsafe.freeMemory(address);
address = 0;
Bits.unreserveMemory(size, capacity);
}
}
5、Unsafe 类
(1)底层类,分配、释放直接内存,不建议使用,由 JDK 开发人员使用
(2)使用 Unsafe 类,完成直接内存的分配、回收,回收不是由 JVM GC 释放,而是需要主动调用 freeMemory 方法
(3)分配内存
//返回值:直接内存的地址
public native long allocateMemory(long var1);
public void setMemory(long var1, long var3, byte var5) {
this.setMemory((Object)null, var1, var3, var5);
}
public native void setMemory(Object var1, long var2, long var4, byte var6);
(4)释放内存
public native void freeMemory(long var1);
6、禁用显式回收
(1)一般 JVM 调优时,加上参数 -XX:+DisableExplicitGC,表示禁止显示 GC,即 System.gc() 无效
(2)如:使手动 System.gc() 无效,它是一种 Full GC,会回收新生代、老年代,造成程序执行时间较长
(3)可能影响回收直接内存,只能等待 JVM 自动 GC,所以需要通过 Unsafe 对象,调用 freeMemory 方法释放内存
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战