偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

JVM 實(shí)戰(zhàn) OutOfMemoryError 異常

運(yùn)維 數(shù)據(jù)庫運(yùn)維 虛擬化
Java堆用于儲(chǔ)存對(duì)象實(shí)例,我們只要不斷地創(chuàng)建對(duì)象,并且保證GC Roots到對(duì)象之間有可達(dá)路徑來避免垃圾回收機(jī)制清除這些對(duì)象,那么隨著對(duì)象數(shù)量的增加,總?cè)萘坑|及最大堆的容量限制后就會(huì)產(chǎn)生內(nèi)存溢出異常。

[[420231]]

在《Java虛擬機(jī)規(guī)范》的規(guī)定里,除了程序計(jì)數(shù)器外,虛擬機(jī)內(nèi)存的其他幾個(gè)運(yùn)行時(shí)區(qū)域都有發(fā)生OutOfMemoryError(下文稱OOM)異常的可能。(本文主要是基于 jdk1.8 展開探討)

Java 堆溢出

Java堆用于儲(chǔ)存對(duì)象實(shí)例,我們只要不斷地創(chuàng)建對(duì)象,并且保證GC Roots到對(duì)象之間有可達(dá)路徑來避免垃圾回收機(jī)制清除這些對(duì)象,那么隨著對(duì)象數(shù)量的增加,總?cè)萘坑|及最大堆的容量限制后就會(huì)產(chǎn)生內(nèi)存溢出異常。

模擬代碼

下面是簡單的模擬堆內(nèi)存溢出的代碼:

  1. /** 
  2.  * VM Args:-Xms10m -Xmx10m -XX:+HeapDumpOnOutOfMemoryError 
  3.  * @author zhengsh 
  4.  * @date 2021-8-13 
  5.  */ 
  6. public class HeapOOM { 
  7.      
  8.     public static void main(String[] args) { 
  9.         List<byte[]> list = new ArrayList<>(); 
  10.         while (true) { 
  11.             list.add(new byte[2048]); 
  12.         } 
  13.     } 

返回結(jié)果信息如下所示:

  1. Exception in thread "main" java.lang.OutOfMemoryError: Java heap space 
  2.  at cn.zhengsh.jvm.oom.HeapOOM.main(HeapOOM.java:16) 

問題分析

我們需要定位是內(nèi)存泄漏(Memory Leak)還是,內(nèi)存溢出(Memory Overflow)

  • 內(nèi)存泄漏
  • 內(nèi)存溢出

內(nèi)存泄漏

我們可以通過 jdk 自帶的 jvisualvm 工具來加載堆快照文件進(jìn)行分析。如果是內(nèi)存泄漏,可進(jìn)一步通過工具查看泄漏對(duì)象到GC Roots的引用鏈,找到泄漏對(duì)象是通過怎樣的引用路徑、與哪些GC Roots相關(guān)聯(lián),才導(dǎo)致垃圾收集器無法回收它們,根據(jù)泄漏對(duì)象的類型信息 以及它到GC Roots引用鏈的信息,一般可以比較準(zhǔn)確地定位到這些對(duì)象創(chuàng)建的位置,進(jìn)而找出產(chǎn)生內(nèi)存泄漏的代碼的具體位置。

內(nèi)存溢出

如果不是內(nèi)存泄漏,換句話說就是內(nèi)存中的對(duì)象確實(shí)都是必須存活的,那就應(yīng)當(dāng)檢查Java虛擬機(jī)的堆參數(shù)(-Xmx與-Xms)設(shè)置,與機(jī)器的內(nèi)存對(duì)比,看看是否還有向上調(diào)整的空間。再從代碼上檢查是否存在某些對(duì)象生命周期過長、持有狀態(tài)時(shí)間過長、存儲(chǔ)結(jié)構(gòu)設(shè)計(jì)不合理等情況,盡量減少程序運(yùn) 行期的內(nèi)存消耗。

虛擬機(jī)棧和本地方法棧溢出

HotSpot虛擬機(jī)中并不區(qū)分虛擬機(jī)棧和本地方法棧,因此對(duì)于HotSpot來說,-Xoss參數(shù)(設(shè)置 本地方法棧大小)雖然存在,但實(shí)際上是沒有任何效果的,棧容量只能由-Xss參數(shù)來設(shè)定。關(guān)于虛擬機(jī)棧和本地方法棧,在《Java虛擬機(jī)規(guī)范》中描述了兩種異常:

  1. 如果線程請(qǐng)求的棧深度大于虛擬機(jī)所允許的最大深度,將拋出StackOverflowError異常。
  2. 如果虛擬機(jī)的棧內(nèi)存允許動(dòng)態(tài)擴(kuò)展,當(dāng)擴(kuò)展棧容量無法申請(qǐng)到足夠的內(nèi)存時(shí),將拋出 OutOfMemoryError異常。

《Java虛擬機(jī)規(guī)范》明確允許Java虛擬機(jī)實(shí)現(xiàn)自行選擇是否支持棧的動(dòng)態(tài)擴(kuò)展,而HotSpot虛擬機(jī) 的選擇是不支持?jǐn)U展,所以除非在創(chuàng)建線程申請(qǐng)內(nèi)存時(shí)就因無法獲得足夠內(nèi)存而出現(xiàn) OutOfMemoryError異常,否則在線程運(yùn)行時(shí)是不會(huì)因?yàn)閿U(kuò)展而導(dǎo)致內(nèi)存溢出的,只會(huì)因?yàn)闂H萘繜o法 容納新的棧幀而導(dǎo)致StackOverflowError異常。

虛擬機(jī)棧內(nèi)存溢出

StackOverflowError

示例代碼:

  1. /** 
  2.  * VM Args:-Xss128k 
  3.  * 
  4.  * @author zhengsh 
  5.  * @date 2021-08-13 
  6.  */ 
  7. public class JavaVMStackSOF { 
  8.     private int stackLength = 1; 
  9.  
  10.     public void stackLeak() { 
  11.         stackLength++; 
  12.         stackLeak(); 
  13.     } 
  14.  
  15.     public static void main(String[] args) throws Throwable { 
  16.         JavaVMStackSOF oom = new JavaVMStackSOF(); 
  17.         try { 
  18.             oom.stackLeak(); 
  19.         } catch (Throwable e) { 
  20.             System.out.println("stack length:" + oom.stackLength); 
  21.             throw e; 
  22.         } 
  23.     } 

返回異常信息

  1. Exception in thread "main" java.lang.StackOverflowError 
  2. stack length:992 
  3.  at cn.zhengsh.jvm.oom.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:13) 
  4.  at cn.zhengsh.jvm.oom.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:14) 
  5.   //.... 省略更多 

OutOfMemoryError

  1. package cn.zhengsh.jvm.oom; 
  2.  
  3. /** 
  4.  * @author zhengsh 
  5.  * @date 2021-08-13 
  6.  */ 
  7. public class JavaVMStackSOF2 { 
  8.     private static int stackLength = 0; 
  9.  
  10.     public static void test() { 
  11.         long unused1, unused2, unused3, unused4, unused5, unused6, unused7, unused8, unused9, unused10, unused11, 
  12.             unused12, unused13, unused14, unused15, unused16, unused17, unused18, unused19, unused20, unused21, 
  13.             unused22, unused23, unused24, unused25, unused26, unused27, unused28, unused29, unused30, unused31, 
  14.             unused32, unused33, unused34, unused35, unused36, unused37, unused38, unused39, unused40, unused41, 
  15.             unused42, unused43, unused44, unused45, unused46, unused47, unused48, unused49, unused50, unused51, 
  16.             unused52, unused53, unused54, unused55, unused56, unused57, unused58, unused59, unused60, unused61, 
  17.             unused62, unused63, unused64, unused65, unused66, unused67, unused68, unused69, unused70, unused71, 
  18.             unused72, unused73, unused74, unused75, unused76, unused77, unused78, unused79, unused80, unused81, 
  19.             unused82, unused83, unused84, unused85, unused86, unused87, unused88, unused89, unused90, unused91, 
  20.             unused92, unused93, unused94, unused95, unused96, unused97, unused98, unused99, unused100; 
  21.         stackLength++; 
  22.         test(); 
  23.         unused1 = unused2 = unused3 = unused4 = unused5 = unused6 = unused7 = unused8 = unused9 = unused10 = unused11 = 
  24.             unused12 = unused13 = unused14 = unused15 = unused16 = unused17 = unused18 = unused19 = unused20 = 
  25.                 unused21 = unused22 = unused23 = unused24 = unused25 = unused26 = unused27 = unused28 = unused29 = 
  26.                     unused30 = unused31 = unused32 = unused33 = unused34 = unused35 = unused36 = unused37 = unused38 = 
  27.                         unused39 = unused40 = unused41 = unused42 = unused43 = 
  28.                             unused44 = unused45 = unused46 = unused47 = unused48 = unused49 = unused50 = unused51 = 
  29.                                 unused52 = unused53 = unused54 = unused55 = unused56 = unused57 = unused58 = unused59 = 
  30.                                     unused60 = unused61 = unused62 = unused63 = unused64 = unused65 = unused66 = 
  31.                                         unused67 = unused68 = unused69 = unused70 = unused71 = unused72 = unused73 = 
  32.                                             unused74 = unused75 = unused76 = unused77 = unused78 = unused79 = unused80 = 
  33.                                                 unused81 = unused82 = unused83 = unused84 = unused85 = unused86 = 
  34.                                                     unused87 = unused88 = unused89 = unused90 = unused91 = unused92 = 
  35.                                                         unused93 = unused94 = unused95 = 
  36.                                                             unused96 = unused97 = unused98 = unused99 = unused100 = 0; 
  37.     } 
  38.  
  39.     public static void main(String[] args) { 
  40.         try { 
  41.             test(); 
  42.         } catch (Error e) { 
  43.             System.out.println("stack length:" + stackLength); 
  44.             throw e; 
  45.         } 
  46.     } 

輸出結(jié)果:

  1. stack length:6986 
  2. Exception in thread "main" java.lang.StackOverflowError 
  3.  at cn.zhengsh.jvm.oom.JavaVMStackSOF2.test(JavaVMStackSOF2.java:22) 
  4.  at cn.zhengsh.jvm.oom.JavaVMStackSOF2.test(JavaVMStackSOF2.java:22) 

總結(jié)

無論是由于棧幀太大還是虛擬機(jī)棧容量太小,當(dāng)新的棧幀內(nèi)存無法分配的時(shí)候, HotSpot虛擬機(jī)拋出的都是StackOverflowError異常??墒侨绻谠试S動(dòng)態(tài)擴(kuò)展棧容量大小的虛擬機(jī)上,相同代碼則會(huì)導(dǎo)致不一樣的情況。

創(chuàng)建線程導(dǎo)致內(nèi)存溢出

注意:下面的這個(gè)實(shí)驗(yàn)可能導(dǎo)致操作系統(tǒng)卡死,建議大家在虛擬機(jī)中執(zhí)行

  1. /** 
  2.  * VM Args:-Xss512k 
  3.  * 
  4.  * @author zhengsh 
  5.  * @date 2021-08-13 
  6.  */ 
  7. public class JavaVMStackOOM { 
  8.      
  9.     private void dontStop() { 
  10.         while (true) { 
  11.         } 
  12.     } 
  13.  
  14.     public void stackLeakByThread() { 
  15.         while (true) { 
  16.             Thread thread = new Thread(new Runnable() { 
  17.                 @Override 
  18.                 public void run() { 
  19.                     dontStop(); 
  20.                 } 
  21.             }); 
  22.             thread.start(); 
  23.         } 
  24.     } 
  25.  
  26.     public static void main(String[] args) throws Throwable { 
  27.         JavaVMStackOOM oom = new JavaVMStackOOM(); 
  28.         oom.stackLeakByThread(); 
  29.     } 

方法區(qū)和運(yùn)行時(shí)常量池溢出

由于運(yùn)行時(shí)常量池是方法區(qū)的一部分,所以這兩個(gè)區(qū)域的溢出測(cè)試可以放到一起進(jìn)行。HotSpot從JDK 7 開始逐步“去永久代”的計(jì)劃,并在JDK 8中完全使用元空間來代替永久代。

方法區(qū)內(nèi)存溢出

方法區(qū)的主要職責(zé)是用于存放類型的相關(guān)信息,如類 名、訪問修飾符、常量池、字段描述、方法描述等。對(duì)于這部分區(qū)域的測(cè)試,基本的思路是運(yùn)行時(shí)產(chǎn) 生大量的類去填滿方法區(qū),直到溢出為止。雖然直接使用Java SE API也可以動(dòng)態(tài)產(chǎn)生類(如反射時(shí)的 GeneratedConstructorAccessor和動(dòng)態(tài)代理等),但在本次實(shí)驗(yàn)中借助了CGLib直接操作字節(jié)碼運(yùn)行時(shí)生成了大量的動(dòng)態(tài)類。

  1. /** 
  2.  * VM Args:-XX:MetaspaceSize=21m -XX:MaxMetaspaceSize=21m 
  3.  * 
  4.  * @author zhengsh 
  5.  * @date 2021-08-13 
  6.  */ 
  7. public class JavaMethodAreaOOM { 
  8.     public static void main(String[] args) { 
  9.         while (true) { 
  10.             Enhancer enhancer = new Enhancer(); 
  11.             enhancer.setSuperclass(OOMObject.class); 
  12.             enhancer.setUseCache(false); 
  13.             enhancer.setCallback(new MethodInterceptor() { 
  14.                 @Override 
  15.                 public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { 
  16.                     return proxy.invokeSuper(obj, args); 
  17.                 } 
  18.             }); 
  19.             enhancer.create(); 
  20.         } 
  21.     } 
  22.  
  23.     static class OOMObject { 
  24.     } 

輸出代碼

  1. Caused by: java.lang.OutOfMemoryError: Metaspace 
  2. Caused by: java.lang.OutOfMemoryError: Metaspace 

常量池案例

String::intern()是一個(gè)本地方法,它的作用是如果字符串常量池中已經(jīng)包含一個(gè)等于此String對(duì)象的 字符串,則返回代表池中這個(gè)字符串的String對(duì)象的引用;否則,會(huì)將此String對(duì)象包含的字符串添加 到常量池中,并且返回此String對(duì)象的引用。在JDK 6或更早之前的HotSpot虛擬機(jī)中,常量池都是分配在永久代中,我們可以通過-XX:PermSize和-XX:MaxPermSize限制永久代的大小,即可間接限制其中常量池的容量。

  1. /** 
  2.  * @author zhengsh 
  3.  * @date 2021-08-13 
  4.  */ 
  5. public class RuntimeConstantPoolOOM2 { 
  6.     public static void main(String[] args) { 
  7.         String str1 = new StringBuilder("計(jì)算機(jī)").append("軟件").toString(); 
  8.         System.out.println(str1.intern() == str1); 
  9.         String str2 = new StringBuilder("ja").append("va").toString(); 
  10.         System.out.println(str2.intern() == str2); 
  11.     } 

這段代碼在JDK 6中運(yùn)行,會(huì)得到兩個(gè)false,而在JDK 7中運(yùn)行,會(huì)得到一個(gè)true和一個(gè)false。產(chǎn)生差異的原因是,在JDK 6中,intern()方法會(huì)把首次遇到的字符串實(shí)例復(fù)制到永久代的字符串常量池中存儲(chǔ),返回的也是永久代里面這個(gè)字符串實(shí)例的引用,而由StringBuilder創(chuàng)建的字符串對(duì)象實(shí)例在 Java堆上,所以必然不可能是同一個(gè)引用,結(jié)果將返回 false。而JDK 7(以及部分其他虛擬機(jī),例如JRockit)的intern()方法實(shí)現(xiàn)就不需要再拷貝字符串的實(shí)例到永久代了,既然字符串常量池已經(jīng)移到Java堆中,那只需要在常量池里記錄一下首次出現(xiàn)的實(shí)例引用即可,因此intern()返回的引用和由StringBuilder創(chuàng)建的那個(gè)字符串實(shí)例就是同一個(gè)。而對(duì)str2比較返 回false,這是因?yàn)?ldquo;java”這個(gè)字符串在執(zhí)行String-Builder.toString()之前就已經(jīng)出現(xiàn)過了,字符串常量 池中已經(jīng)有它的引用,不符合intern()方法要求“首次遇到”的原則,“計(jì)算機(jī)軟件”這個(gè)字符串則是首次出現(xiàn)的,因此結(jié)果返回true。

本機(jī)直接內(nèi)存溢出

直接內(nèi)存(Direct Memory)的容量大小可通過 -XX:MaxDirectMemorySize參數(shù)來指定,默認(rèn)與Java堆最大值(由-Xmx指定)一致,代碼越過了DirectByteBuffer類直接通過反射獲取Unsafe實(shí)例進(jìn)行內(nèi)存分配(Unsafe類的getUnsafe()方法指定只有引導(dǎo)類加載器才會(huì)返回實(shí)例,體現(xiàn)了設(shè)計(jì)者希望只有虛擬機(jī)標(biāo)準(zhǔn)類庫里面的類才能使用Unsafe的功能,在JDK 10時(shí)才將Unsafe 的部分功能通過VarHandle開放給外部使用),因?yàn)殡m然 DirectByteBuffer分配內(nèi)存也會(huì)拋出內(nèi)存溢出異常,但它拋出異常時(shí)并沒有真正向操作系統(tǒng)申請(qǐng)分配內(nèi)存,而是通過計(jì)算得知內(nèi)存無法分配就會(huì)在代碼里手動(dòng)拋出溢出異常,真正申請(qǐng)分配內(nèi)存的方法是 Unsafe::allocateMemory()。

  1. /** 
  2.  * VM Args:-Xmx20M -XX:MaxDirectMemorySize=10M 
  3.  * 
  4.  * @author zhengsh 
  5.  * @date 2021-08-13 
  6.  */ 
  7. public class DirectMemoryOOM { 
  8.     private static final int _1MB = 1024 * 1024; 
  9.  
  10.     public static void main(String[] args) throws Exception { 
  11.         Field unsafeField = Unsafe.class.getDeclaredFields()[0]; 
  12.         unsafeField.setAccessible(true); 
  13.         Unsafe unsafe = (Unsafe)unsafeField.get(null); 
  14.         while (true) { 
  15.             unsafe.allocateMemory(_1MB); 
  16.         } 
  17.     } 

輸出內(nèi)容:

  1. Exception in thread "main" java.lang.OutOfMemoryError 
  2. Exception in thread "main" java.lang.OutOfMemoryError 
  3.  
  4.  at java.base/jdk.internal.misc.Unsafe.allocateMemory(Unsafe.java:616) 
  5.  at jdk.unsupported/sun.misc.Unsafe.allocateMemory(Unsafe.java:462) 
  6.  at cn.zhengsh.jvm.oom.DirectMemoryOOM.main(DirectMemoryOOM.java:21) 

參考資料

《深入理解 JVM 虛擬機(jī)-第三版》 周志明

 

https://docs.oracle.com/javase/specs/jls/se8/html/index.html

 

責(zé)任編輯:武曉燕 來源: 運(yùn)維開發(fā)故事
相關(guān)推薦

2023-08-01 08:20:42

JVM優(yōu)化虛擬機(jī)

2012-05-15 02:04:22

JVMJava

2012-03-01 10:51:37

JavaJVM

2023-10-12 22:35:08

2023-11-15 16:46:04

內(nèi)存Java

2024-12-04 15:49:29

2024-12-04 16:44:51

2010-09-27 13:33:26

JVM異常

2016-10-31 19:41:29

Java垃圾回收

2020-08-10 17:49:25

JVM內(nèi)存溢出

2021-12-05 18:18:20

linux

2024-10-15 08:37:08

2017-07-31 15:47:50

Zuul統(tǒng)一處理

2017-05-18 14:14:25

過濾器Spring ClouZuul

2017-05-19 15:13:05

過濾器Spring ClouZuul

2009-07-15 15:09:18

2012-01-11 13:04:40

JavaJVM

2015-01-09 10:01:50

Spring MVC

2010-09-27 08:38:49

JVM堆JVM棧

2017-09-20 08:48:09

JVM內(nèi)存結(jié)構(gòu)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)