jvm参数调优(持续更新)
JVM虚拟机
jvm调试参数
idea jvm参数设置入口


- -Xss 等价 -XX {数字} 调整栈的运行大小 如
-XX:128k递归函数如f(n)=f(n-1)+f(n-2),f(n-1)和f(n-2)的内存都会算在f(n)栈里,所以若栈太深,会报stack overflow error - -Xms(Initial Heap Size)
作用:设置 JVM 初始堆内存大小(程序启动时分配的堆内存)。
默认值:物理内存的 1/64(例如,8GB 内存的机器默认初始堆约为 128MB)。
示例:-Xms512m 表示初始堆大小为 512MB。 - -Xmx(Maximum Heap Size)
作用:设置 JVM 最大堆内存大小(程序运行期间堆内存的上限)。
默认值:物理内存的 1/4(例如,8GB 机器默认最大堆约为 2GB)。
示例:-Xmx2g 表示最大堆大小为 2GB。
-XX:+PrintStringTableStatistics 是 Java 虚拟机(JVM)中的一个非稳定参数(-XX 参数),用于在 JVM 终止时打印字符串常量池(String Pool)的统计信息,包括哈希表桶的数量、已使用的桶数量、字符串数量等。 - -XX:StringTableSize 是 Java 虚拟机(JVM)中的一个非稳定参数(-XX 参数),用于设置字符串常量池(String Pool)的哈希表桶数量,旨在优化字符串的存储和查找效率,减少哈希冲突,从而提升性能。使用方法:-XX:StringTableSize = {N}
- -XX:+DisableExplicitGC
启用后,JVM会忽略所有通过System.gc()或Runtime.getRuntime().gc()显式触发的GC请求。代码中调用这些方法将无任何效果,如同未调用一般。某些代码可能频繁调用System.gc()(如旧版资源清理逻辑),导致不必要的GC停顿。通过禁用显式GC,可减少性能波动。
cpu占用过高
- 当电脑程序占用cpu过高,也许是某些程序线程有问题,如何找到是哪个线程在作怪?
- top 是一个实时动态查看系统资源占用和进程状态的命令行工具,广泛用于 Linux/Unix 系统监控和性能分析。它能以全屏交互式界面显示关键系统指标(如 CPU、内存、负载等)和所有正在运行的进程的详细信息,并支持按需排序和过滤。
- 使用top指令查看占用cpu较多的进程
- ps H -eo pid,tid,%cpu |grep 某个程序pid 就可以查看该进程线程占用cpu比率
- jstack 是 Java 虚拟机(JVM)提供的命令行工具,用于生成当前 JVM 中所有线程的堆栈跟踪信息(Thread Dump)。它能帮助开发者快速诊断线程状态、死锁、阻塞、高 CPU 占用等问题。可以显示一个java进程中所有线程的信息,比如nid–>操作系统级别的线程ID,
nid是以十六机制表示的,通过显示信息可以定位到问题代码行数
使用方法: jstack {pid} 显示的nid是十六进制,需要执行转换
运行调试工具
- jsp 查看当前运行的java进程pid
- jmap 是 JDK 自带的命令行工具,用于生成 Java 进程的堆内存转储(Heap Dump)或分析内存映射信息**
jmap -heap {pid}**查看某进程堆状况jhsdb jmap --heap --pid XXX (对于jdk8之后的版本,不能再使用jmap -heap pid的命令了,需要使用上面的命令),下面指令一样适当改变 - jmap可用于保存 Java 虚拟机的堆快照(Heap Dump),其主要功能及使用方法如下:
生成堆转储快照(Heap Dump)
将 JVM 当前时刻的堆内存状态保存为二进制文件(.hprof),供后续分析内存泄漏、对象分布等问题。
支持两种模式:
全部对象:jmap -dump:format=b,file=<filename.hprof> <pid>
仅存活对象:jmap -dump:live,format=b,file=<filename.hprof> <pid>(live表示触发 Full GC 后仅保存存活对象,减少文件大小)。
- jconsole 是 JDK 自带的图形化监控工具,基于 JMX(Java Management Extensions) 规范构建,用于实时监控和管理 Java 虚拟机(JVM)的运行状态。它提供了直观的可视化界面,帮助开发者诊断性能问题、分析资源消耗,并支持本地和远程 JVM 监控。

- jvisualvm(Java VisualVM)是 JDK 自带的多功能图形化监控与分析工具,集成了性能监控、堆转储分析、线程诊断、MBean 管理等功能,适用于开发、测试及生产环境的 Java 应用分析。
jvisualvm工具在jdk1.8后默认不添加,需要手动下载
这里提供了了堆dump功能,堆导出,保存堆的快照,便于静态调试出出现问题的类
使用该功能后,跳转到以下界面,右侧有查找最大对象功能,便于找出内存消耗较大的类
- javap 是 JDK 自带的反汇编工具,用于分析 .class 文件的字节码、类结构及成员信息。通过它,开发者可以深入了解 Java 编译后的代码逻辑、JVM 执行机制,以及进行调试和优化。
- 查看字节码指令
使用 -c 选项反汇编方法代码,显示每条字节码指令(如 aload_0、invokevirtual),帮助理解编译器如何将源代码转换为 JVM 指令。- 使用 -v(或 -verbose)输出详细信息,包括常量池(字符串、类引用等)、方法签名、行号表(调试用)等。
示例:
javap -c HelloWorld.class
| 参数 | 效果 |
|---|---|
| -c | 反汇编方法代码,显示字节码指令。 |
| -p | 显示所有类成员(包括私有字段和方法)。 |
| -v | 输出详细信息(常量池、行号表、局部变量表等)。 |
| -s | 显示内部类型签名(JVM 规范格式)。 |
| -l | 输出行号表和局部变量表(调试用)。 |
| -classpath | 指定类路径(覆盖默认路径或 CLASSPATH 环境变量)。 |
方法区
方法区保存以下信息:
- 类的结构信息
- 运行时常量池
- JIT 编译后的代码
- 其他元数据
- 方法区逻辑上是堆的一部分,不过这知识一个规范,jvm厂商不一定以这种方式实现。
- 永久代:在JDK 8之前,方法区在HotSpot虚拟机中被称为永久代(PermGen),它位于堆内存中,与新生代、老年代共享堆空间。但永久代有固定大小,容易因类加载过多导致内存溢出(OutOfMemoryError: PermGen space)。
- 元空间:从JDK 8开始,方法区被元空间(Metaspace)取代。元空间使用本地内存而非堆内存,极大缓解了类加载造成的内存溢出问题。元空间的大小可以通过JVM参数(如-XX:MetaspaceSize和-XX:MaxMetaspaceSize)进行调整。
当方法区内存不足时,也会抛出OutOfMemoryError报错
HotSpot虚拟机内存示意图:
如图hotspot虚拟机元空间使用的是操作系统内存,一般不会溢出
常量池
常量池包括 运行时常量池, Class 文件常量池(静态常量池)、字符串常量池 和 基本类型包装类常量池
使用javap -v查看反编译代码如下图,红色框内即为class文件常量池(静态常量池)
当运行程序时,*.class被加载,这些常量会被加载到内存,也就是运行时常量池,然而加载到内存并不是将这些类似#1 #2的标号加载进入内存,而是其真实的内存地址,虚拟机根据内存地址直接找到常量数据
- StringTable 串池
是运行时常量池的子集,专门用于存储字符串字面量。
JDK 1.6 及之前:字符串常量池位于方法区(永久代)。 JDK 1.7 及之后:移至堆内存,避免永久代内存溢出问题。
程序刚开始执行,串池为空,字符串加载到串池为懒加载,比如程序第一行是String s=“a”,此时jvm会去串池寻找是否存在该字符串(通过哈希快速查找),如果有,则直接把地址引用过去(这也就是为什么字符串不可更改的原因,字符串对象内容不可变,因此可以安全共享引用),如果没有则会通过Class 文件常量池间接获取字符串字面量,将其字符串常量加载到字符串常量池,并将其加载到局部变量表的slot栏里。
以下是某class文件反编译的字节码,其对应于源码String s=‘a’;
ldc表示加载,这里加载#2,jvm会先去字符串常量池查找是否存在该字符串,若没有,将其加载到StringTable,一开始所有字符串并没有都加载到串池,而是用到时才加载到串池,这是一种懒加载
现在分析以下代码,来判断s3和s2是否一样:
其反编译字节码为:
可以看到在执行s3字符串赋值时,与前两种大不相同,其调用StringBuilder无参构造器创建了StingBuilder,并且调用append方法添加字符串a和b,最后通过StringBuilder的toString方法返回给了s3,toString方法代码如下:
在 Java 中,通过 new 创建的实例对象绝大多数情况下确实是在堆(Heap)内存中分配的显然是在堆里创建了一个新的字符串对象,并且将地址传给了s3.显然s3不等于s2
- String a=“a”+“b” 编译优化
String a="a";
String b="b";
String c="a"+"b";
- String c=“a”+“b"反编译对应以下代码,可以看出并没有借助StringBuilder,这是jvm优化后结果,变量c一定是一个定值"ab”,所以优化为String c=“ab”
29: ldc #4 // String ab
31: astore 5
- 使用inter()方法主动将字符串放入串池
例子:s.inter() ===》返回值 为 String返回的一定是字符串常量池里的字符串
inter方法会先去字符串常量池里查看是否有该字符串,若有则直接返回字符串常量池的字符串;
若没有,则把当前的字符串放入常量池这个方法可以在出现大量相同字符串前后缀使用,引用同一个字符串,可以降低大量内存
判断下面两题结果:
例1:
public class Test1 {
public static void main(String[] args) {
String a=new String("a")+new String("b");
a.intern();
String c="ab";
System.out.println(a=="ab");
}
}
执行结果:true
例2:
public class Test1 {
public static void main(String[] args) {
String a=new String("a")+new String("b");
// a.intern();
String c="ab";
System.out.println(a=="ab");
}
}
执行结果:false
其结果不一样的原因为:使用new String + new String创建的字符串在堆里,在常量池里没有ab时可能会复用堆中的 String 对象(即直接让常量池引用堆对象) 在不同虚拟机里可能表现不一样,在hotspot是这样的,同时这是以1.8后为准,在1.6及以前,intern是直接复制一份到常量池,而不是引用
- 在jvm1.6前,StringTable串池在永久代,永久代在方法区里,然而永久代内存回收管理时间周期较长,当gc(内存回收)永久代时,StrinTable才会处理回收部分内存,在1.8后,StrinTable转到了堆里,同时,原来的永久代实现变为了元空间逻辑实现,元空间在系统内存。

StringTable性能调优
- -XX:StringTableSize=<自定义大小> 是 Java 虚拟机(JVM)中的一个非稳定参数(-XX 参数),用于设置字符串常量池(String Pool)的哈希表桶数量,旨在优化字符串的存储和查找效率,减少哈希冲突,从而提升性能。
使用方法: -XX:StringTableSize={N}
设置太少桶可能导致哈希碰撞严重,查找速度降低,太多则会占用更多内存,需要根据情况找到一种平衡; - 使用intern()进行字符串内存调优
十分重要的方法
假设有一个list不断的添加大量字符串对象,并且这些字符串有可能出现不少是重复的,我们可以选择使用
list.add({字符串对象}.intern()),现在你应该明白为什么intern()方法会返回字符串常量池的字符串了,这样,在遇到相同字符串后,可以直接复用常量池的字符串,而无需再开辟一个新空间创建一个字符串对象。
直接内存 DirectMemory
java本身是不具备调用磁盘的权力,而是由调用本地方法调用空间,而这个过程需要由用户态转到内核态,文件先读取到系统缓冲区,系统缓冲区是java不可以运行的,需再从系统缓冲区里读取到java的缓冲区(在堆内存里),这个过程进行了数据复制,浪费了不少时间。
使用直接内存,可以由java直接读取访问,这段内存系统,java都可以直接访问。减少了复制的操作,速度更快了。
直接内存有以下特点:
- 常见于NIO操作时,用于数据缓冲区
- 分配回收成本高,但读写性能强
- 不受jvm内存关联
直接内存回收机制
- 直接内存本身不直接受JVM垃圾回收器管理,但它是通过DirectByteBuffer对象引用的。当DirectByteBuffer对象被GC判定为不可达(即没有强引用指向它)时,GC会将其标记为可回收对象。
在DirectByteBuffer的构造过程中,会创建一个Cleaner对象(通过sun.misc.Cleaner或java.lang.ref.Cleaner),该Cleaner持有一个对直接内存的引用,并注册了一个回调函数(用于释放直接内存)。- 当直接内存无引用且找不到GC Root时,其关联的ByteBuffer对象会被垃圾回收器回收,由referenceHanbdler线程通过Cleaner的clean方法调用Unsafe.freeMemory()释放内存,但这一过程存在延迟性,并非由Unsafe直接主动回收无引用的内存。
- Cleaner机制触发回收
当DirectByteBuffer对象被GC回收时,Cleaner会被加入到一个待处理的队列中。
后台的ReferenceHandler线程(优先级较高)会处理这个队列,调用Cleaner的回调函数(即sun.misc.Cleaner的clean()方法),最终通过Unsafe.freeMemory()释放直接内存。
手动触发回收(不推荐)- 可以通过调用System.gc()建议JVM执行垃圾回收,但无法保证立即回收直接内存。
也可以通过反射调用Cleaner的clean()方法强制释放,但这种方式破坏了封装性,可能导致不可预测的行为。
使用ByteBuffer bytebuffer=ByteBuffer.allocateDirect(int capacity)可分配直接内存
回收直接内存有一定延迟,可以使用unsafe对象的freeMemory()方法来释放
垃圾回收GC
标记清除算法
- 标记阶段(Marking)
从GC Roots出发,递归遍历所有引用链,标记所有可达对象为“存活”。
使用三色标记法(Tri-Color Marking)优化并发标记过程:
白色:未被访问的对象(潜在垃圾)。
灰色:已被访问但未处理完引用的对象。
黑色:已被访问且所有引用已处理的对象(存活对象)。
初始时所有对象为白色,从GC Roots开始标记为灰色,逐步处理灰色对象的引用,最终未被标记为黑色的对象视为白色(垃圾)。- 清除阶段(Sweeping)
遍历堆内存,回收所有未被标记的白色对象。
部分算法(如标记-整理算法)会进一步压缩存活对象,减少内存碎片。
并发标记的修正(如CMS、G1收集器)
在并发标记过程中,用户线程可能修改对象引用关系,导致漏标或误标。
通过写屏障(Write Barrier)记录引用变化,并在重新标记阶段修正。标记整理算法
在标记清除后,整理内存碎片
判断是否为垃圾算法
- 引用计数法
判断当前变量是否在被引用,若没有被引用则回收;
缺点:不可对循环引用变量进行回收
- 可达性分析算法
通过一系列GC Roots(垃圾收集根对象)作为起点,遍历整个对象图,标记所有可达对象,未被标记的对象则视为不可达(即垃圾对象)。
GC Roots 的类型
GC Roots 是垃圾回收的起点,包括以下对象:
- 虚拟机栈(栈帧中的本地变量表)中引用的对象
例如:方法中局部变量引用的对象。- 方法区中类静态属性引用的对象
例如:类的静态字段引用的对象。- 方法区中常量引用的对象
例如:字符串常量池中的引用。- 本地方法栈中JNI(Native方法)引用的对象
例如:通过JNI调用的本地代码引用的对象。
Java虚拟机内部的引用
例如:基本类型对应的Class对象、系统类加载器等。- 其他临时性GC Roots
根据垃圾收集器和回收区域的不同,可能临时加入其他对象(如分代收集中的跨代引用)。
引用
- 强引用
Object obj = new Object(); // 强引用
强引用是通过new关键字直接创建的对象引用,它直接指向一个对象,只要强引用存在,对象就不会被垃圾回收器(GC)回收,即使内存不足(OOM)。
- 弱引用 WeakReference
弱引用通过WeakReference类实现,指向的对象会在下一次垃圾回收(GC)时被回收,无论内存是否充足。
import java.lang.ref.WeakReference;
public class WeakReferenceDemo {
public static void main(String[] args) {
Object obj = new Object(); // 强引用
WeakReference<Object> weakRef = new WeakReference<>(obj); // 弱引用
obj = null; // 断开强引用
System.gc(); // 提示GC运行(不保证立即回收)
// 检查弱引用是否被回收
System.out.println("Weak reference: " + weakRef.get()); // 可能输出null(对象已被回收)
}
}
- 软引用 SoftReference
- 软引用通过SoftReference类实现,指向的对象会在内存不足时被回收(比弱引用更“懒惰”)。
适用于大对象缓存或内存敏感但允许延迟回收的场景。- 核心特性
内存不足时回收:GC会优先回收软引用对象,但仅在内存紧张(如触发Full GC且仍空间不足)时才会回收。
典型用途:
大对象缓存:如图片、视频等占用内存较大的资源。
预加载数据:允许数据在内存不足时被释放,避免OOM。
public static void main(String[] args){
List<SoftRegerence<byte[]>> lsit=new ArrayList<>();
//假设只开辟了1MB堆内存,下面也不报错OutOfMemory
for(int i=0;i<8;i++){
SoftRegerence<byte[]> ref=new SoftRegerence<>(new byte[1024*1024];
}
for(SoftReference<byte[]> ref:list){
System.out.println(ref.get());
}
//最后只有最后一个不是null,其他都是null
软引用原理:list–>pSoftReference–>byte[]
第一个引用为list对SoftRegerence的强引用,第二是软引用
- 虚引用 PhantomReference
虚引用(Phantom Reference)是Java中最弱的一种引用类型,属于java.lang.ref包,需与引用队列(ReferenceQueue)配合使用。
核心用途
- 资源清理与释放
典型场景:释放文件句柄、数据库连接、堆外内存(如NIO的DirectByteBuffer通过Cleaner机制实现堆外内存回收)。
原理:在对象回收前,通过虚引用触发清理逻辑(如关闭文件、解锁资源),避免资源泄漏。- 对象回收跟踪
监控对象何时被GC回收,用于调试内存泄漏或分析对象生命周期。
与终结器(Finalizer)协同
替代finalize()方法(因finalize()性能差且易导致死锁),通过虚引用实现更安全的资源清理。
举个例子
直接内存使用虚引用的原理释放
ByteBuffer bytebuffer=ByteBuffer.allocateDirect(int capacity);
以下执行流程值得品味
虚引用(Cleaner)的绑定:
在 DirectByteBuffer 构造时,会创建一个 Cleaner 对象(继承自 PhantomReference),并将其注册到全局的 ReferenceQueue。 PhantomReference phantomRef = new PhantomReference<>(obj, queue);
Cleaner 内部持有一个 Runnable(Deallocator),用于释放堆外内存。
bytebuffer变量是局部变量,属于强引用,此时执行bytebuffer=null,DirectByteBuffer 对象仅剩虚引用(Cleaner)关联,此时对象变为可回收状态(但尚未被回收)。
当 JVM 发生 GC 时,DirectByteBuffer 对象会被标记为可回收。
虚引用的特性:GC 不会因为虚引用而延迟回收对象。对象被回收后,虚引用(Cleaner)会被加入 ReferenceQueue
JVM 内部有一个高优先级的 ReferenceHandler 线程,它会轮询 ReferenceQueue。
当发现 Cleaner 虚引用时,会调用其 clean() 方法。
clean() 方法执行 Deallocator.run(),通过 Unsafe.freeMemory() 释放堆外内存。
- 终结器引用
终结器引用(FinalReference) 是 Java 中一种特殊的引用类型,用于在对象被垃圾回收(GC)前执行清理逻辑(如调用 finalize() 方法)。它是 JVM 实现对象终结(Finalization)机制的核心组件,但因其存在性能问题和不确定性,已被官方弃用。通过重写finalize()方法实现,对象被回收前调用。执行时间不确定,且可能导致对象复活,已被try-with-resources取代。
终结器引用的本质
定义:
终结器引用是 java.lang.ref.FinalReference 类的实例(通常由 JVM 内部创建),用于跟踪需要调用 finalize() 方法的对象。
关联对象:
当一个类重写了 finalize() 方法时,JVM 会在对象被标记为可回收时,为其创建一个终结器引用,并将其加入内部的 终结器引用队列(FinalReferenceQueue)。
核心作用:在对象回收前执行清理逻辑
触发条件:
当对象没有强引用(且未被其他引用类型引用)时,JVM 会标记其为可回收,并启动终结流程。
执行流程:
第一次 GC:对象被标记为可回收,但不会立即回收。
创建终结器引用:JVM 为对象创建一个 FinalReference,并将其加入 FinalReferenceQueue。
调用 finalize():
独立的 Finalizer 线程(低优先级)从队列中取出终结器引用,调用对象的 finalize() 方法。
如果 finalize() 中重新引用了对象(如赋值给静态变量),对象会“复活”,避免被回收。
第二次 GC:
如果对象未复活,JVM 会再次标记其为可回收,并在本次 GC 中真正释放内存。
如果对象复活,终结流程终止,对象继续存活。
终结器引用的局限性
(1)性能问题
延迟回收:
对象需经过两次 GC 才能被真正回收,导致内存占用时间延长。
线程阻塞:
Finalizer 线程是单线程的,若某个 finalize() 方法执行缓慢或抛出异常,会阻塞后续对象的终结流程。
(2)不确定性
执行时机不可预测:
finalize() 的调用依赖 GC 和 Finalizer 线程的调度,可能长时间延迟甚至不执行(如 JVM 退出时)。
复活风险:
若 finalize() 中重新引用对象,会导致对象无法被回收,引发内存泄漏。
(3)官方弃用
Java 9+ 的警告:
finalize() 方法被标记为 @Deprecated,官方推荐使用以下替代方案:
try-with-resources:自动管理 AutoCloseable 资源(如文件流、数据库连接)。
Cleaner(虚引用):高效释放堆外内存(如 DirectByteBuffer)。
- 引用队列
引用队列(ReferenceQueue) 是一个与软引用(SoftReference)、弱引用(WeakReference)和虚引用(PhantomReference)配合使用的工具,用于跟踪被垃圾回收器(GC)回收的引用对象。
注意,是一个对象,那么这些引用也是占用内存的。它的核心作用是在引用对象被回收时接收通知,从而允许开发者执行后续的清理操作(如释放资源、更新缓存状态等)。
为什么需要引用队列?
软引用/弱引用:当它们指向的对象被GC回收时,引用对象本身(如SoftReference或WeakReference实例)会变为可回收状态,但开发者无法直接感知这一事件。核心目的是通过通知机制实现资源管理和引用对象的清理
虚引用:虚引用本身无法通过get()获取对象,它的唯一作用就是在对象被回收时通知引用队列。说白了,就是对引用对象做内存释放。
引用队列的作用:提供一个回调机制,让开发者在引用对象被回收时执行自定义逻辑(如资源清理、日志记录等)。软,弱引用可以不配置引用队列使用,但是虚引用和终结器引用必须配合引用队列使用
- 注册引用队列(以虚引用为例)
ReferenceQueue<Object> queue = new ReferenceQueue<>();
Object obj = new Object();
PhantomReference<Object> phantomRef = new PhantomReference<>(obj, queue);
引用队列的作用示例:
分代回收
Java管理的堆内存(Heap)主要分为新生代(Young Generation)和老年代(Old Generation),而新生代由伊甸园,幸存区From,幸存区To组成。
- 新生代先被分配到伊甸园,当伊甸园(Eden区)内存不足时,会触发Minor GC1,此时存活的对象会被放入幸存区(Survivor区)。在每次Minor GC(Young GC)执行完成后,幸存区的From和To指向的内存地址会来回交换,一个作为源,一个作为复制目的地址,每次都是从From复制到To(
From复制到To的对象内存是紧挨着的,这就是创建两个幸存区的意义:确保幸存区没有内存碎片),然后交换From和To,也就是说最后From幸存区指向的是存放着对象,整理后没有内存碎片的空间,而To就可以将其当作空。 - 幸存区里的对象会有存活次数(Age)的记录,每次minor gc后,只有从Eden区或当前Survivor区存活并复制到另一Survivor区的对象,其年龄(Age)才会加1。
- 幸存区的对象在存活一段时间后,有可能进入老年代,进入老生代有以下情况:
- 在minor gc后发现某个幸存区对象的age达到阈值(可以手动设置,最大为15),就将其转到老年代。
- 当伊甸园内存不足,并且幸存区内存也不够,则将Age大的对象放入老年代
- 在幸存区进入老年代过程,发现老年代内存不足,则触发Full GC,Full Gc会对老年代以及新生代内存回收,Full GC 会进行碎片整理,而 Minor GC 通常不会。
class文件
-
Minor GC(Young GC)是Java虚拟机中针对新生代(Young Generation)的垃圾回收机制,其核心作用是回收新生代中不再被引用的对象,释放内存空间。Minor Gc和Full GC都会进行STW(Stop The World)机制,STW(Stop The World)是Java虚拟机(JVM)垃圾回收(GC)过程中的一种关键机制,指在执行垃圾回收时,暂停所有用户线程,确保垃圾回收器能够安全、一致地操作堆内存。 ↩︎
更多推荐



所有评论(0)