Java 内存溢出(一)原因、复现、排查
目錄
- 一、內存溢出原因
- 二、內存溢出實例
- 1、堆溢出
- 2.虛擬機棧和本地方法棧溢出
- 3.方法區和運行時常量池溢出
- 4.本機直接內存溢出
- 三、內存溢出排查
內存溢出: 是指應用系統中存在無法回收的內存或使用的內存過多,最終使得程序運行要用到的內存大于虛擬機能提供的最大內存。這篇文章整理自《深入理解 java 虛擬機》。
一、內存溢出原因
內存溢出就是內存不夠,引起內存溢出的原因有很多種,常見的有以下幾種:
當然實際情況中內存溢出的原因就太多了。
以上的圖是基于 java7 來描述的,從上面這張圖我們能夠得到如下信息:java 虛擬機把內存分為 5 個模塊。
(1)程序計數器:程序計數器是線程私有的,主要作用是通過改變這個計數器的值來選取下一條需要執行的字節碼指令。既然每個線程都有一個,那么這些線程的計數器是互不影響的,也不會拋出任何異常。
(2)虛擬機棧和本地方法棧:虛擬機棧描述的是 java 方法執行的內存模型,每個方法在執行的時候都會創建一個棧幀用于存儲局部變量表、操作數棧、動態鏈接、方法出口等信息。本地方法棧與虛擬機棧的區別是,虛擬機棧為虛擬機執行 java 方法服務,而本地方法棧則為虛擬機提供 native 方法服務。
在單線程的操作中,無論是由于棧幀太大,還是虛擬機棧空間太小,當棧空間無法分配時,虛擬機拋出的都是 StackOverflowError 異常,而不會得到 OutOfMemoryError 異常。而在多線程環境下,則會拋出 OutOfMemoryError 異常。
(3)java 堆和方法區:java 堆區主要存放對象實例和數組等,方法區保存類信息、常量、靜態變量等等。運行時常量池也是方法區的一部分。這兩塊區域是線程共享的區域,只會拋出 OutOfMemoryError。
二、內存溢出實例
1、堆溢出
既然堆是存放實例對象的,那我們就無限創建實例對象。這樣堆區遲早會滿。
執行時需要設置VM參數:-Xmx10M,限制最大堆內存為10M
import java.util.ArrayList; import java.util.List;public class HeapOOM {static class User {}public static void main(String[] args) {List<User> list = new ArrayList<>();while (true) {list.add(new User());}} }異常內容:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap spaceat java.util.Arrays.copyOf(Arrays.java:3210)at java.util.Arrays.copyOf(Arrays.java:3181)at java.util.ArrayList.grow(ArrayList.java:261)at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:235)at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:227)at java.util.ArrayList.add(ArrayList.java:458)at com.demo.HeapOOM.main(HeapOOM.java:6)因為我提前設置了堆區內存限制,所以無限創建就會拋出異常。
2.虛擬機棧和本地方法棧溢出
Java 虛擬機規范中描述了兩種異常:
如果線程請求的棧深度大于虛擬機所允許的最大深度,將拋出 StackOverflowError 異常。如果虛擬機在擴展棧時無法申請到足夠的內存空間,則拋出 OutOfMemoryError 異常。
第一種我們只需要使用方法遞歸調用即可模擬:
public class StackOutOfMemoryError {public static void main(String[] args) {test();}private static void test() {System.out.println("StackOverflowError 異常測試");test();} }異常內容:
Exception in thread "main" java.lang.StackOverflowErrorat sun.nio.cs.UTF_8$Encoder.encodeLoop(UTF_8.java:691)at java.nio.charset.CharsetEncoder.encode(CharsetEncoder.java:579)at sun.nio.cs.StreamEncoder.implWrite(StreamEncoder.java:271)at sun.nio.cs.StreamEncoder.write(StreamEncoder.java:125)at java.io.OutputStreamWriter.write(OutputStreamWriter.java:207)at java.io.BufferedWriter.flushBuffer(BufferedWriter.java:129)at java.io.PrintStream.write(PrintStream.java:526)at java.io.PrintStream.print(PrintStream.java:669)at java.io.PrintStream.println(PrintStream.java:806)at com.demo.StackOutOfMemoryError.test(StackOutOfMemoryError.java:6)第二種也可以遞歸調用模擬,但是使用的是類直接調用:
public class JavaVMStackSOF {private int stackLength = 1;public void stackLeak() {stackLength++;stackLeak();}public static void main(String[] args) {JavaVMStackSOF oom = new JavaVMStackSOF();oom.stackLeak();} }異常內容:
Exception in thread "main" java.lang.StackOverflowErrorat com.demo.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:5)3.方法區和運行時常量池溢出
執行時需要設置VM參數:-Xmx10M,限制最大堆內存為10M
import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy;import java.lang.reflect.Method;public class JavaMethodAreaOOM {private static class User {}public static void main(String[] args) {while (true) {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(User.class);enhancer.setUseCache(false);enhancer.setCallback(new MethodInterceptor() {@Overridepublic Object intercept(Object obj, Method method,Object[] args, MethodProxy proxy) throws Throwable {return proxy.invokeSuper(obj, args);}});enhancer.create();}} }異常內容:
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceededat java.lang.AbstractStringBuilder.<init>(AbstractStringBuilder.java:68)at java.lang.StringBuffer.<init>(StringBuffer.java:116)at org.objectweb.asm.Type.getDescriptor(Unknown Source)at net.sf.cglib.core.ClassEmitter.declare_field(ClassEmitter.java:193)at net.sf.cglib.proxy.MethodInterceptorGenerator.generate(MethodInterceptorGenerator.java:94)at net.sf.cglib.proxy.Enhancer.emitMethods(Enhancer.java:994)at net.sf.cglib.proxy.Enhancer.generateClass(Enhancer.java:498)at net.sf.cglib.core.DefaultGeneratorStrategy.generate(DefaultGeneratorStrategy.java:25)at net.sf.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:216)at net.sf.cglib.proxy.Enhancer.createHelper(Enhancer.java:377)at net.sf.cglib.proxy.Enhancer.create(Enhancer.java:285)at com.demo.JavaMethodAreaOOM.main(JavaMethodAreaOOM.java:22)4.本機直接內存溢出
執行時需要設置VM參數:-Xmx10M,限制最大堆內存為10M
import sun.misc.Unsafe;import java.lang.reflect.Field;public class DirectMemoryOOM {private static final int _1MB = 1024 * 1024;public static void main(String[] args) throws Exception {Field unsafeField = Unsafe.class.getDeclaredFields()[0];unsafeField.setAccessible(true);Unsafe unsafe = (Unsafe) unsafeField.get(null);while (true) {unsafe.allocateMemory(_1MB);}} }異常內容:
Exception in thread "main" java.lang.OutOfMemoryErrorat sun.misc.Unsafe.allocateMemory(Native Method)at com.demo.DirectMemoryOOM.main(DirectMemoryOOM.java:12)DirectMemory 容量可通過 -XX:MaxDirectMemorySize指定,如果不指定,則默認與 Java 堆最大值(-Xmx 指定)一樣。
三、內存溢出排查
排查最主要的就是檢查代碼,而且內存溢出往往都是代碼的問題。當然以下幾點都是需要注意的:
(1)內存中加載的數據量過于龐大,如一次性從數據庫取出過多數據;
(2)集合類中有對對象的引用,使用完后未清空,是的 JVM 不能回收;
(3)代碼中存在死循環或循環產生過多重復的對象實體;
(4)使用的第三方軟件中的 BUG;
(5)啟動參數內存值設定的過小。
內存溢出解決方法:
方法一:修改 JVM 啟動參數,直接增加內存,暫時解決問題;
方法二:檢查錯誤日志,定位可能發生內存溢出的位置,優化代碼;
方法三:如果設置了內存參數 -XX:+HeapDumpOnOutOfMemoryError,當內存溢出時會產生 .hprof 文件(記錄了 Java 進程在某個時間內的快照),可以通過 MAT 工具進行分析,優化代碼。
整理完畢,完結撒花~
參考地址:
1.java內存溢出,https://blog.csdn.net/u014401141/article/details/122825443
總結
以上是生活随笔為你收集整理的Java 内存溢出(一)原因、复现、排查的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 南邮CTF——逆向
- 下一篇: Dubbo、Spring Cloud和k