什么是JVMJVM的作用是Java程序的核心运行环境负责将Java字节码.class文件解释或编译为底层操作系统和硬件能理解的指令。JVM是Java实现“一次编写到处运行”跨平台的核心机制。JVM的运行时数据区域区域名称线程共享性存储内容是否内存溢出OutOfMemoryError是否栈溢出StackOverflowError其他特点程序计数器线程私有当前线程执行的字节码指令地址否否唯一无OOME的区域虚拟机栈线程私有栈帧局部变量表、操作数栈、动态链接、方法返回地址是 。不停创建线程是。无限递归每个方法对应一个栈帧深度超出限制时抛出SOE。本地方法栈线程私有Native方法的调用信息是 。同上是。同上。与虚拟机栈类似但服务于Native方法如C/C代码。堆线程共享对象实例、数组通过new创建的对象是。无限创建对象否GC主要区域通过-Xms和-Xmx配置大小。方法区元空间线程共享类信息、运行时常量池、静态变量、即时编译后的代码是否JDK8后改为元空间Metaspace使用本地内存通过-XX:MetaspaceSize配置。运行时常量池线程共享字面量如字符串常量、符号引用类/方法/字段的引用是属于方法区。String.intern() 驻留字符串否动态扩展如String.intern()方法添加字符串。直接内存线程共享通过DirectByteBuffer分配的堆外内存用于NIO操作是 。分配 DirectByteBuffer否不属于JVM运行时数据区但受-XX:MaxDirectMemorySize限制。StackOverflowError和OutOfMemoryErrorStackOverflowError•触发原因由线程的栈深度超过JVM允许的最大限制引起通常由无限递归或过深的方法调用导致。•发生区域虚拟机栈线程私有。每个方法调用会在栈中压入栈帧栈帧过多导致栈空间耗尽。OutOfMemoryError•触发原因JVM无法分配足够的内存空间可能发生在堆、方法区或线程创建时无法分配栈内存。•发生区域◦堆对象实例分配失败最常见。◦方法区加载过多类或常量如Java 8的Metaspace。◦虚拟机栈线程创建时无法分配足够的栈内存如线程过多或单个栈设置过大。堆内存中分为哪些区域因为大多数对象“朝生夕死”JVM 堆内存通过分代设计根据对象的存活时间将分为两块堆内存区域“新生代”和“老年代”。这样JVM对新生代和老年代的对象使用不同的垃圾回收策略以此优化垃圾回收效率•新生代高频 Minor GC 快速回收短期对象。•老年代低频 Major GC 处理长期存活对象。区域描述生命周期垃圾回收机制触发 GC 的条件新生代存放新创建的对象绝大多数对象在此区域被快速回收。短对象存活时间短Minor GC复制算法新生代空间不足时触发。▸Eden 区对象初次分配的区域占新生代的绝大部分空间默认 80%。极短多数对象用完即死回收后存活对象复制到 Survivor 区。Eden 区满时触发。▸Survivor 区分为From和To区存放从 Eden 区或另一 Survivor 区存活的对象。中等需通过多次 Minor GC 存活对象在 Survivor 区之间复制年龄增长。当 Survivor 区不足以存放存活对象时触发。老年代存放长期存活的对象如缓存、全局变量或大对象直接分配。长对象存活时间久Major GC / Full GC标记-清除/整理算法老年代空间不足时触发。元空间Metaspace注意Java 8 后不属于堆内存而是本地内存存储类元数据、方法信息等。长与类生命周期一致无显式 GC依赖 JVM 自动管理。元空间内存不足时触发 Full GC。对象的分配规则和触发垃圾回收条件Eden与Survivor区默认8:1:1新生代的高频垃圾回收叫做Minor GC。老年代的低频垃圾回收叫做Major GC。对象分配规则先判断对象是否无逃逸如果无逃逸而且栈空间够用则直接在栈上分配判断是否为大对象如果是直接分配到老年代。判断是否有线程本地分配缓冲区TLAB如果有的话则直接在Eden区分配内存。如果没有则要通过CAS的方式在Eden区分配内存对象逃逸分析和标量替换逃逸分析如果对象仅在当前方法内部使用未传递到外部JVM 直接在栈帧中分配内存随栈帧销毁自动回收无需 GC。标量调换在确定对象无逃逸后JVM 将对象拆解为独立的标量基本类型或引用直接分配在栈或寄存器中避免创建完整的对象。优点1.无需连续空间。2. 消除对象头内存占用线程本地分配缓冲区TLAB线程本地分配缓冲区Thread-Local Allocation Buffer, TLAB​​ 是 JVM 为每个线程在年轻代的 ​Eden 区​ 中分配的一块私有内存区域。​核心目的减少多线程环境下对象分配时的锁竞争提升内存分配效率。​实现方式JVM 启动时为每个线程在 Eden 区分配一个初始 TLAB大小可配置。线程优先在自己的 TLAB 中分配对象无需加锁。若 TLAB 剩余空间不足触发一次 ​TLAB 分配失败线程尝试加锁申请新的 TLAB。触发minor gc的条件Eden区满了。对象进入老年代的四种情况年龄太大。 MinorGC的次数超过15次该阈值可以由参数“-XX:MaxTenuringThreshold”指定动态年龄判断:MinorGC之后发现Survivor区中的一批对象的总大小大于了这块Survivor区的50%那么就会将此时大于等于这批对象年龄最大值的所有对象直接进入老年代。比率可以由-XX:TargetsurvivorRatio指定大对象直接进入老年代1M。大对象的阈值可以由参数-XX:PretenureSizeThreshold指定前提是Serial和ParNew收集器。因为大对象在分配内存时的复制操作降低效率。MinorGC后存活对象太多无法放入Survivor。动态年龄判断触发条件在Minor GC执行后JVM会对Survivor区进行对象年龄分析‌。Survivor区中存活对象的总大小超过该区域内存的50%由-XX:TargetSurvivorRatio参数指定默认50%‌晋升规则JVM会按年龄从小到大累加对象所占空间直到总大小超过Survivor区的50%‌。例如年龄1年龄2…年龄n的对象总和超过阈值则年龄≥n的对象均晋升。满足条件的对象直接进入老年代无需达到预设的年龄阈值如默认15次GC‌。触发major GC的条件老年代空间不足两种情况1年轻代晋升老年代时 2大对象直接分配到老年代时分配担保失败 执行Minor GC前JVM会检查老年代剩余空间是否大于年轻代存活对象总大小避免Minor GC后存活对象无法放入Survivor区。若老年代剩余空间不足会直接触发Major GC。达到垃圾收集器的特定触发条件1CMS收集器老年代使用率超过阈值-XX:CMSInitiatingOccupancyFraction默认92%时触发收集。2G1收集器根据最大暂停时间目标-XX:MaxGCPauseMillis动态调整老年代回收时机。代码显式调用System.gc()老年代分配担保机制Handle Promotion分配担保机制是JVM垃圾回收GC中的一种内存管理策略主要用于在Minor GC年轻代回收过程中确保存活对象能安全晋升到老年代避免因内存不足导致回收失败或直接触发Full GC。其核心思想是老年代为年轻代的内存分配提供“担保”。在JDK 6及之前分配担保机制可以用参数-XX:-HandlePromotionFailure**开启JDK 7及之后该参数已失效JVM始终启用分配担保机制。在Minor GC发生前JVM会检查以下两个条件判断是否允许直接进行Minor GC老年代剩余连续空间年轻代存活对象总大小• 如果成立说明老年代有足够空间容纳晋升对象Minor GC可安全执行。老年代剩余连续空间历次Minor GC后晋升到老年代对象的平均大小• 如果成立即使本次存活对象略多老年代也可能容纳基于历史统计的乐观预测。若任一条件成立JVM允许执行Minor GC否则触发Full GC回收整个堆包括老年代和年轻代。如何判断对象可以被回收对象不在被任何地方引用即代表对象可以被回收。一般两种方法引用计数(因为无法解决循环引用的问题一般不使用这种方法)可达性分析如何判断一个对象是否不再被引用JVM 垃圾回收机制使用可达性分析方式判断对象是否存活。 GC Roots 是可达性分析的根节点。所有从 GC Roots 出发通过引用链能访问到的对象都是存活对象不可达的对象会被回收。GC Root有哪些​虚拟机栈中的本地变量​方法区中的静态变量​JNI 全局引用的对象系统类加载器加载的类​Java 本地方法栈中的引用活跃线程对象被锁持有的对象垃圾回收算法有哪些(1) 标记-清除Mark-Sweep原理1. 标记阶段从 GC Roots 出发标记所有可达对象。2. 清除阶段遍历堆内存回收未被标记的对象。优点实现简单无需移动对象。缺点1、产生内存碎片可能触发频繁 GC。 2、两次遍历标记 清除效率较低。(2) 标记-复制Mark-Copy原理将内存分为两块From 和 To。将 From 区的存活对象复制到 To 区并清空 From 区。交换 From 和 To 的角色。优点• 无内存碎片适合年轻代对象存活率低。• 只需遍历存活对象效率高。缺点• 内存利用率低默认浪费 50% 空间。• 复制大对象开销大。(3) 标记-整理Mark-Compact原理标记阶段标记所有存活对象。整理阶段将存活对象向内存一端移动清理边界外的内存。优点无内存碎片适合老年代。缺点移动对象需更新引用地址开销较大。垃圾回收器有哪些serial收集器特点单线程收集器收集过程运行-新生代采用复制算法(单线程、STW)-运行-老年代使用标记整理算法(单线程、STW)-运行适用于client端java程序ParNew新生代收集器、复制算法、多线程。其实是Serial收集器的多线程版本。收集过程运行-新生代采用复制算法(多线程、STW)-运行Parallel Scavenge新生代收集器、复制算法、多线程。特点是可以通过参数指定吞吐量和停顿时间。吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值。Serial Old老年代、单线程、“标记整理”算法。Serial Old是Serial收集器的老年代版本。Parallel Old收集器老年代、多线程、“标记整理”算法Parallel Scavenge收集器的老年代版本。CMS收集器多线程、标记清除算法、老年代CMSConcurrent Mark Sweep收集器是一种以获取最短回收停顿时间为目标的收集器。整个过程分为4步初始标记STW标记GC Roots能直接关联的对象并发标记进行GC Roots Tracing重新标记STW;修正并发标记期间因为用户程序继续运作而导致标记变动的那一部分对象的标记记录。并发清除** 缺点**CMS收集器对CPU资源非常敏感。并发阶段占用CPU资源会导致应用程序总吞吐量下降。CMS收集器无法处理浮动垃圾。在并发清理阶段程序运行的垃圾无法清理。CMS基于“标记清除”算法。清除后会产生大量内存碎片无法为大对象分配内存不得不提前进行Full GC。为解决这个问题CMS收集器提供了参数XX:UseCMSCompactAtFullCollection和-XX:CMSFullGCsBeforeCompactionN用于在FullGC时触发标记整理的回收方式。内存整理的过程是无法并发的空间碎片问题没有了但停顿时间会延长。G1收集器G1Garbage-First是 JDK 7 引入、JDK 9 默认的垃圾收集器专为大内存数十 GB 至 TB 级和低延迟场景设计。其核心目标是平衡吞吐量和停顿时间同时提供可预测的停顿模型。可用于新生代和老年代。使用的算法一般场景使用复制算法Full GC时使用标记整理算法。1. 核心设计思想•分 Region 内存布局堆内存被划分为多个固定大小的 Region默认 2048 个 Region大小通过-XX:G1HeapRegionSize设置范围为 1MB~32MB。•Region 类型◦Eden年轻代◦Survivor存活区◦Old老年代◦Humongous巨型对象区占用连续多个 Region•动态分代Region 可动态分配给不同代无需物理隔离。•停顿时间可控通过-XX:MaxGCPauseMillisN默认 200ms设定目标最大停顿时间G1 优先回收垃圾比例高的 RegionGarbage-First 名称来源。MaxGCPauseMillis参数的性能权衡‌设置过低可能导致GC频率增加降低整体吞吐量‌设置过高单次停顿时间延长但GC次数减少‌2. 工作流程(1) Young GC年轻代回收•触发条件Eden 区占满时触发。•过程并行复制存活对象将 Eden 和 Survivor 区的存活对象复制到新的 Survivor 区或晋升到 Old 区。更新引用更新老年代到年轻代的跨代引用通过Remembered Set记录。 参考资料https://blog.csdn.net/bookssea/article/details/132448692回收 Region清空原 Eden 和 Survivor 区标记为可分配。(2) Mixed GC混合回收•触发条件• 堆内存使用比例达到阈值-XX:InitiatingHeapOccupancyPercentN默认 45%。• 并发标记完成后回收包含最多垃圾的 Old 区。•过程并发标记◦初始标记STW标记 GC Roots 直接关联的对象。◦并发标记与用户线程并发标记全堆存活对象。◦最终标记STW处理剩余对象。筛选回收STW选择高垃圾比例的 Region 回收复制存活对象到空闲 Region。(3) Full GC•触发条件对象分配失败就会进入Full GC。对象分配的流程TLAB分配 -- 扩展TLAB进行分配 -- 申请新的TLAB -- 从自由分区获取新的Region给新生代– 堆内存扩展分区给新生代 -- 垃圾回收(YGC MGC) -- FGC• Mixed GC 回收速度跟不上对象分配速度。• 内存碎片导致无法分配大对象Humongous 分配失败。•风险G1 的 Full GC 退化为单线程的Serial Old GC停顿时间长需尽量避免。还有更新的垃圾收集器ZGC、Shenandoah