Java运行时一个类是什么时候被加载的?
《JVM 虚拟机规范》未强制规定类的加载时机,由虚拟机自行实现,如 HotSpot 虚拟机采用按需加载方式,在需要使用类时才进行加载。
JVM一个类的加载过程?
一个类从加载到JVM内存,到JVM内存卸载,他的整个生命周期会经历7个阶段:
- 加载(Loading):将类的字节码加载到 JVM 内存
- 验证(Verification):校验字节码合法性,确保符合 JVM 规范
- 准备(Preparation):为类静态变量分配内存并设置默认初始值
- 解析(Resolution):将符号引用转换为直接引用(如类、方法的内存地址)
- 初始化(Initialization) :执行类构造器
<font style="color:#000000;"><clinit>()</font>方法,初始化静态变量与静态代码块 - 使用(Using):类实例化、调用类方法 / 变量,进入实际使用阶段
- 卸载(Unloading):类不再被引用,由垃圾收集器从 JVM 内存中移除
一个类被初始化的过程?
- 初始化阶段 :Java 虚拟机真正执行类中编写的 Java 程序代码,完成类的最终初始化(如执行静态代码块、为静态变量赋程序定义的初始值)。
- 准备阶段 :仅为类的静态变量分配内存,并赋予系统默认零值(非程序定义值),不执行任何 Java 代码。
继承时父子类的初始化顺序是怎样的?
核心规律:静态代码(变量 + 初始化块)优先于实例代码,父类代码优先于子类代码,且同类中静态变量与静态块、实例变量与实例块的执行顺序与代码定义顺序一致。
- 父类--静态变量
- 父类--静态初始化块
- 子类--静态变量
- 子类--静态初始化块
- 父类--变量
- 父类--初始化块
- 父类--构造器
- 子类--变量
- 子类--初始化块
- 子类--构造器
什么是类加载器?
在类 "加载" 阶段,负责通过类的全限定名获取描述该类的二进制字节流的代码,称为类加载器。
JVM有哪些类加载器?
虚拟机视角分类:
- 启动类加载器(Bootstrap ClassLoader):C++ 实现,属于虚拟机自身部分。
- 其他类加载器 :均由 Java 语言实现,独立于虚拟机外部,且全部继承抽象类
<font style="color:rgb(0, 0, 0);">java.lang.ClassLoader</font>。
开发者视角三层架构(JDK) :遵循 "双亲委派模型" 的层级关系:
<font style="color:rgba(0, 0, 0, 0.85);">BootstrapClassLoader(启动类加载器) <- ExtClassLoader(扩展类加载器) <- AppClassLoader(应用程序类加载器) <- 自定义类加载器</font>
JVM中不同的类加载器加载哪些文件?
- 启动类加载器(Bootstrap ClassLoader) :
- 加载
<font style="color:rgba(0, 0, 0, 0.85) !important;"><JAVA_HOME>\jre\lib</font>下的核心类库(如 rt.jar、resources.jar、charsets.jar), <font style="color:rgba(0, 0, 0, 0.85) !important;">-Xbootclasspath</font>参数指定路径的类库。
- 加载
- 扩展类加载器(Extension ClassLoader) :
- 实现类为
<font style="color:rgba(0, 0, 0, 0.85) !important;">sun.misc.Laucher$ExtClassLoader</font>,加载<font style="color:rgba(0, 0, 0, 0.85) !important;"><JAVA_HOME>\jre\lib\ext</font>目录及<font style="color:rgba(0, 0, 0, 0.85) !important;">java.ext.dirs</font>系统变量指定路径的类库。
- 实现类为
- 应用程序类加载器(Application ClassLoader) :
- 实现类为
<font style="color:rgba(0, 0, 0, 0.85) !important;">sun.misc.Launcher$AppClassLoader</font> - 加载用户类路径(ClassPath)上的所有类库。
- 实现类为
JVM三层类加载器之间的关系是继承关系?
三者之间不是继承关系。只是父类那一代 并不是真正的父亲或者母亲。
你了解JVM类加载的双亲委派模型吗?
双亲委派模型的核心逻辑是「先委派父加载器,再尝试自身加载」,具体流程如下:
- 当类加载器收到类加载请求时,不会先自行加载,而是将请求委派给其上层父类加载器;
- 该委派逻辑逐层传递,所有加载请求最终会传至最顶层的启动类加载器;
- 若上层父类加载器在自身搜索范围内未找到目标类(即无法完成加载),则反馈给下层类加载器;
- 当下层类加载器收到父类的 "加载失败" 反馈后,才会尝试自行加载目标类。
JDK为什么要设计双亲委派模型,有什么好处?
- 安全保障:防止核心类库被篡改,确保 Java 核心 API 的完整性
- 效率优化:避免类的重复加载,减少资源消耗
- 唯一性保证:确保同一类在 JVM 中唯一存在,维护程序稳定性
可以打破JVM双亲委派模型吗?如何打破JVM双亲委派模型?
可以打破。通过自定义类加载器,重写其<font style="color:#000000;">loadClass</font>方法,去除双亲双亲委派逻辑即可实现。
如何自定义自己的类加载器?
- 基础步骤 :继承
<font style="color:#000000;">ClassLoader</font>类。 - 方法选择 :
- 覆盖
<font style="color:#000000;">findClass(String name)</font>:遵循双亲委派模型,仅实现自定义加载逻辑。 - 覆盖
<font style="color:#000000;">loadClass()</font>:可修改委派逻辑,从而打破双亲委派模型。
- 覆盖
ClassLoader 中的loadClass() 、findClass()、defineClass() 区别?
- 核心方法作用 :
<font style="color:#000000;">loadClass()</font>:实现类加载主逻辑,包含默认双亲委派机制。<font style="color:#000000;">findClass(String name)</font>:按名称 / 位置加载.class 字节码,无默认实现(JDK1.2 新增)。<font style="color:#000000;">defineClass()</font>:将字节码转换为 Class 对象。
- 自定义类加载器策略 :
- 需打破双亲委派:重写
<font style="color:#000000;">loadClass()</font>方法,修改委派逻辑。 - 需遵守双亲委派:继承
<font style="color:#000000;">ClassLoader</font>后重写<font style="color:#000000;">findClass()</font>,实现自定义加载逻辑(JDK1.2 后推荐方式,无需改动委派机制)。
- 需打破双亲委派:重写
加载一个类采用Class.forName()和 ClassLoader 有什么区别?
- 类完整生命周期:字节码 → 类加载 → 链接(验证→准备→解析)→ 初始化 → 使用 → 卸载;其中类加载会将类信息存入元空间(方法区)。
- 类获取方式差异 :
<font style="color:rgb(0, 0, 0);">Class.forName()</font>:获取的 Class 对象已完成初始化。<font style="color:rgb(0, 0, 0);">ClassLoader.loadClass()</font>:获取的 Class 对象尚未初始化。
你了解Tomcat 的类加载机制嘛?

- 扩展体系:在 Java 原生类加载机制基础上,Tomcat 新增 3 个基础类加载器、每个 Web 应用的类加载器及 JSP 类加载器。
- 核心特性 :自定义
<font style="color:rgb(0, 0, 0);">WebAppClassLoader</font>类加载器,打破双亲委派机制 ------ 收到类加载请求时,先尝试自身加载,找不到再委托父加载器,目的是优先加载 Web 应用自身定义的类(区别于<font style="color:rgb(0, 0, 0);">ClassLoader</font>默认的双亲优先委派模型)。
为什么Tomcat要破坏双亲委派模型?
Tomcat 破坏破坏双亲委派模型主要为满足四类核心需求:
- Web 应用间类隔离:同一 Tomcat 上的不同 Web 应用类库相互隔离,避免冲突
- 类库共享能力:支持不同 Web 应用共享通用类库,减少资源消耗
- 服务器安全防护:保障 Tomcat 自身类库不受部署的 Web 应用影响
- JSP 热部署 / 热加载:实现 JSP 页面修改后无需重启即可生效
有没有同时过热加载和热部署,如何自己实现一个热加载?
| 维度 | 热加载 | 热部署 |
|---|---|---|
| 核心定义 | 不重启服务,仅重新加载修改的 Class 文件使代码生效 | 不重启服务,重新部署整个项目(如 Tomcat 重新解压 War 包) |
| 实现逻辑 | 后台线程轮询检测 Class 文件是否变更,变更则重新加载 | 删除旧项目解压目录,重新处理新项目资源(如 War 包) |
| 耗时 | 耗时短,仅加载变更类 | 耗时长,需处理整个项目 |
| 应用场景 | 开发 / 调试阶段,提升效率 | 需整体更新项目的场景 |
| 安全性 | 存在不安全性,一般不用于正式环境 | - |
| 底层依赖 | 基于 Java 类加载器实现 | 基于服务器部署机制(如 Tomcat) |
热加载实现步骤:
- 自定义类加载器,用于加载需热加载的类;
- 通过自定义类加载器加载目标类;
- 轮询监测目标类的 Class 文件是否更新,若更新则触发重新加载;
Java代码到底是如何运行起来的?
- 核心本质:运行Java程序即开启一个JVM进程,JVM作为"运行字节码的软件"是字节码执行的载体
- 常见运行流程 :
- 流程 1:
<font style="color:rgb(0, 0, 0);">Mall.java</font>→<font style="color:rgb(0, 0, 0);">javac</font>编译→<font style="color:rgb(0, 0, 0);">Mall.class</font>→<font style="color:rgb(0, 0, 0);">java Mall</font>命令启动 JVM 进程 - 流程 2:
<font style="color:rgb(0, 0, 0);">Mall.java</font>→<font style="color:rgb(0, 0, 0);">javac</font>编译→<font style="color:rgb(0, 0, 0);">Mall.jar</font>→<font style="color:rgb(0, 0, 0);">java -jar Mall.jar</font>命令启动 JVM 进程 - 流程 3:
<font style="color:rgb(0, 0, 0);">Mall.java</font>→<font style="color:rgb(0, 0, 0);">javac</font>编译→<font style="color:rgb(0, 0, 0);">Mall.class</font>→<font style="color:rgb(0, 0, 0);">Mall.war</font>→部署到 Tomcat→<font style="color:rgb(0, 0, 0);">startup.sh</font>启动→触发<font style="color:rgb(0, 0, 0);">org.apache.catalina.startup.Bootstrap</font>类启动 JVM 进程
- 流程 1:
JVM运行原理图?

JVM的内存结构划分?

JVM运行时数据区 程序计数器 的特点及作用?
- 内存规模:空间极小,可忽略不计。
- 核心作用:作为当前线程执行字节码的行号指示器,记录下一条要执行的字节码指令地址。
- 线程隔离性:线程私有内存区域,每条线程有独立程序计数器,互不影响(适配多线程切换需求)。
- 异常特性 :不存在
<font style="color:rgb(0, 0, 0);">OutOfMemoryError</font>(仅更新指令地址,无需申请新内存)。 - GC 特性:无需垃圾回收(GC),内存无回收需求。
JVM运行时数据区 虚拟机栈 的特点及作用?
- 线程属性:线程私有内存区域,随线程创建而生成、线程结束而销毁。
- 核心结构与操作:方法执行时创建栈帧(存储局部变量表等信息),方法入栈执行、执行完毕出栈,遵循 "先进后出" 规则。
- 存储内容:存放方法的局部变量名,变量名指向的常量值、对象值等实际数据存储在堆中。
- 内存配置 :默认大小通常为 1M,可通过
<font style="color:rgb(0, 0, 0);">-Xss</font>参数(如<font style="color:rgb(0, 0, 0);">-Xss1M</font>)设置,自身占用内存较小。 - 异常类型 :
- 栈深度超过虚拟机允许范围时,抛出
<font style="color:rgb(0, 0, 0);">StackOverflowError</font>; - 栈扩展时无法申请到足够内存,抛出
<font style="color:rgb(0, 0, 0);">OutOfMemoryError</font>(较为少见,HotSpot 虚拟机中不常见)。
- 栈深度超过虚拟机允许范围时,抛出
- GC 特性:无需垃圾回收(GC),随线程生命周期自动回收。
JVM运行时数据区 本地方法栈 的特点及作用?
- 共性与定位:与虚拟机栈特性基本相似,同为线程私有区域,随线程创建而生、线程结束而死,且 GC 不会回收该区域。
- 核心差异:虚拟机栈为 Java 方法服务,本地方法栈则专门为 Native 方法服务。
- HotSpot 虚拟机特殊处理:将虚拟机栈与本地方法栈合并实现。
- 异常类型 :运行中可能抛出
<font style="color:rgb(0, 0, 0);">StackOverflowError</font>(栈深度超限)和<font style="color:rgb(0, 0, 0);">OutOfMemoryError</font>(栈扩展内存申请失败)。
JVM运行时数据 Java堆 的特点及作用?
- 基础属性:线程共享区域,虚拟机启动时创建,是 JVM 管理的最大内存块。
- 存储内容:存放所有实例对象与数组。
- GC 关联:垃圾收集器(GC)的核心管理区域。
- 内存划分 :
- 整体分为新生代、老年代;
- 新生代细分为 Eden、From Survivor、To Survivor,三者比例通常为 8:1:1。
- 配置与优化 :
- 堆大小可通过
<font style="color:rgb(0, 0, 0);">-Xmx</font>(最大堆)、<font style="color:rgb(0, 0, 0);">-Xms</font>(初始堆)调节; - 共享堆中可划分线程私有分配缓存区(TLAB),提升对象分配效率。
- 堆大小可通过
- 异常类型 :堆无法扩展时,抛出
<font style="color:rgb(0, 0, 0);">java.lang.OutOfMemoryError:Java heap space</font>。
JVM中对象如何在堆内存分配?
核心分配算法(按堆内存规整性选择):
- 指针碰撞(Bump The Pointer)
- 适用场景:堆内存规整时(内存无碎片)。
- 对应收集器:使用 Serial、ParNew 等带空间压缩(Compact)能力的收集器时采用,算法简单高效。
- 空闲列表(Free List)
- 适用场景:堆内存不规整时(存在内存碎片)。
- 对应收集器:使用 CMS 等仅带清除(Sweep)算法的收集器时采用,逻辑较复杂。
并发安全优化:本地线程分配缓存(TLAB):
- 问题背景:对象创建频繁,并发下直接分配内存可能导致 "内存争抢"(如 A 未修改指针,B 误用旧内存地址)。
- 解决方案 :
- 同步锁定:JVM 用 CAS + 失败重试保证内存更新的原子性。
- 线程隔离(TLAB):每个线程在堆中预先分配一小块私有内存,优先从 TLAB 分配;仅当 TLAB 用完时,再申请新内存(减少并发竞争)。
JVM堆内中的对象布局?

Hotspot 虚拟机中,对象存储结构分三部分:
- 对象头(Header)
- Mark Word:存储对象运行数据(哈希码、GC 分代年龄、锁状态等),32 位占 32bit,64 位占 64bit。
- 类型指针:指向类元数据的指针,确定对象所属类;数组对象额外包含数组长度信息(普通对象可通过元数据确定大小)。
- 实例数据(Instance Data)
- 存储程序定义的所有成员变量(含父类继承和子类定义的字段内容)。
- 对齐填充(Padding)
- 非必需,仅用于占位,确保对象大小为 8 字节的整数倍(HotSpot 虚拟机要求)。
JVM什么情况下会发生堆内存溢出?
- 溢出原因:Java 堆存储对象,若持续创建对象且通过 GC Roots 保持可达路径(避免被回收),当对象总量达最大堆容量限制时,即产生内存溢出。
- 排查方式:借助 MAT 工具分析 xxx.hprof 文件,定位溢出原因。
JVM如何判断对象可以被回收?
- 核心目的:GC 回收堆内存前,通过该算法区分对象 "存活" 或 "死去"。
- 算法逻辑:以 "GC Roots" 根对象为起点,沿引用关系向下搜索(路径为 "引用链");若对象到 GC Roots 无引用链(不可达),则判定为可回收对象。
- GC Roots 包含的对象类型 :
- 虚拟机栈(栈帧本地变量表)中引用的对象(如方法参数、局部变量);
- 方法区 / 元空间中类静态属性、常量引用的对象;
- 本地方法栈中 JNI(Native 方法)引用的对象;
- JVM 内部引用对象(如基本类型 Class 对象、常驻异常对象、系统类加载器);
- 被
<font style="color:rgb(0, 0, 0);">synchronized</font>锁持有的对象; - JMXBean、JVMTI 回调、本地代码缓存等反映 JVM 内部情况的对象。
谈谈Java中不同的引用类型?
Java 有四种引用类型,特性如下:
- 强引用 :如
<font style="color:rgba(0, 0, 0, 0.85) !important;">Object object = new Object()</font>,最常见的引用方式,只要存在就不会被 GC 回收。 - 软引用(SoftReference):内存充足时不回收,内存不足时触发回收,适合缓存场景。
- 弱引用(WeakReference):无论内存是否充足,GC 运行时都会回收,生命周期更短。
- 虚引用(PhantomReference):极少使用,形同虚设,仅在对象被 GC 回收时触发通知或后续处理。
JVM堆中新生代垃圾回收过程?

- GC 回收范围 :针对新生代、老年代、元空间 / 方法区(永久代);虚拟机栈无 GC------ 方法执行完栈帧出栈,局部变量直接清理。
- 堆中对象存活规律 :
- 短期存活对象 :创建后迅速用完,在新生代分配,易被 GC 回收;
- 长期存活对象 :需持续使用,在新生代的 From Survivor(S0)、To Survivor(S1)间经历 15 次 GC 后(对象年龄达 15),进入老年代。
JVM对象动态年龄判断是怎么回事?
虚拟机并非仅要求对象年龄达<font style="color:#000000;">MaxTenuringThreshold=15</font>才晋升老年代,还存在动态年龄判断规则 :
对 Survivor 区对象按年龄从小到大累加,当累加到某年龄 X 时,总和超过 Survivor 区空间的 50%(默认值,可通过<font style="color:#000000;">-XX:TargetSurvivorRatio</font>调整),则所有年龄大于 X 的对象直接晋升老年代。
什么是老年代空间分配担保机制?


场景前提:
新生代初始配置:
- Eden(800m→Minor GC 后剩 300m)、S0(100m)、S1(100m);
- 老年代(1000m,剩余 350m/200m);
核心问题:Minor GC 后存活对象超 Survivor 容量需转入老年代,若老年代空间不足如何处理?
老年代空间分配担保流程(Minor GC 前判断):
- 首次判断:老年代可用空间 ≥ 新生代所有对象总和
- 若满足:直接执行 Minor GC。即使 Minor GC 后所有对象存活(需转入老年代),老年代也能容纳,无风险。
- 二次判断:老年代可用空间 < 新生代所有对象总和,但 ≥ 历次 Minor GC 后转入老年代的对象平均大小
- 若满足:冒险执行 Minor GC(目的是避免频繁 Full GC)。但存在风险:若 Minor GC 后存活对象超老年代可用空间,会触发 Full GC。
- 最终处理:Full GC 后仍空间不足
- 若 Full GC 后,老年代仍无法容纳 Minor GC 后的存活对象,直接触发 OOM(内存溢出)。
核心目的:通过分层判断,优先避免不必要的 Full GC,仅在必要时触发,平衡 GC 效率与内存安全性。
什么情况下对象会进入老年代?
- 年龄阈值机制
对象躲过 15 次 GC 后自动进入老年代,可通过 JVM 参数<font style="color:rgba(0, 0, 0, 0.85) !important;">-XX:MaxTenuringThreshold</font>调整阈值(默认 15)。 - 动态年龄判断机制
Survivor 区对象按年龄从小到大累加,当累加到某年龄 X 的总和超过 Survivor 区空间阈值(默认 50%,可通过<font style="color:rgba(0, 0, 0, 0.85) !important;">-XX:TargetSurvivorRatio</font>调整),则年龄大于 X 的对象直接进入老年代。 - 老年代空间担保机制
Minor GC 前,若老年代可用空间无法容纳新生代所有对象,但满足 "可用空间≥历次 Minor GC 后转入老年代的对象平均大小",则冒险执行 Minor GC;若 Minor GC 后存活对象超老年代容量,触发 Full GC;Full GC 后仍不足则 OOM。 - 大对象直接进入机制
需大量连续内存空间的对象(如长字符串、大数组),会跳过新生代直接进入老年代,避免新生代频繁 GC 导致内存碎片。
JVM 运行时数据区 元空间 的特点及作用?
- 版本与名称演变:JDK 1.8 起引入 "元空间" 概念,替代之前的 "方法区 / 永久代"。
- 内存属性:与 Java 堆类似,为线程共享的内存区域。
- 存储内容:存放被加载的类信息、字段、静态变量、常量池、即时编译的代码等数据。
- 内存特性:基于本地内存实现,可随本地内存剩余空间扩展,也支持手动设置大小。
- GC 特性:垃圾收集极少发生,因回收条件苛刻且可回收信息有限。
JVM 本机 直接内存 的特点及作用?
- 内存归属:不属于 JVM 运行时数据区,而是直接使用本机物理内存。
- 核心用途与优势 :JDK 1.4 引入的 NIO 类(基于通道与缓冲区的 IO 方式),可通过 Native 函数库直接分配堆外内存;借助 Java 堆中的
<font style="color:rgb(0, 0, 0);">DirectByteBuffer</font>对象引用操作该内存,避免 Java 堆与 Native 堆间的数据复制,在部分场景中显著提升性能。 - 异常风险 :若直接内存分配超出本机物理内存限制,可能触发
<font style="color:rgb(0, 0, 0);">OutOfMemoryError</font>异常。
JVM本机直接内存溢出问题?
- 容量配置 :可通过
<font style="color:rgb(0, 0, 0);">-XX:MaxDirectMemorySize</font>参数指定大小(单位字节),用于限制 NIO 直接缓冲区的最大总分配量;默认值为 0,此时 JVM 自动决定 NIO 直接缓冲区的分配大小。 - 溢出特性:直接内存导致的内存溢出不会生成 Heap Dump 文件;若程序使用 NIO 技术,出现内存溢出时应优先排查直接内存相关原因。
如何计算一个对象的大小?
lucene 提供的一个工具方法可以计算

堆为什么要分成年轻代和老年代?
JVM 将堆分为新生代和老年代并采用不同回收算法,核心依据是对象存活特性的差异:
- 对象存活周期分化明显
- 绝大多数对象短期存活,集中放在新生代,回收时只需关注少量存活对象,以低代价回收大量空间。
- 少数对象长期存活,放入老年代,需适配其 "熬过多次 GC 后更难回收" 的特性。
- 分代设计的核心价值
- 针对不同存活特性的对象采用针对性回收策略,提升整体 GC 效率:新生代侧重快速回收大量短期对象,老年代侧重高效管理长期存活对象。
Eden区与Survivor区的空间大小比值默认为8:1:1?
- 区域构成:包含 1 个 Eden 区(对象诞生地)和 2 个 Survivor 区(S0、S1)。
- Survivor 区作用 :
- 一个用于保存上次新生代 GC 后存活的对象;
- 另一个为空,新生代 GC 时,将 Eden 区和非空 Survivor 区中存活的对象复制到该空 Survivor 区。
- 对象存活特性:统计显示 90% 的对象 "朝生夕死",每次 GC 会回收 90% 的对象,剩余 10% 的存活对象由 Survivor 区预留空间保存。

请介绍下JVM中的垃圾回收算法?
- 标记 - 清除算法 :先标记可回收对象,再统一清除。缺点是产生内存碎片。
- 标记 - 复制算法:将内存分为两块,每次使用一块,回收时将存活对象复制到另一块。适用于新生代(如 Eden 与 Survivor 区)。
- 标记 - 整理算法 :标记后将存活对象向一端移动,再清除边界外内存。适用于老年代,避免碎片。
- 分代收集算法 :结合上述算法,新生代用复制算法 ,老年代用标记 - 清除或标记 - 整理算法,按需回收。
JVM中的垃圾回收器?
- 新生代收集器 :专注回收短期存活对象,适配新生代特性
- Serial:单线程收集,简单高效,适用于客户端场景
- ParNew:Serial 的多线程版本,常与 CMS 配合使用
- Parallel Scavenge:多线程收集,侧重控制吞吐量
- 老年代收集器 :针对长期存活对象,优化回收效率
- CMS:并发收集,低停顿,优先保证响应速度
- Serial Old:Serial 的老年代版本,单线程,作 CMS 降级预案
- Parallel Old:Parallel Scavenge 的老年代版本,多线程,匹配吞吐量需求
- 整堆收集器 :覆盖新生代与老年代,全局优化
- G1:分区回收,可预测停顿,兼顾吞吐量与响应速度
- 前沿成果 (OpenJDK):
- ZGC、Shenandoah:超低延迟收集器,大幅降低停顿时间,适配高并发场景
什么是内存泄漏?什么是内存溢出?
- 内存溢出(OutOfMemory):无空闲内存,即使经 GC 回收后仍无法提供足够内存空间。
- 内存泄漏(Memory Leak):程序运行中未释放已无用的内存,单次泄漏影响小,长期堆积会引发内存溢出。
常见内存泄漏场景:
- 单例对象引用泄漏:单例生命周期与应用一致,若单例持有外部对象引用,该外部对象无法被 GC 回收,导致泄漏。
- 未关闭资源泄漏 :数据库连接、Socket 网络连接、IO 流等资源,若未在
<font style="color:rgb(0, 0, 0);">finally</font>中手动关闭,其占用内存无法释放,造成泄漏。
线上环境的JVM都设置多大?
- 虚拟机栈:单个线程栈大小设为 512K(-Xss512k),300 个线程约占用 300M 内存。
- 堆内存 :分配 4G(机器内存的一半),按收集器不同划分:
- CMS 收集器:新生代 1/3(约 1.3G),老年代 2/3(约 2.7G)
- G1 收集器:新生代与老年代比例 6:4
- 元空间:配置 512M,满足大多数场景需求。
线上Java项目CPU飙到100%怎么排查?
- 定位进程 :使用
<font style="color:rgba(0, 0, 0, 0.85) !important;">top</font>命令找到 CPU 占用最高的 Java 进程(PID) - 定位线程 :执行
<font style="color:rgba(0, 0, 0, 0.85) !important;">top -Hp PID</font>查看该进程内 CPU 最高的线程(TID) - 转换线程 ID :将线程 ID 转为十六进制
<font style="color:rgba(0, 0, 0, 0.85) !important;">printf "%x\n" TID</font> - 查看线程栈 :
<font style="color:rgba(0, 0, 0, 0.85) !important;">jstack PID | grep 十六进制TID -A 30</font>,分析对应的代码栈 - 分析代码:根据栈信息定位具体类和方法,检查是否存在死循环、频繁 GC、锁竞争等问题
- 辅助工具 :结合
<font style="color:rgba(0, 0, 0, 0.85) !important;">jstat -gcutil PID 1000</font>观察 GC 情况,<font style="color:rgba(0, 0, 0, 0.85) !important;">jmap -histo PID</font>查看对象分布,进一步确认原因
JVM堆溢出后,其他线程是否可以继续工作?
- 线程影响 :OOM 是否影响其他线程需结合场景分析,一般情况下,发生 OOM 的线程会终结,其持有的堆内存对象会被 GC 回收,释放空间。
- 全局影响:即便其他线程暂能工作,OOM 发生前会触发频繁 GC,导致系统资源占用飙升,严重影响整体性能与响应效率。
亿级流量的订单系统JVM优化?
- 系统预估
每个用户访问20次/天,500万用户,流量 = 500万 * 20 = 10000万 = 1 亿;
购买率 15%,每人1单,每天订单量 = 500万 * 15% = 75万订单/天;
二八原则,下单集中在一天4小时内,洪峰下单量 = 75 万 / 4 小时 = 18.75万单 / 小时 = 18.75 万单 / 60 分 / 60 秒 = 52 单 / 秒
50 k * 20 * 10 = 52 * 200 = 10400 = 10MB/秒,每秒52单基本上JVM没有压力
- 流量洪峰场景