国产成人精品久久免费动漫-国产成人精品天堂-国产成人精品区在线观看-国产成人精品日本-a级毛片无码免费真人-a级毛片毛片免费观看久潮喷

您的位置:首頁技術文章
文章詳情頁

快速定位Java 內存OOM的問題

瀏覽:4日期:2022-08-16 08:20:20

Java服務出現了OOM(Out Of Memory)問題,總結了一些相對通用的方案,希望能幫助到Java技術棧的同學。

某Java服務(假設PID=10765)出現了OOM,最常見的原因為:

有可能是內存分配確實過小,而正常業務使用了大量內存

某一個對象被頻繁申請,卻沒有釋放,內存不斷泄漏,導致內存耗盡

某一個資源被頻繁申請,系統資源耗盡,例如:不斷創建線程,不斷發起網絡連接

畫外音:無非“本身資源不夠”“申請資源太多”“資源耗盡”幾個原因。

更具體的,可以使用以下工具逐一排查。

一、確認是不是內存本身就分配過小

方法:jmap -heap 10765

快速定位Java 內存OOM的問題

如上圖,可以查看新生代,老生代堆內存的分配大小以及使用情況,看是否本身分配過小。

二、找到最耗內存的對象

方法:jmap -histo:live 10765 | more

快速定位Java 內存OOM的問題

如上圖,輸入命令后,會以表格的形式顯示存活對象的信息,并按照所占內存大小排序:

實例數

所占內存大小

類名

是不是很直觀?對于實例數較多,占用內存大小較多的實例/類,相關的代碼就要針對性review了。

上圖中占內存最多的對象是RingBufferLogEvent,共占用內存18M,屬于正常使用范圍。

如果發現某類對象占用內存很大(例如幾個G),很可能是類對象創建太多,且一直未釋放。例如:

申請完資源后,未調用close()或dispose()釋放資源

消費者消費速度慢(或停止消費了),而生產者不斷往隊列中投遞任務,導致隊列中任務累積過多

畫外音:線上執行該命令會強制執行一次fgc。另外還可以dump內存進行分析。

三、確認是否是資源耗盡

工具:

pstree

netstat

查看進程創建的線程數,以及網絡連接數,如果資源耗盡,也可能出現OOM。

這里介紹另一種方法,通過

/proc/${PID}/fd

/proc/${PID}/task

可以分別查看句柄詳情和線程數。

例如,某一臺線上服務器的sshd進程PID是9339,查看

ll /proc/9339/fd

ll /proc/9339/task

快速定位Java 內存OOM的問題

如上圖,sshd共占用了四個句柄

0 -> 標準輸入

1 -> 標準輸出

2 -> 標準錯誤輸出

3 -> socket(容易想到是監聽端口)

sshd只有一個主線程PID為9339,并沒有多線程。

所以,只要

ll /proc/${PID}/fd | wc -l

ll /proc/${PID}/task | wc -l (效果等同pstree -p | wc -l)

就能知道進程打開的句柄數和線程數。

補充:Java內存溢出OOM

Java內存溢出OOM

經典錯誤

JVM中常見的兩個錯誤

StackoverFlowError :棧溢出

OutofMemoryError: java heap space:堆溢出

除此之外,還有以下的錯誤

java.lang.StackOverflowErrorjava.lang.OutOfMemoryError:java heap spacejava.lang.OutOfMemoryError:GC overhead limit exceeededjava.lang.OutOfMemoryError:Direct buffer memoryjava.lang.OutOfMemoryError:unable to create new native threadjava.lang.OutOfMemoryError:Metaspace架構

OutOfMemoryError和StackOverflowError是屬于Error,不是Exception

快速定位Java 內存OOM的問題

StackoverFlowError

堆棧溢出,我們有最簡單的一個遞歸調用,就會造成堆棧溢出,也就是深度的方法調用

棧一般是512K,不斷的深度調用,直到棧被撐破

public class StackOverflowErrorDemo { public static void main(String[] args) { stackOverflowError(); } /** * 棧一般是512K,不斷的深度調用,直到棧被撐破 * Exception in thread 'main' java.lang.StackOverflowError */ private static void stackOverflowError() { stackOverflowError(); }}

運行結果

Exception in thread 'main' java.lang.StackOverflowError at com.moxi.interview.study.oom.StackOverflowErrorDemo.stackOverflowError(StackOverflowErrorDemo.java:17)OutOfMemoryError

java heap space

創建了很多對象,導致堆空間不夠存儲

/** * Java堆內存不足 */public class JavaHeapSpaceDemo { public static void main(String[] args) { // 堆空間的大小 -Xms10m -Xmx10m // 創建一個 80M的字節數組 byte [] bytes = new byte[80 * 1024 * 1024]; }}

我們創建一個80M的數組,會直接出現Java heap space

Exception in thread 'main' java.lang.OutOfMemoryError: Java heap spaceGC overhead limit exceeded

GC回收時間過長時會拋出OutOfMemoryError,過長的定義是,超過了98%的時間用來做GC,并且回收了不到2%的堆內存

連續多次GC都只回收了不到2%的極端情況下,才會拋出。假設不拋出GC overhead limit 錯誤會造成什么情況呢?

那就是GC清理的這點內存很快會再次被填滿,迫使GC再次執行,這樣就形成了惡性循環,CPU的使用率一直都是100%,而GC卻沒有任何成果。

快速定位Java 內存OOM的問題

代碼演示:

為了更快的達到效果,我們首先需要設置JVM啟動參數

-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m

這個異常出現的步驟就是,我們不斷的像list中插入String對象,直到啟動GC回收

/** * GC 回收超時 * JVM參數配置: -Xms10m -Xmx10m -XX:+PrintGCDetails */public class GCOverheadLimitDemo { public static void main(String[] args) { int i = 0; List<String> list = new ArrayList<>(); try { while(true) {//1.6時intern()方法發現字符串常量池(存儲永久代)沒有就復制,物理拷貝//1.7時intern()方法發現字符串常量池(存儲堆)沒有就在保存地址值映射實際堆內存對象list.add(String.valueOf(++i).intern()); } } catch (Exception e) { System.out.println('***************i:' + i); e.printStackTrace(); throw e; } finally { } }}

運行結果

[Full GC (Ergonomics) [PSYoungGen: 2047K->2047K(2560K)] [ParOldGen: 7106K->7106K(7168K)] 9154K->9154K(9728K), [Metaspace: 3504K->3504K(1056768K)], 0.0311093 secs] [Times: user=0.13 sys=0.00, real=0.03 secs] [Full GC (Ergonomics) [PSYoungGen: 2047K->0K(2560K)] [ParOldGen: 7136K->667K(7168K)] 9184K->667K(9728K), [Metaspace: 3540K->3540K(1056768K)], 0.0058093 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] Heap PSYoungGen total 2560K, used 114K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000) eden space 2048K, 5% used [0x00000000ffd00000,0x00000000ffd1c878,0x00000000fff00000) from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000) to space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000) ParOldGen total 7168K, used 667K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000) object space 7168K, 9% used [0x00000000ff600000,0x00000000ff6a6ff8,0x00000000ffd00000) Metaspace used 3605K, capacity 4540K, committed 4864K, reserved 1056768K class space used 399K, capacity 428K, committed 512K, reserved 1048576KException in thread 'main' java.lang.OutOfMemoryError: GC overhead limit exceeded at java.lang.Integer.toString(Integer.java:403) at java.lang.String.valueOf(String.java:3099) at com.moxi.interview.study.oom.GCOverheadLimitDemo.main(GCOverheadLimitDemo.java:18)

我們能夠看到 多次Full GC,并沒有清理出空間,在多次執行GC操作后,就拋出異常 GC overhead limit

Direct buffer memory

Netty + NIO:這是由于NIO引起的

寫NIO程序的時候經常會使用ByteBuffer來讀取或寫入數據,這是一種基于通道(Channel) 與 緩沖區(Buffer)的I/O方式,它可以使用Native 函數庫直接分配堆外內存,然后通過一個存儲在Java堆里面的DirectByteBuffer對象作為這塊內存的引用進行操作。這樣能在一些場景中顯著提高性能,因為避免了在Java堆和Native堆中來回復制數據。

ByteBuffer.allocate(capability):第一種方式是分配JVM堆內存,屬于GC管轄范圍,由于需要拷貝所以速度相對較慢

ByteBuffer.allocteDirect(capability):第二種方式是分配OS本地內存,不屬于GC管轄范圍,由于不需要內存的拷貝,所以速度相對較快

但如果不斷分配本地內存,堆內存很少使用,那么JVM就不需要執行GC,DirectByteBuffer對象就不會被回收,這時候堆內存充足,但本地內存可能已經使用光了,再次嘗試分配本地內存就會出現OutOfMemoryError,那么程序就崩潰了。

一句話說:本地內存不足,但是堆內存充足的時候,就會出現這個問題

我們使用 -XX:MaxDirectMemorySize=5m 配置能使用的堆外物理內存為5M

-Xms20m -Xmx20m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m

然后我們申請一個6M的空間

// 只設置了5M的物理內存使用,但是卻分配 6M的空間ByteBuffer bb = ByteBuffer.allocateDirect(6 * 1024 * 1024);

這個時候,運行就會出現問題了

配置的maxDirectMemory:5.0MB

[GC (System.gc()) [PSYoungGen: 2030K->488K(2560K)] 2030K->796K(9728K), 0.0008326 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC (System.gc()) [PSYoungGen: 488K->0K(2560K)] [ParOldGen: 308K->712K(7168K)] 796K->712K(9728K), [Metaspace: 3512K->3512K(1056768K)], 0.0052052 secs] [Times: user=0.09 sys=0.00, real=0.00 secs] Exception in thread 'main' java.lang.OutOfMemoryError: Direct buffer memory at java.nio.Bits.reserveMemory(Bits.java:693) at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:123) at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:311) at com.moxi.interview.study.oom.DIrectBufferMemoryDemo.main(DIrectBufferMemoryDemo.java:19)unable to create new native thread

不能夠創建更多的新的線程了,也就是說創建線程的上限達到了

在高并發場景的時候,會應用到

高并發請求服務器時,經常會出現如下異常java.lang.OutOfMemoryError:unable to create new native thread,準確說該native thread異常與對應的平臺有關

導致原因:

應用創建了太多線程,一個應用進程創建多個線程,超過系統承載極限

服務器并不允許你的應用程序創建這么多線程,linux系統默認運行單個進程可以創建的線程為1024個,如果應用創建超過這個數量,就會報 java.lang.OutOfMemoryError:unable to create new native thread

解決方法:

想辦法降低你應用程序創建線程的數量,分析應用是否真的需要創建這么多線程,如果不是,改代碼將線程數降到最低

對于有的應用,確實需要創建很多線程,遠超過linux系統默認1024個線程限制,可以通過修改linux服務器配置,擴大linux默認限制

/** * 無法創建更多的線程 */public class UnableCreateNewThreadDemo { public static void main(String[] args) { for (int i = 0; ; i++) { System.out.println('************** i = ' + i); new Thread(() -> {try { TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);} catch (InterruptedException e) { e.printStackTrace();} }, String.valueOf(i)).start(); } }}

這個時候,就會出現下列的錯誤,線程數大概在 900多個

Exception in thread 'main' java.lang.OutOfMemoryError: unable to cerate new native thread

如何查看線程數

ulimit -u

Metaspace

元空間內存不足,Matespace元空間應用的是本地內存

-XX:MetaspaceSize 的初始化大小為20M

元空間是什么

元空間就是我們的方法區,存放的是類模板,類信息,常量池等

Metaspace是方法區HotSpot中的實現,它與持久代最大的區別在于:Metaspace并不在虛擬內存中,而是使用本地內存,也即在java8中,class metadata(the virtual machines internal presentation of Java class),被存儲在叫做Matespace的native memory

永久代(java8后背元空間Metaspace取代了)存放了以下信息:

虛擬機加載的類信息

常量池

靜態變量

即時編譯后的代碼

模擬Metaspace空間溢出,我們不斷生成類 往元空間里灌輸,類占據的空間總會超過Metaspace指定的空間大小

代碼

在模擬異常生成時候,因為初始化的元空間為20M,因此我們使用JVM參數調整元空間的大小,為了更好的效果

-XX:MetaspaceSize=8m -XX:MaxMetaspaceSize=8m

代碼如下:

/** * 元空間溢出 * */public class MetaspaceOutOfMemoryDemo { // 靜態類 static class OOMTest { } public static void main(final String[] args) { // 模擬計數多少次以后發生異常 int i =0; try { while (true) {i++;// 使用Spring的動態字節碼技術Enhancer enhancer = new Enhancer();enhancer.setSuperclass(OOMTest.class);enhancer.setUseCache(false);enhancer.setCallback(new MethodInterceptor() { @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { return methodProxy.invokeSuper(o, args); }}); } } catch (Exception e) { System.out.println('發生異常的次數:' + i); e.printStackTrace(); } finally { } }}

會出現以下錯誤:

發生異常的次數: 201

java.lang.OutOfMemoryError:Metaspace注意

在JDK1.7之前:永久代是方法區的實現,存放了運行時常量池、字符串常量池和靜態變量等。

在JDK1.7:永久代是方法區的實現,將字符串常量池和靜態變量等移出至堆內存。運行時常量池等剩下的還再永久代(方法區)

在JDK1.8及以后:永久代被元空間替代,相當于元空間實現方法區,此時字符串常量池和靜態變量還在堆,運行時常量池還在方法區(元空間),元空間使用的是直接內存。

-XX:MetaspaceSize=N//設置Metaspace的初始(和最小大小) -XX:MaxMetaspaceSize=N//設置Metaspace的最大大小 與永久代很大的不同就是,如果不指定大小的話,隨著更多類的創建,虛擬機會耗盡所有可用的系統內存。

以上為個人經驗,希望能給大家一個參考,也希望大家多多支持好吧啦網。如有錯誤或未考慮完全的地方,望不吝賜教。

標簽: Java
相關文章:
主站蜘蛛池模板: 欧美一级在线免费观看 | 国产一区二区三区久久 | 美国毛片网 | 久久国产精品久久国产精品 | 经典香港a毛片免费观看 | 禁止18周岁进入免费网站观看 | 日韩一区二区天海翼 | 99久久精品国产自免费 | 八戒午夜精品视频在线观看 | 理论在线看 | 国产精品欧美亚洲韩国日本 | 日本一级在线播放线观看免 | 国产免费成人在线视频 | 国产二区三区毛片 | 成人在线手机视频 | 久久久久亚洲精品一区二区三区 | 久久亚洲精品中文字幕第一区 | 国产一级在线现免费观看 | 欧美一区亚洲二区 | 高清毛片一区二区三区 | 国产高清视频免费最新在线 | 美女叉开腿让男人捅 | 美女扒开腿让男人桶 | 天天看夜夜操 | 亚洲欧美日韩在线精品一区二区 | 男人干女人逼 | 欧美日韩高清观看一区二区 | 日本在线免费观看视频 | 国产精品爱久久久久久久三级 | 亚洲欧美日韩精品久久久 | 国产视频久久久久 | 国产精品yjizz视频网一二区 | 欧美理论在线 | 久久精品国产亚洲欧美 | 亚洲伦 | 国产精品三级一区二区 | 亚洲理论片在线中文字幕 | 一区二区三区在线 | 欧 | 欧美一级毛片欧美毛片视频 | 亚洲在线视频观看 | 成人一级网站 |