OutOfMemory和StackOverflow的区别是什么

典型回答 OutOfMemory 是内存溢出错误,他通常发生在程序试图分配内存时,但是超出可用内存限制。这可能是因为程序使用了太多内存,或者由于内存泄漏而导致内存不断累积。 StackOverflow 是栈溢出错误,他通常发生在程序的调用栈变得过深时,如递归调用。每次函数调用都会在栈上分配一些内存,当递归调用或者函数调用层次过深时,栈空间会被耗尽,从而导致StackOverflowError。 ✅请分别写出一个Java堆、栈、元空间溢出的代码 OutOfMemory一般发生在Java的堆内存上,StackOverflow一般发生在Java的栈内存中。但是也不绝对,在栈上也可能发生OutOfMemory。 扩展知识 OutOfMemory OutOfMemory在具体报错上还有以下几种情况: Java Heap Space:这是最常见的OutOfMemoryError。它发生在Java堆内存不足,通常由程序中创建的对象过多或者单个对象太大引起。这种错误可能导致Java应用程序崩溃。 PermGen Space(在Java 7之前)或 Metaspace(在Java 8及更高版本):这种错误发生在永久代(Java 7之前)或元空间(Java 8及更高版本)不足。通常由于加载过多的类或创建过多的动态代理类等原因引起。 Native Heap:这种错误发生在本机堆内存不足。Java虚拟机使用本机代码(native code)来执行某些操作,如本机方法,这些操作可能会占用本机堆内存。 Direct Memory:这种错误发生在程序使用NIO(New I/O)库或直接内存缓冲区时,由于分配了过多的直接内存而耗尽。 GC Overhead Limit Exceeded:这个错误发生在垃圾收集器花费了太多时间进行垃圾回收,而没有足够的内存被释放。这通常是由于内存不足以满足垃圾收集需求而引起的。 Requested array size exceeds VM limit:这个错误发生在试图创建一个太大的数组,超过了虚拟机的限制。 Unable to create new native thread:这个错误发生在虚拟机无法创建更多的本机线程,通常由于操作系统限制引起。 栈上OOM 在《Java虚拟机规范》规定了:如果虚拟机的栈内存允许动态扩展,当扩展栈容量无法申请到足够的内存时,将抛出OutOfMemoryError异常。 在某些编程语言和运行时环境中,栈内存允许动态扩展,而不会固定在一个特定的大小。这种情况下,栈内存可以动态增加,以适应程序的需要。然而,这种实现在Java中并不常见,如我们常用的Hotspot虚拟机种,栈内存是有限且固定的,不能动态扩展。 所以,在HotSpot虚拟机中是不会出现因为栈空间不足而抛出OutOfMemoryError异常的情况的,只会发生StackOverflow。 真实排查过程 ✅OOM问题排查过程

March 22, 2026 · 1 min · santu

什么是safe point,有啥用?

典型回答 Safe Point(安全点)是JVM中的一个关键概念。官方的解释是(https://openjdk.org/groups/hotspot/docs/HotSpotGlossary.html): A point during program execution at which all GC roots are known and all heap object contents are consistent. From a global point of view, all threads must block at a safepoint before the GC can run. (As a special case, threads running JNI code can continue to run, because they use only handles. During a safepoint they must block instead of loading the contents of the handle.) From a local point of view, a safepoint is a distinguished point in a block of code where the executing thread may block for the GC. Most call sites qualify as safepoints. There are strong invariants which hold true at every safepoint, which may be disregarded at non-safepoints. Both compiled Java code and C/C++ code be optimized between safepoints, but less so across safepoints. The JIT compiler emits a GC map at each safepoint. C/C++ code in the VM uses stylized macro-based conventions (e.g., TRAPS) to mark potential safepoints. ...

March 22, 2026 · 1 min · santu

什么是逃逸分析?

典型回答 逃逸分析是Java HotSpot Server编译器中JIT优化的一个重要步骤。它在Java SE 6u23及以后的版本中默认启用。 ✅简单介绍一下JIT优化技术? 对象基于逃逸分析可以有三种状态:全局逃逸(GlobalEscape)、参数逃逸(ArgEscape)和无逃逸(NoEscape)。 全局逃逸(GlobalEscape):对象超出了方法或线程的范围,比如被存储在静态字段或作为方法的返回值。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class GlobalEscapeExample { private static Object staticObject; public void globalEscape() { staticObject = new Object(); // 这个对象赋值给静态字段,因此它是全局逃逸的 } } public static StringBuffer craeteStringBuffer(String s1, String s2) { StringBuffer sb = new StringBuffer(); sb.append(s1); sb.append(s2); return sb; } 如我们新建的staticObject就是全局逃逸的。以及下面的方法中的sb对象,也是全局逃逸的。 ...

March 22, 2026 · 1 min · santu

说一说JVM的并发回收和并行回收

典型回答 很多人知道并发和并行的概念,并发指的是一个CPU不断的通过时间片切换来执行不同的任务,并行是指多个CPU同时执行不同的任务。 ✅什么是并发,什么是并行? 在Java的GC中,也有并发回收和并行回收的概念,但是这两个概念和我们前面提到的并发和并行之间的区别没啥关系。 我们说的并行回收其实就是Parallel GC,具体到垃圾收集器的实现上,就是Parallel Scavenge,Parallel Old,ParNew等收集器。而我们说的并发回收比较典型的就是 Concurrenct Mark and Sweep GC,也就是我们常说的CMS,当然G1也是并发回收器 关于这几种垃圾收集器的介绍,可以看下面这篇文章,大家看完之后再继续看本文。 ✅新生代和老年代的垃圾回收器有何区别? 那么,其实我们所熟悉的并行回收器主要的关注目标是吞吐量(吞吐量=代码运行时间/(代码运行时间+垃圾收集时间),所以他会想尽办法最高效率的利用CPU时间来进行垃圾回收。所以他会在垃圾收集期间通常会暂停应用程序的执行(STW),以快速完成垃圾收集。 所以,并行回收器可以在多核处理器上显著提高垃圾收集的速度,尤其是在堆内存较大,且CPU资源充足的场景下效果最佳。 而并发回收器主要关注的目标是STW的时长,它允许垃圾收集线程在应用程序线程运行的同时执行部分垃圾收集工作,从而减少了STW的时间。并发回收期间,只有在特定的收集阶段会发生短暂的STW。 ✅什么是STW?有什么影响? 所以,并发回收器减少了应用程序的停顿时间,适用于需要较低延迟的应用场景。 在适用场景上,并行垃圾回收适用于对停顿时间(STW时间)要求不是非常严格,但希望最大化吞吐量的应用。而并发垃圾回收适用于对响应时间敏感的应用,如在线交易处理系统、游戏服务器等,这些应用需要尽可能减少GC引起的停顿。

March 22, 2026 · 1 min · santu

Java一定就是平台无关的吗?

典型回答 不一定! 虽然Java在诞生之初,设计的主要目标就是跨平台,并且也做了很多事情来让这个成为可能,也是很多年来Java的一个重要优势。 ✅Java是如何实现的平台无关? 其中比较重要的就是通过JVM的平台有关来实现了java语言的平台无关。也就是说只要设备上安装了相应平台的 JVM,Java 程序就可以在任何平台上运行,不需要做任何修改。这就是“一次编写,到处运行(Write Once, Run Anywhere,)”的理念。 但是,这个事儿在现如今为了适应云原生,推出了很多静态编译的手段。比如AOT和Native Image: 提前编译(AOT,Ahead-Of-Time Compilation):这是指将 Java 字节码编译成特定平台的机器码,而不是在运行时通过 JIT(Just-In-Time)编译器。这种方式可以减少 JVM 启动时间并提高程序运行效率。 使用 GraalVM 的 Native Image:GraalVM 允许将 Java 程序编译成所谓的“原生映像”(native image),这是一种直接运行在操作系统上,不需要 JVM 的程序。 ✅什么是AOT编译?和JIT有啥区别? 不管是通过AOT编译把代码翻译成机器码,还是Native Image来消除JVM,最终都是是平台相关的了。 扩展知识 为什么Java要走回头路? 明明Java是靠着跨平台,平台无关性起家的,并且这么多年来这一直都是他的一个非常大的优势, 为什么如今要走到一条平台有关的路上来呢? 主要是因为在当今的云原生 Serverless的环境下,平台无关已经不再那么重要了。而且很多时候我们的代码都是部署在k8s、docker等容器中的。所以很多时候我们都不太关心具体的部署工作,而是交给云服务商直接完成了。而至于底层的具体运行的环境差别并没有那么明显了。 而牺牲一定的平台无关性,可以带来更快地启动速度、更好的资源利用、提供更加简单的部署过程,何乐而不为呢? 所以,这并不是削弱了 Java 的平台无关性,而是为了在特定场景下获得更好的性能和资源利用率,选择了牺牲一定程度的平台无关性。

March 22, 2026 · 1 min · santu

为什么初始标记和重新标记需要STW,而并发标记不需要?

典型回答 CMS(Concurrent Mark-Sweep)和G1(Garbage-First)是两种常见的收集器,它们都旨在减少应用程序停顿时间。他们的GC过程采用三色标记法,把整个GC过程分为了初始标记、并发标记、重新标记、以及垃圾清理。 其中初始标记和重新标记都是需要STW的,而并发标记则不需要。 ✅什么是STW?有什么影响? 在初始标记阶段,针对根(GCRoot)直接引用的对象进行标记,这个过程也通常被叫做根扫描。为了防止在初始标记过程中根对象被修改,这个过程是STW的,虽然G1可以通过采用写屏障技术(https://www.yuque.com/hollis666/ec96i7/lva8a9gfhagbrw2g#CejOa )来获知对象是否发生了修改,但是因为大多数的GCRoot他并不是对象,所以无法被获知的,所以,这个阶段是需要进行STW的。 GC Root都有哪些: ✅JVM如何判断对象是否存活? 并发标记阶段。会标记所有从直接可达对象间接可达的对象,这个过程是不会STW的,也就是说用户的线程和GC的线程是并发执行的。这样可以最大限度的减少应用的停顿时间。 重新标记阶段,目的是修正并发标记阶段因应用程序继续运行而产生的任何变化(因为并发标记没有STW,所以会有变化)。此时,需要重新检查和更新那些在并发标记阶段可能发生变化的对象标记信息。重新标记是清理前的最后一次标记,需要确保这个过程的准确性,所以需要做STW来保证。 总之,三个阶段,为了提升性能肯定是能不STW就不STW,而最后一个阶段——重新标记因为是最终阶段,所以需要STW来确保准确性。而第一个阶段——初始标记,因为无法感知到GCRoot的变化,所以需要做STW来确保这个阶段的准确性。

March 22, 2026 · 1 min · santu

什么情况会导致JVM退出?

典型回答 程序正常运行完 当Java应用程序中的所有非守护线程(即用户线程)都完成执行且没有其他活动线程时,JVM会正常退出。这是最常见的退出方式。 ✅什么是守护线程,和普通线程有什么区别? System.exit() 被调用 当代码中的任何位置调用 System.exit(int status) 方法时,JVM会立即开始终止过程。这个方法可以接受一个状态码,通常用0表示正常退出,非0表示异常退出。 Runtime.getRuntime().halt() 被调用 与 System.exit() 不同,Runtime.getRuntime().halt(int status) 方法用于强制终止当前运行的Java虚拟机,而不会执行任何关闭钩子或者终止已注册的未捕获异常处理器。 遇到无法恢复的错误 当JVM遇到一个无法恢复的系统错误,如操作系统信号或内部错误,它可能会立即退出。比如JVM自身的bug或者本地方法库存在一些问题等。 但是需要注意的是,我们常见的一些ERROR,如OOM,并不会导致JVM立即退出: ✅Java发生了OOM一定会导致JVM 退出吗? 但是,在在一些极端情况下,比如元空间(Metaspace)耗尽或JVM本身的资源不足,JVM可能会处于无法恢复的状态,从而导致整个JVM进程终止。 还有就是,有些JVM参数配置可能会在遇到OOM时导致JVM终止。例如,-XX:OnOutOfMemoryError="; " 参数允许用户指定在遇到OOM错误时要执行的命令,这些命令可以包括终止JVM的命令。 如:XX:OnOutOfMemoryError="kill -9 %p" 接收到终止信号 在类Unix系统中,JVM进程可能会因为接收到某些类型的操作系统信号(如SIGKILL或SIGTERM)而立即退出。某些信号会导致JVM进行优雅地关闭,如执行关闭钩子,而某些信号则会导致JVM立即终止。 SIGTERM:kill -15 SIGKILL:kill -9 ✅对JDK进程执行kill -9有什么影响?

March 22, 2026 · 1 min · santu

什么是STW?有什么影响?

典型回答 STW,是Stop-The-World的缩写,Stop-The-World是指系统在执行特定操作时,必须暂停(停止)所有的应用程序线程。 比如在Java中,当需要进行垃圾回收的时候,垃圾回收器需要停止应用程序的所有线程,以便可以安全地识别和回收不再使用的对象。这个过程我们就会称之为是Stop The World了。 STW事件会暂时停止应用程序的运行。对于需要高响应性或实时性能的应用程序来说,这可能导致性能问题,因为它会导致响应延迟。 并且在在STW期间,应用程序的响应时间(RT)和吞吐量(QPS)都会受到影响,这可能导致性能的不可预测性,特别是在负载较高的情况下。 为了减少STW带来的影响,需要对垃圾收集器的配置进行调优,比如选择不同类型的垃圾收集器、调整堆大小或者垃圾收集器的其他参数。 比如选择并发回收器作为垃圾回收器,如CMS、G1等,因为并发回收器主要关注的目标是STW的时长,它允许垃圾收集线程在应用程序线程运行的同时执行部分垃圾收集工作,从而减少了STW的时间。并发回收期间,只有在特定的收集阶段会发生短暂的STW。 扩展知识 其他STW场景 除了GC中的STW事件外,STW术语也可能在其他上下文中使用。 比如: 操作系统和硬件升级/维护:在需要进行操作系统升级或硬件维护的情况下,可能需要将系统完全暂停,以确保安全地进行更新或维护。这种情况下的STW意味着所有运行在系统上的应用和服务都会被暂时停止。 数据库维护:在某些数据库操作中,如重组索引、执行某些类型的备份或升级数据库系统时,可能需要短暂停止数据库服务,这也可以被看作是一种STW事件,因为它会暂停所有数据库操作。 消息队列的重平衡:在分布式消息队列系统(如Kafka)中,重平衡是指在消费者群体中添加或删除消费者时重新分配分区的过程。这个过程中,消息的消费可能会暂时停止,直到重平衡完成,所有的分区都被正确地分配给新的消费者群体。 ✅什么是Kafka的重平衡机制?

March 22, 2026 · 1 min · santu

ZGC和CMS和G1的区别对比_

典型回答 ✅JDK 11中新出的ZGC有什么特点? ✅G1和CMS有什么区别? 上面两篇分别介绍过G1和CMS的区别,以及ZGC的特点,那么三个放到一起对比一下,先把G1和CMS的区别搞过过来,再加上ZGC的一些新东西。 特性 CMS G1 ZGC JDK版本 1.8及以前,(JDK14中被移除) 1.7+ (1.9+的默认GC) JDK 15+ 设计目标 低延迟,减少GC停顿时间 可预测的停顿时间,兼顾吞吐量和延迟 极低延迟(亚毫秒级停顿),支持超大堆 STW 部分STW(参考三色标记法) 部分STW(参考三色标记法) 几乎所有阶段都是并发执行的 分代情况 物理连续的年轻代+老年代 划分为等大小Region,逻辑分代 划分为等大小Region (JDK21前无分代,JDK21后支持分代) 回收位置 老年代 整堆 整堆 GC算法 标记-清除算法 区域化的标记-整理算法(按照Region) 并发标记-整理 + 可扩展的区域分配 碎片产生 存在内存碎片 可防止内存碎片产生 可防止内存碎片产生 可预测性 无法预测 G1的STW时长可预测 堆内存基本要求 一般要求不高 4G以上 超大堆(TB级别) 自适应调优 不支持 支持 核心技术 三色标记法 三色标记法,Region 染色指针、读屏障

March 22, 2026 · 1 min · santu

为什么JDK 1.8要废弃永久代,改用元空间

典型回答 我们知道,从JDK 1.8开始,HotSpot虚拟机对方法区的实现进行了重大改变。永久代被移除,取而代之的是元空间(Metaspace) 先说下他们的区别,然后你大概也就能懂了为啥要这么改了。 永久代是堆上的一部分,大小受堆大小的限制,容易发生OOM。元空间在本地内存上,不受堆大小的限制,不容易发生OOM。 因为方法区作为元空间和永久代的主要区域,他里面存储了大量的被加载的类,而很多时候,项目中用了很多动态生成类的框架之后,比如Spring、等等,就会占用很多空间,如果在堆上,就会导致频繁的GC,以及OOM的问题。 而把方法区放到元空间,即放到堆外内存中, JVM 可以根据机器内存大小动态扩展元空间的大小,减少OOM的发生。 我还在其他资料上,看到有人提过,说不同 JVM 实现(比如 Oracle JDK、IBM J9)对 PermGen 细节不同,导致移植性很差。而且HotSpot 团队自己也觉得 PermGen 的管理逻辑太混乱,扩展维护困难。(这两点知道即可,不用深究)

March 22, 2026 · 1 min · santu

留言给博主