第452集内存溢出从基础到架构实战 | 字数总计: 5.9k | 阅读时长: 24分钟 | 阅读量:
内存溢出从基础到架构实战 1. 概述 1.1 内存溢出的重要性 内存溢出(OutOfMemoryError,OOM) 是Java应用程序中最常见和严重的问题之一。理解内存溢出的原因、诊断方法和解决方案,对于开发高性能、稳定的Java应用至关重要。
内存溢出的影响 :
应用崩溃 :导致应用程序无法正常运行
性能下降 :频繁GC,响应时间变长
用户体验差 :服务不可用,影响业务
数据丢失 :可能导致数据不一致
1.2 内存溢出类型 Java内存溢出主要类型 :
类型
错误信息
发生区域
常见原因
堆溢出
java.lang.OutOfMemoryError: Java heap space
堆内存
对象过多、内存泄漏
栈溢出
java.lang.StackOverflowError
虚拟机栈
递归过深、局部变量过多
方法区溢出
java.lang.OutOfMemoryError: Metaspace
方法区(元空间)
类加载过多
直接内存溢出
java.lang.OutOfMemoryError: Direct buffer memory
直接内存
NIO使用不当
GC开销超限
java.lang.OutOfMemoryError: GC overhead limit exceeded
堆内存
GC效率低
无法创建线程
java.lang.OutOfMemoryError: unable to create new native thread
栈内存
线程过多
1.3 本文内容结构 本文将从以下几个方面全面解析内存溢出:
JVM内存模型 :内存区域划分和特点
堆内存溢出 :原因、诊断、解决方案
栈内存溢出 :原因、诊断、解决方案
方法区溢出 :原因、诊断、解决方案
直接内存溢出 :原因、诊断、解决方案
内存泄漏 :常见内存泄漏场景和解决方案
诊断工具 :jmap、jstat、MAT等工具使用
实战案例 :真实的内存溢出案例和解决方案
架构设计 :内存管理的最佳实践
2. JVM内存模型 2.1 JVM内存区域划分 2.1.1 内存区域概览 JVM内存区域 (Java 8之前):
1 2 3 4 5 6 7 8 9 10 11 12 JVM内存 ├── 程序计数器(Program Counter Register) ├── 虚拟机栈(VM Stack) ├── 本地方法栈(Native Method Stack) ├── 堆(Heap) │ ├── 新生代(Young Generation) │ │ ├── Eden区 │ │ ├── Survivor From区 │ │ └── Survivor To区 │ └── 老年代(Old Generation) └── 方法区(Method Area) └── 运行时常量池(Runtime Constant Pool)
JVM内存区域 (Java 8+):
1 2 3 4 5 6 7 8 9 10 11 12 JVM内存 ├── 程序计数器(Program Counter Register) ├── 虚拟机栈(VM Stack) ├── 本地方法栈(Native Method Stack) ├── 堆(Heap) │ ├── 新生代(Young Generation) │ │ ├── Eden区 │ │ ├── Survivor From区 │ │ └── Survivor To区 │ └── 老年代(Old Generation) └── 元空间(Metaspace) └── 运行时常量池(Runtime Constant Pool)
2.1.2 各内存区域说明 程序计数器 :
线程私有
记录当前执行的字节码指令地址
唯一不会发生OOM的区域
虚拟机栈 :
线程私有
存储局部变量、方法参数、返回地址
可能发生StackOverflowError和OOM
本地方法栈 :
线程私有
为Native方法服务
可能发生StackOverflowError和OOM
堆 :
方法区/元空间 :
线程共享
存储类信息、常量、静态变量
可能发生OOM
2.2 堆内存结构 2.2.1 堆内存划分 新生代(Young Generation) :
Eden区 :新对象分配区域
Survivor From区 :GC后存活对象
Survivor To区 :GC后存活对象
比例 :Eden:Survivor = 8:1:1
老年代(Old Generation) :
堆内存参数 :
1 2 3 4 5 -Xms512m -Xmx2048m -Xmn256m -XX:SurvivorRatio=8 -XX:NewRatio=2
2.3 方法区/元空间 2.3.1 方法区演变 Java 7及之前 :
方法区(Method Area)
永久代(PermGen)
参数:-XX:PermSize、-XX:MaxPermSize
**Java 8+**:
元空间(Metaspace)
使用本地内存
参数:-XX:MetaspaceSize、-XX:MaxMetaspaceSize
变化原因 :
永久代大小难以确定
容易发生OOM
元空间使用本地内存,更灵活
3. 堆内存溢出 3.1 堆溢出概述 3.1.1 错误信息 1 Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
3.1.2 发生原因 主要原因 :
对象过多 :创建了大量对象,超出堆内存限制
内存泄漏 :对象无法被GC回收,持续占用内存
堆内存设置过小 :-Xmx设置不合理
大对象 :创建了过大的对象
3.2 堆溢出示例 3.2.1 示例代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import java.util.ArrayList;import java.util.List;public class HeapOOM { static class OOMObject { private byte [] data = new byte [1024 * 1024 ]; } public static void main (String[] args) { List<OOMObject> list = new ArrayList <>(); while (true ) { list.add(new OOMObject ()); } } }
运行参数 :
1 2 java -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError \ -XX:HeapDumpPath=/tmp/heap_dump.hprof HeapOOM
3.2.2 错误输出 1 2 3 4 java.lang.OutOfMemoryError: Java heap space Dumping heap to /tmp/heap_dump.hprof ... Heap dump file created [12345678 bytes in 0.123 secs] Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
3.3 堆溢出诊断 3.3.1 使用jmap生成堆转储 1 2 3 4 5 6 7 8 jmap -heap <pid> jmap -dump:format=b,file=/tmp/heap_dump.hprof <pid> jmap -histo <pid> | head -20
3.3.2 使用jstat监控GC 1 2 3 4 5 jstat -gcutil <pid> 1000 10 jstat -gccapacity <pid>
3.3.3 使用MAT分析堆转储 MAT(Memory Analyzer Tool) :
下载MAT工具
打开堆转储文件
分析内存占用
查找内存泄漏
MAT分析步骤 :
Histogram :查看对象数量和大小
Dominator Tree :查看对象引用关系
Leak Suspects :自动检测内存泄漏
Thread Overview :查看线程内存占用
3.4 堆溢出解决方案 3.4.1 增加堆内存 1 2 3 4 5 java -Xms512m -Xmx2048m YourApplication
3.4.2 优化代码 避免创建过多对象 :
1 2 3 4 5 6 7 8 9 10 for (int i = 0 ; i < 1000000 ; i++) { String str = new String ("test" + i); } StringBuilder sb = new StringBuilder ();for (int i = 0 ; i < 1000000 ; i++) { sb.append("test" ).append(i); }
及时释放引用 :
1 2 3 4 5 6 7 8 9 List<BigObject> list = new ArrayList <>(); List<BigObject> list = new ArrayList <>(); list = null ;
3.4.3 优化GC 1 2 3 4 5 6 7 8 java -XX:+UseG1GC -Xmx4g YourApplication java -XX:+UseG1GC \ -XX:MaxGCPauseMillis=200 \ -XX:G1HeapRegionSize=16m \ -Xmx4g YourApplication
4. 栈内存溢出 4.1 栈溢出概述 4.1.1 错误信息 1 Exception in thread "main" java.lang.StackOverflowError
4.1.2 发生原因 主要原因 :
递归过深 :递归调用层次太深
局部变量过多 :方法中局部变量占用空间过大
栈深度设置过小 :-Xss设置不合理
4.2 栈溢出示例 4.2.1 递归过深示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class StackOverflow { private int stackLength = 1 ; public void stackLeak () { stackLength++; stackLeak(); } public static void main (String[] args) { StackOverflow oom = new StackOverflow (); try { oom.stackLeak(); } catch (Throwable e) { System.out.println("栈深度: " + oom.stackLength); throw e; } } }
运行参数 :
1 java -Xss128k StackOverflow
4.2.2 局部变量过多示例 1 2 3 4 5 6 7 8 9 public class StackOverflow { public static void main (String[] args) { int [] array1 = new int [10000 ]; int [] array2 = new int [10000 ]; int [] array3 = new int [10000 ]; } }
4.3 栈溢出诊断 4.3.1 查看线程栈 1 2 3 4 5 jstack <pid> > /tmp/thread_dump.txt jstack <pid> | grep -A 50 "Thread-1"
4.3.2 分析栈信息 查看线程转储文件 :
4.4 栈溢出解决方案 4.4.1 增加栈深度 1 2 3 4 5 java -Xss512k YourApplication java -Xss1m YourApplication
4.4.2 优化递归 将递归改为迭代 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public int fibonacci (int n) { if (n <= 1 ) return n; return fibonacci(n - 1 ) + fibonacci(n - 2 ); } public int fibonacci (int n) { if (n <= 1 ) return n; int a = 0 , b = 1 ; for (int i = 2 ; i <= n; i++) { int temp = a + b; a = b; b = temp; } return b; }
使用尾递归优化 :
1 2 3 4 5 public int factorial (int n, int acc) { if (n <= 1 ) return acc; return factorial(n - 1 , n * acc); }
4.4.3 减少局部变量 1 2 3 4 5 6 7 8 9 10 11 12 public void method () { int [] array1 = new int [10000 ]; int [] array2 = new int [10000 ]; } public void method () { DataHolder holder = new DataHolder (); }
5. 方法区溢出 5.1 方法区溢出概述 5.1.1 错误信息 Java 7及之前 :
1 Exception in thread "main" java.lang.OutOfMemoryError: PermGen space
**Java 8+**:
1 Exception in thread "main" java.lang.OutOfMemoryError: Metaspace
5.1.2 发生原因 主要原因 :
类加载过多 :动态生成大量类
常量池过大 :字符串常量过多
方法区设置过小 :-XX:MaxPermSize或-XX:MaxMetaspaceSize设置不合理
5.2 方法区溢出示例 5.2.1 动态类生成示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 import java.lang.reflect.Method;import javassist.ClassPool;import javassist.CtClass;public class MetaspaceOOM { public static void main (String[] args) throws Exception { ClassPool pool = ClassPool.getDefault(); for (int i = 0 ; i < 100000 ; i++) { CtClass cc = pool.makeClass("GeneratedClass" + i); Class<?> clazz = cc.toClass(); } } }
运行参数 :
1 2 3 4 5 java -XX:PermSize=10m -XX:MaxPermSize=10m MetaspaceOOM java -XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m MetaspaceOOM
5.2.2 字符串常量池示例 1 2 3 4 5 6 7 8 9 10 11 12 13 import java.util.ArrayList;import java.util.List;public class StringConstantPoolOOM { public static void main (String[] args) { List<String> list = new ArrayList <>(); int i = 0 ; while (true ) { list.add(String.valueOf(i++).intern()); } } }
5.3 方法区溢出诊断 5.3.1 查看类加载信息 1 2 3 4 5 jstat -class <pid> jstat -gc <pid> | grep Metaspace
5.3.2 使用jmap查看类信息 1 2 3 4 5 jmap -clstats <pid> jmap -dump:format=b,file=/tmp/heap_dump.hprof <pid>
5.4 方法区溢出解决方案 5.4.1 增加方法区大小 1 2 3 4 5 java -XX:PermSize=256m -XX:MaxPermSize=256m YourApplication java -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=256m YourApplication
5.4.2 优化类加载 避免动态生成过多类 :
1 2 3 4 5 6 7 for (int i = 0 ; i < 100000 ; i++) { generateClass("Class" + i); }
及时卸载类 :
1 2 3 4 5 ClassLoader loader = new CustomClassLoader ();loader = null ;
5.4.3 优化字符串使用 1 2 3 4 5 String str = new String ("test" ).intern();
6. 直接内存溢出 6.1 直接内存溢出概述 6.1.1 错误信息 1 Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory
6.1.2 发生原因 主要原因 :
NIO使用不当 :大量使用DirectByteBuffer
直接内存设置过小 :-XX:MaxDirectMemorySize设置不合理
直接内存未释放 :DirectByteBuffer未及时回收
6.2 直接内存溢出示例 6.2.1 NIO使用示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 import java.nio.ByteBuffer;import java.util.ArrayList;import java.util.List;public class DirectMemoryOOM { public static void main (String[] args) { List<ByteBuffer> list = new ArrayList <>(); while (true ) { ByteBuffer buffer = ByteBuffer.allocateDirect(1024 * 1024 ); list.add(buffer); } } }
运行参数 :
1 java -XX:MaxDirectMemorySize=10m DirectMemoryOOM
6.3 直接内存溢出诊断 6.3.1 查看直接内存使用 1 2 3 4 5 6 jmap -histo <pid> | grep DirectByteBuffer java -XX:NativeMemoryTracking=summary YourApplication jcmd <pid> VM.native_memory summary
6.4 直接内存溢出解决方案 6.4.1 增加直接内存 1 2 java -XX:MaxDirectMemorySize=512m YourApplication
6.4.2 优化NIO使用 及时释放DirectByteBuffer :
1 2 3 4 5 6 7 8 9 10 11 12 13 ByteBuffer buffer = ByteBuffer.allocateDirect(1024 );try { } finally { if (buffer.isDirect()) { sun.misc.Cleaner cleaner = ((sun.nio.ch.DirectBuffer) buffer).cleaner(); if (cleaner != null ) { cleaner.clean(); } } }
使用堆内存代替直接内存 :
1 2 ByteBuffer buffer = ByteBuffer.allocate(1024 );
7. GC开销超限 7.1 GC开销超限概述 7.1.1 错误信息 1 Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
7.1.2 发生原因 触发条件 :
连续多次GC,回收的内存很少(<2%)
GC时间占比超过98%
主要原因 :
堆内存过小 :频繁触发GC
内存泄漏 :对象无法回收
GC效率低 :GC算法不适合
7.2 GC开销超限示例 7.2.1 示例代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import java.util.ArrayList;import java.util.List;public class GCOverheadOOM { public static void main (String[] args) { List<String> list = new ArrayList <>(); int i = 0 ; while (true ) { list.add(String.valueOf(i++).intern()); if (i % 1000 == 0 ) { list.remove(0 ); } } } }
运行参数 :
1 2 3 4 java -Xmx10m -XX:+UseG1GC \ -XX:+PrintGCDetails \ -XX:+PrintGCDateStamps \ GCOverheadOOM
7.3 GC开销超限诊断 7.3.1 查看GC日志 1 2 3 4 5 6 7 8 java -Xloggc:/tmp/gc.log \ -XX:+PrintGCDetails \ -XX:+PrintGCDateStamps \ YourApplication
7.3.2 使用GCViewer分析 GCViewer工具 :
下载GCViewer
打开GC日志文件
分析GC统计信息
查看GC效率
7.4 GC开销超限解决方案 7.4.1 增加堆内存 1 2 java -Xms512m -Xmx2048m YourApplication
7.4.2 优化GC算法 1 2 3 4 5 6 7 java -XX:+UseG1GC \ -XX:MaxGCPauseMillis=200 \ -Xmx4g YourApplication java -XX:+UseZGC -Xmx4g YourApplication
7.4.3 修复内存泄漏 查找内存泄漏 :
使用MAT分析堆转储
查找无法回收的对象
修复代码中的内存泄漏
8. 无法创建线程 8.1 无法创建线程概述 8.1.1 错误信息 1 Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread
8.1.2 发生原因 主要原因 :
线程过多 :创建了过多线程
栈内存不足 :每个线程需要栈内存
系统限制 :操作系统线程数限制
8.2 无法创建线程示例 8.2.1 示例代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class UnableToCreateThread { public static void main (String[] args) { int i = 0 ; while (true ) { new Thread (() -> { try { Thread.sleep(Integer.MAX_VALUE); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); System.out.println("创建线程: " + (++i)); } } }
运行参数 :
1 java -Xss1m UnableToCreateThread
8.3 无法创建线程诊断 8.3.1 查看线程数 1 2 3 4 5 6 7 8 jstack <pid> | grep "java.lang.Thread.State" | wc -l ps -eLf | grep java | wc -l ulimit -u
8.4 无法创建线程解决方案 8.4.1 使用线程池 1 2 3 4 5 6 7 8 9 10 11 12 13 14 for (int i = 0 ; i < 10000 ; i++) { new Thread (() -> { }).start(); } ExecutorService executor = Executors.newFixedThreadPool(100 );for (int i = 0 ; i < 10000 ; i++) { executor.submit(() -> { }); }
8.4.2 减少栈大小 1 2 java -Xss256k YourApplication
8.4.3 增加系统限制 1 2 3 4 5 6 7 8 ulimit -u 4096vim /etc/security/limits.conf
9. 内存泄漏 9.1 内存泄漏概述 9.1.1 什么是内存泄漏 内存泄漏 :
对象无法被GC回收
持续占用内存
最终导致内存溢出
内存泄漏 vs 内存溢出 :
内存泄漏 :对象无法回收(原因)
内存溢出 :内存不足(结果)
9.2 常见内存泄漏场景 9.2.1 静态集合持有引用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class MemoryLeak { private static List<Object> list = new ArrayList <>(); public void add (Object obj) { list.add(obj); } } public class MemoryLeak { private static List<WeakReference<Object>> list = new ArrayList <>(); public void add (Object obj) { list.add(new WeakReference <>(obj)); } }
9.2.2 监听器未移除 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class MemoryLeak { private List<Listener> listeners = new ArrayList <>(); public void addListener (Listener listener) { listeners.add(listener); } } public class MemoryLeak { private List<Listener> listeners = new ArrayList <>(); public void addListener (Listener listener) { listeners.add(listener); } public void removeListener (Listener listener) { listeners.remove(listener); } }
9.2.3 内部类持有外部类引用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class Outer { private byte [] data = new byte [1024 * 1024 ]; class Inner { void doSomething () { } } } public class Outer { private byte [] data = new byte [1024 * 1024 ]; static class Inner { void doSomething () { } } }
9.2.4 ThreadLocal未清理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class MemoryLeak { private static ThreadLocal<Object> threadLocal = new ThreadLocal <>(); public void set (Object obj) { threadLocal.set(obj); } } public class MemoryLeak { private static ThreadLocal<Object> threadLocal = new ThreadLocal <>(); public void set (Object obj) { threadLocal.set(obj); } public void remove () { threadLocal.remove(); } }
9.3 内存泄漏诊断 9.3.1 使用MAT分析 步骤 :
生成堆转储文件
使用MAT打开
查看Leak Suspects报告
分析Dominator Tree
查找无法回收的对象
9.3.2 使用jmap分析 1 2 3 4 5 jmap -histo <pid> | head -20
10. 诊断工具 10.1 jmap工具 10.1.1 基本用法 1 2 3 4 5 6 7 8 9 10 11 jmap -heap <pid> jmap -dump:format=b,file=/tmp/heap_dump.hprof <pid> jmap -histo <pid> jmap -histo:live <pid>
10.1.2 堆转储分析 生成堆转储 :
1 2 3 4 5 6 7 8 9 10 11 jmap -dump:format=b,file=/tmp/heap_dump.hprof <pid> java -XX:+HeapDumpOnOutOfMemoryError \ -XX:HeapDumpPath=/tmp/heap_dump.hprof \ YourApplication jcmd <pid> GC.run_finalization jcmd <pid> VM.dump_heap /tmp/heap_dump.hprof
10.2 jstat工具 10.2.1 GC统计 1 2 3 4 5 6 7 8 jstat -gcutil <pid> 1000 10 jstat -gccapacity <pid> jstat -class <pid>
10.2.2 GC日志分析 输出说明 :
1 2 S0 S1 E O M CCS YGC YGCT FGC FGCT GCT 0.00 0.00 25.00 45.00 80.00 75.00 10 0.250 2 0.500 0.750
S0/S1:Survivor区使用率
E:Eden区使用率
O:老年代使用率
M:元空间使用率
YGC:Young GC次数
YGCT:Young GC总耗时
FGC:Full GC次数
FGCT:Full GC总耗时
10.3 MAT工具 10.3.1 MAT使用步骤
下载MAT :https://www.eclipse.org/mat/
打开堆转储文件
分析内存占用 :
Histogram:对象统计
Dominator Tree:对象引用树
Leak Suspects:内存泄漏检测
查找问题 :定位内存泄漏源头
10.3.2 MAT分析技巧 查找大对象 :
打开Dominator Tree
按Retained Heap排序
查看占用内存最大的对象
查找内存泄漏 :
打开Leak Suspects报告
查看可疑对象
分析对象引用链
定位泄漏源头
10.4 VisualVM工具 10.4.1 VisualVM使用 功能 :
实时监控JVM
查看堆内存使用
分析线程
生成堆转储
使用步骤 :
启动VisualVM
连接到Java进程
查看监控信息
生成堆转储
10.5 jcmd工具 10.5.1 jcmd使用 1 2 3 4 5 6 7 8 9 10 11 12 jcmd <pid> help jcmd <pid> GC.run_finalization jcmd <pid> VM.dump_heap /tmp/heap_dump.hprof jcmd <pid> VM.info jcmd <pid> VM.system_properties
11. 实战案例 11.1 案例1:Web应用堆溢出 11.1.1 问题描述 现象 :
生产环境Web应用频繁OOM
错误信息:java.lang.OutOfMemoryError: Java heap space
应用重启后一段时间又出现
11.1.2 诊断过程 1 2 3 4 5 6 7 8 jmap -heap <pid> jmap -dump:format=b,file=/tmp/heap_dump.hprof <pid>
11.1.3 问题原因 原因 :
Session对象被静态Map持有
Session过期后未清理
导致内存泄漏
11.1.4 解决方案 1 2 3 4 5 6 7 8 9 private static Map<String, Session> sessionMap = new HashMap <>();private static Map<String, Session> sessionMap = new ConcurrentHashMap <>();scheduledExecutor.scheduleAtFixedRate(() -> { sessionMap.entrySet().removeIf(entry -> entry.getValue().isExpired()); }, 0 , 1 , TimeUnit.HOURS);
11.2 案例2:大数据处理栈溢出 11.2.1 问题描述 现象 :
处理大数据时出现栈溢出
错误信息:java.lang.StackOverflowError
递归深度过大
11.2.2 解决方案 1 2 3 4 5 6 7 8 9 10 11 12 public void process (List<Data> dataList) { if (dataList.isEmpty()) return ; process(dataList.subList(1 , dataList.size())); } public void process (List<Data> dataList) { for (Data data : dataList) { } }
11.3 案例3:动态类生成元空间溢出 11.3.1 问题描述 现象 :
使用CGLib动态生成代理类
元空间持续增长
最终OOM:java.lang.OutOfMemoryError: Metaspace
11.3.2 解决方案 1 2 3 4 5 6 7 8 9 10 for (int i = 0 ; i < 100000 ; i++) { Enhancer enhancer = new Enhancer (); enhancer.setSuperclass(TargetClass.class); enhancer.setCallback(new MethodInterceptor () { ... }); enhancer.create(); }
12. 架构设计最佳实践 12.1 内存管理策略 12.1.1 堆内存配置 配置原则 :
-Xms和-Xmx设置为相同值,避免动态调整
堆内存 = 物理内存的50%-70%
根据应用特点调整新生代和老年代比例
配置示例 :
1 2 3 4 5 6 7 8 java -Xms2g \ -Xmx2g \ -XX:NewRatio=2 \ -XX:SurvivorRatio=8 \ -XX:+UseG1GC \ -XX:MaxGCPauseMillis=200 \ YourApplication
12.1.2 GC选择 GC选择原则 :
场景
推荐GC
参数
小堆内存(<4GB)
Parallel GC
-XX:+UseParallelGC
大堆内存(>4GB)
G1 GC
-XX:+UseG1GC
低延迟要求
ZGC(Java 11+)
-XX:+UseZGC
超大堆内存(>32GB)
ZGC或Shenandoah
-XX:+UseZGC
12.2 内存监控 12.2.1 监控指标 关键指标 :
堆内存使用率
GC频率和耗时
Full GC频率
内存泄漏趋势
12.2.2 监控方案 使用Prometheus + Grafana :
1 2 3 4 5 scrape_configs: - job_name: 'jvm' static_configs: - targets: ['localhost:9999' ]
使用JMX Exporter :
1 2 java -javaagent:jmx_prometheus_javaagent.jar=9999:config.yml \ YourApplication
12.3 预防措施 12.3.1 代码层面
及时释放引用 :对象使用完后及时置null
避免内存泄漏 :注意静态集合、监听器、ThreadLocal
合理使用缓存 :设置缓存大小和过期时间
优化数据结构 :选择合适的数据结构
12.3.2 架构层面
限流 :防止突发流量导致内存溢出
降级 :内存不足时降级服务
扩容 :根据负载动态调整资源
监控告警 :及时发现内存问题
13. 总结 13.1 核心要点
内存溢出类型 :堆溢出、栈溢出、方法区溢出、直接内存溢出等
诊断工具 :jmap、jstat、MAT、VisualVM等
解决方案 :增加内存、优化代码、优化GC
内存泄漏 :常见场景和预防措施
架构设计 :内存管理策略和监控方案
13.2 架构师建议
预防为主 :
合理设置JVM参数
代码层面避免内存泄漏
架构层面做好限流和降级
监控告警 :
实时监控内存使用
设置合理的告警阈值
及时发现问题
快速响应 :
13.3 最佳实践
JVM参数调优 :根据应用特点合理配置
代码审查 :避免常见的内存泄漏场景
压力测试 :提前发现内存问题
监控告警 :建立完善的监控体系
相关文章 :