JAVA?OOM內(nèi)存溢出問題深入解析
JAVA內(nèi)存OOM溢出的幾種常見業(yè)務(wù)場景
當(dāng)生產(chǎn)系統(tǒng)遇到莫種場景時(shí)就會(huì)觸發(fā)OOM內(nèi)存溢出,導(dǎo)致系統(tǒng)不可用。常見的場景如:
- 秒殺活動(dòng):當(dāng)大量的請求進(jìn)來的時(shí)候,導(dǎo)致系統(tǒng)瞬間創(chuàng)建大量的內(nèi)存對象,如果內(nèi)存對象都是大對象而且不能及時(shí)釋放,就會(huì)撐爆內(nèi)存
- 特殊業(yè)務(wù)場景:如獲取報(bào)表數(shù)據(jù),報(bào)表信息需要從數(shù)據(jù)庫中查詢大量的數(shù)據(jù)到內(nèi)存中進(jìn)行處理,如果對數(shù)據(jù)沒有控制的話,就會(huì)撐爆內(nèi)存
- 內(nèi)存空間分配過小:如tomcat類的web容器分配的java內(nèi)存空間過小,當(dāng)過多的線程創(chuàng)建的對象直接把內(nèi)存用完
- 創(chuàng)建的類過多,導(dǎo)致永久代或者元空間被消耗光
JAVA內(nèi)存結(jié)構(gòu)原理
- 類的加載
- 編寫好的java文件通過IDE編譯,變成.class二進(jìn)制字節(jié)文件
- 該字節(jié)文件加載進(jìn)類加載器
詳細(xì)過程如下,java程序運(yùn)行時(shí),虛擬機(jī)會(huì)單獨(dú)劃分出一塊內(nèi)存區(qū)域,我們需要特別關(guān)注堆和棧這兩塊區(qū)域,生產(chǎn)搶修頻繁出問題的也是這兩個(gè)區(qū)域。(堆負(fù)責(zé)數(shù)據(jù)存儲(chǔ),棧則負(fù)責(zé)程序運(yùn)行)
- JAVA 堆
Java 堆區(qū)還可以劃分為新生代和老年代,新生代又可以進(jìn)一步劃分為 Eden 區(qū)、Survivor 1 區(qū)、Survivor 2 區(qū)。
該區(qū)域的主要特點(diǎn):
- 該區(qū)域是程序的數(shù)據(jù)存儲(chǔ)區(qū)域,主要負(fù)責(zé)存放new對象,但不存放數(shù)據(jù)類型和對象引用
- 該區(qū)域是垃圾回收的主要工作區(qū)域
- JAVA棧
存放基本數(shù)據(jù)類型(boolean、byte、char、short、int、float、long、double)以及對象的引用(它可能是一個(gè)指向?qū)ο笃鹗嫉刂返囊弥羔?,也可能指向一個(gè)代表對象的句柄或者其他與此對象相關(guān)的位置)。
這個(gè)區(qū)域可能有兩種異常:如果線程請求的棧深度大于虛擬機(jī)所允許的深度,將拋出 StackOverflowError 異常;如果虛擬機(jī)??梢詣?dòng)態(tài)擴(kuò),當(dāng)擴(kuò)展時(shí)無法申請到足夠的內(nèi)存時(shí)會(huì)拋出 OutOfMemoryError異常。
設(shè)計(jì)程序演示內(nèi)存溢出現(xiàn)象
為了更好的理解程序是如何運(yùn)行,并導(dǎo)致JAVA內(nèi)存溢出的,我設(shè)計(jì)了四個(gè)場景來進(jìn)行模擬演示
- 棧溢出(StackOverflowError)
- 堆溢出(OutOfMemoryError:Java heap space)
- 永久代溢出 java8之前版本(OutOfMemoryError: PermGen space)。java8之后(包含)版本(OutOfMemoryError: PermGen space)
- 直接內(nèi)存溢出
- 棧溢出(StackOverflowError)
線程請求的棧深度大于虛擬機(jī)所允許的深度,將拋出 StackOverflowError 異常
/** * 棧溢出 */ public class Stack { public static void main(String[] args) { new Stack().test(); } public void test() { test(); } }
棧深度被消耗殆盡拋出異常
Exception in thread "main" java.lang.StackOverflowError
at Stack.test(Stack.java:11)
at Stack.test(Stack.java:11)
at Stack.test(Stack.java:11)
- 堆溢出(OutOfMemoryError:Java heap space)
通過不斷的往數(shù)組列表中存放大對象,最終把內(nèi)存資源消耗殆盡
import java.util.ArrayList; import java.util.List; public class HeapOOMTest { public static void main(String[] args) { List<byte[]> list = new ArrayList<>(); try { while (true) { // 分配大量的內(nèi)存,每次分配1MB的字節(jié)數(shù)組 byte[] data = new byte[1024 * 1024]; // 1MB list.add(data); } } catch (OutOfMemoryError e) { System.out.println("發(fā)生堆溢出錯(cuò)誤:Java heap space"); e.printStackTrace(); } } }
拋出異常
發(fā)生堆溢出錯(cuò)誤:Java heap space
java.lang.OutOfMemoryError: Java heap space
at HeapOOMTest.main(HeapOOMTest.java:11)
- 永久代溢出 java8之前版本(OutOfMemoryError: PermGen space)。java8之后(包含)版本(OutOfMemoryError: Metaspace)
java1.8之后就舍棄了永久代,取而代之的是元空間。我的環(huán)境是jdk1.8,所以只能演示元空間的報(bào)錯(cuò)。
這段Java代碼演示了如何在Java程序中觸發(fā)"OutOfMemoryError: Metaspace"錯(cuò)誤。它通過不斷生成新的類、加載這些類并將其加入到列表中,最終導(dǎo)致"Metaspace"區(qū)域的內(nèi)存耗盡。
具體流程如下:
- 在
main
方法中,通過ArrayList
創(chuàng)建一個(gè)classes
列表,用于存儲(chǔ)動(dòng)態(tài)生成的類。 在
while(true)
循環(huán)中,程序不斷執(zhí)行以下操作:- 生成一個(gè)唯一的類名("DynamicClass" + 當(dāng)前納秒時(shí)間戳)。
- 使用
generateClassBytes
方法生成類的字節(jié)碼。 - 將生成的字節(jié)碼寫入到以類名命名的文件中。
- 使用自定義的
CustomClassLoader
類加載器加載這個(gè)類。 - 將加載后的類對象添加到
classes
列表中。 - 可選地,刪除已經(jīng)加載的類文件,釋放磁盤空間。
- 當(dāng)"Metaspace"區(qū)域的內(nèi)存耗盡時(shí),會(huì)拋出"OutOfMemoryError: Metaspace"異常,程序捕獲并打印異常信息。
import java.util.List; import java.util.ArrayList; import java.nio.file.*; import java.io.IOException; public class PermGenOutOfMemory { public static void main(String[] args) { List<Class<?>> classes = new ArrayList<>(); try { while (true) { // Generate a unique class name and bytecode String className = "DynamicClass" + System.nanoTime(); byte[] classData = generateClassBytes(className); // Define the class by writing it to a file and loading it Path classFilePath = Paths.get(className + ".class"); Files.write(classFilePath, classData); Class<?> clazz = loadClass(className, classFilePath); classes.add(clazz); // Optional: Delete the class file after loading Files.delete(classFilePath); } } catch (OutOfMemoryError | IOException e) { System.out.println("OutOfMemoryError: Metaspace"); e.printStackTrace(); } } static byte[] generateClassBytes(String className) { // Replace this with your own class bytecode generation logic // You can use libraries like ASM for bytecode generation // For simplicity, let's just create a class with a single method String classContent = "public class " + className + " { public void doSomething() { System.out.println(\"Hello from " + className + "\"); } }"; return classContent.getBytes(); } static Class<?> loadClass(String className, Path classFilePath) throws IOException { // Load the class from the file into the Metaspace byte[] classData = Files.readAllBytes(classFilePath); return new CustomClassLoader().loadClass(className, classData); } static class CustomClassLoader extends ClassLoader { public Class<?> loadClass(String name, byte[] classData) { return defineClass(name, classData, 0, classData.length); } } }
- 直接內(nèi)存溢出
分配 1MB 的直接內(nèi)存并將其累加到 allocatedMemory 中,使用 unsafe.allocateMemory(memory) 分配直接內(nèi)存。
如果在分配直接內(nèi)存時(shí)發(fā)生異常,則會(huì)捕獲異常并打印異常堆棧信息。
import sun.misc.Unsafe; import java.lang.reflect.Field; public class DirectMemoryOOMTest { public static void main(String[] args) { int i = 0; long allocatedMemory = 0; Unsafe unsafe = null; try { Field field = Unsafe.class.getDeclaredFields()[0]; field.setAccessible(true); unsafe = (Unsafe) field.get(null); while (true) { long memory = 1024 * 1024; allocatedMemory += memory; unsafe.allocateMemory(memory); i++; } } catch (Exception e) { e.printStackTrace(); } } }
Exception in thread "main" java.lang.OutOfMemoryError
at sun.misc.Unsafe.allocateMemory(Native Method)
at DirectMemoryOOMTest.main(DirectMemoryOOMTest.java:19)
PS D:\java>
總結(jié)
- 了解了java內(nèi)存結(jié)構(gòu)原理和生產(chǎn)主要發(fā)生問題的區(qū)域
- 了解了哪些業(yè)務(wù)場景會(huì)導(dǎo)致的OOM
- 通過代碼演示了幾種導(dǎo)致OOM問題的情況
以上就是JAVA內(nèi)存溢出問題深入解析的詳細(xì)內(nèi)容,更多關(guān)于JAVA 內(nèi)存溢出的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java函數(shù)式編程之通過行為參數(shù)化傳遞代碼
行為參數(shù)化就是可以幫助你處理頻繁變更的需求的一種軟件開發(fā)模式,這篇文章將給大家詳細(xì)的介紹一下Java函數(shù)式編程之行為參數(shù)化傳遞代碼,感興趣的同學(xué)可以參考閱讀下2023-08-08java導(dǎo)出Excel(非模板)可導(dǎo)出多個(gè)sheet方式
Java開發(fā)中,導(dǎo)出Excel是常見需求,有時(shí)需要支持多個(gè)Sheet導(dǎo)出,此技巧介紹非模板方式實(shí)現(xiàn)單標(biāo)題單Sheet以及多Sheet導(dǎo)出,標(biāo)題一致或不一致均可,可換成Map使用,適合個(gè)人開發(fā)者和需要Excel導(dǎo)出功能的場景2024-09-09基于HTTP協(xié)議實(shí)現(xiàn)簡單RPC框架的方法詳解
RPC全名(Remote?Procedure?Call),翻譯過來就是遠(yuǎn)程過程調(diào)用,本文將為大家介紹如何基于HTTP協(xié)議實(shí)現(xiàn)簡單RPC框架,感興趣的小伙伴可以了解一下2023-06-06使用ClassFinal實(shí)現(xiàn)SpringBoot項(xiàng)目jar包加密的操作指南
在實(shí)際開發(fā)中,保護(hù)項(xiàng)目的安全性和保密性是至關(guān)重要的,針對于 Spring Boot 項(xiàng)目,我們需要將 JAR 包進(jìn)行加密從而有效地防止未經(jīng)授權(quán)的訪問和修改,本文將介紹如何使用ClassFinal在 Spring Boot 項(xiàng)目中實(shí)現(xiàn) JAR 包加密,需要的朋友可以參考下2024-06-06Java實(shí)現(xiàn)圖片上傳到服務(wù)器并把上傳的圖片讀取出來
在各大網(wǎng)站上都可以實(shí)現(xiàn)上傳頭像功能,可以選擇自己喜歡的圖片做頭像,從本地上傳,今天小編給大家分享Java實(shí)現(xiàn)圖片上傳到服務(wù)器并把上傳的圖片讀取出來,需要的朋友參考下2017-02-02