欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

快速定位Java 內(nèi)存OOM的問題

 更新時間:2021年03月02日 09:06:11   作者:程序猿不脫發(fā)2  
這篇文章主要介紹了快速定位Java 內(nèi)存OOM的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧

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

某Java服務(wù)(假設(shè)PID=10765)出現(xiàn)了OOM,最常見的原因為:

有可能是內(nèi)存分配確實過小,而正常業(yè)務(wù)使用了大量內(nèi)存

某一個對象被頻繁申請,卻沒有釋放,內(nèi)存不斷泄漏,導(dǎo)致內(nèi)存耗盡

某一個資源被頻繁申請,系統(tǒng)資源耗盡,例如:不斷創(chuàng)建線程,不斷發(fā)起網(wǎng)絡(luò)連接

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

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

一、確認是不是內(nèi)存本身就分配過小

方法:jmap -heap 10765

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

二、找到最耗內(nèi)存的對象

方法:jmap -histo:live 10765 | more

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

實例數(shù)

所占內(nèi)存大小

類名

是不是很直觀?對于實例數(shù)較多,占用內(nèi)存大小較多的實例/類,相關(guān)的代碼就要針對性review了。

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

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

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

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

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

三、確認是否是資源耗盡

工具:

pstree

netstat

查看進程創(chuàng)建的線程數(shù),以及網(wǎng)絡(luò)連接數(shù),如果資源耗盡,也可能出現(xiàn)OOM。

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

/proc/${PID}/fd

/proc/${PID}/task

可以分別查看句柄詳情和線程數(shù)。

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

ll /proc/9339/fd

ll /proc/9339/task

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

0 -> 標(biāo)準輸入

1 -> 標(biāo)準輸出

2 -> 標(biāo)準錯誤輸出

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

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

所以,只要

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

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

就能知道進程打開的句柄數(shù)和線程數(shù)。

補充:Java內(nèi)存溢出OOM

Java內(nèi)存溢出OOM

經(jīng)典錯誤

JVM中常見的兩個錯誤

StackoverFlowError :棧溢出

OutofMemoryError: java heap space:堆溢出

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

java.lang.StackOverflowError
java.lang.OutOfMemoryError:java heap space
java.lang.OutOfMemoryError:GC overhead limit exceeeded
java.lang.OutOfMemoryError:Direct buffer memory
java.lang.OutOfMemoryError:unable to create new native thread
java.lang.OutOfMemoryError:Metaspace

架構(gòu)

OutOfMemoryError和StackOverflowError是屬于Error,不是Exception

StackoverFlowError

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

棧一般是512K,不斷的深度調(diào)用,直到棧被撐破

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

運行結(jié)果

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

OutOfMemoryError

java heap space

創(chuàng)建了很多對象,導(dǎo)致堆空間不夠存儲

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

我們創(chuàng)建一個80M的數(shù)組,會直接出現(xiàn)Java heap space

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

GC overhead limit exceeded

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

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

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

代碼演示:

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

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

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

/**
 * GC 回收超時
 * JVM參數(shù)配置: -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()方法發(fā)現(xiàn)字符串常量池(存儲永久代)沒有就復(fù)制,物理拷貝
        //1.7時intern()方法發(fā)現(xiàn)字符串常量池(存儲堆)沒有就在保存地址值映射實際堆內(nèi)存對象
        list.add(String.valueOf(++i).intern());
      }
    } catch (Exception e) {
      System.out.println("***************i:" + i);
      e.printStackTrace();
      throw e;
    } finally {
    }
  }
}

運行結(jié)果

[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 1048576K
Exception 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,并沒有清理出空間,在多次執(zhí)行GC操作后,就拋出異常 GC overhead limit

Direct buffer memory

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

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

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

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

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

一句話說:本地內(nèi)存不足,但是堆內(nèi)存充足的時候,就會出現(xiàn)這個問題

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

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

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

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

這個時候,運行就會出現(xiàn)問題了

配置的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

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

在高并發(fā)場景的時候,會應(yīng)用到

高并發(fā)請求服務(wù)器時,經(jīng)常會出現(xiàn)如下異常java.lang.OutOfMemoryError:unable to create new native thread,準確說該native thread異常與對應(yīng)的平臺有關(guān)

導(dǎo)致原因:

應(yīng)用創(chuàng)建了太多線程,一個應(yīng)用進程創(chuàng)建多個線程,超過系統(tǒng)承載極限

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

解決方法:

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

對于有的應(yīng)用,確實需要創(chuàng)建很多線程,遠超過linux系統(tǒng)默認1024個線程限制,可以通過修改linux服務(wù)器配置,擴大linux默認限制

/**
 * 無法創(chuàng)建更多的線程
 */
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();
    }
  }
}

這個時候,就會出現(xiàn)下列的錯誤,線程數(shù)大概在 900多個

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

如何查看線程數(shù)

ulimit -u

Metaspace

元空間內(nèi)存不足,Matespace元空間應(yīng)用的是本地內(nèi)存

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

元空間是什么

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

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

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

虛擬機加載的類信息

常量池

靜態(tài)變量

即時編譯后的代碼

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

代碼

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

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

代碼如下:

/**
 * 元空間溢出
 *
 */
public class MetaspaceOutOfMemoryDemo {
  // 靜態(tài)類
  static class OOMTest {
  }
  public static void main(final String[] args) {
    // 模擬計數(shù)多少次以后發(fā)生異常
    int i =0;
    try {
      while (true) {
        i++;
        // 使用Spring的動態(tài)字節(jié)碼技術(shù)
        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("發(fā)生異常的次數(shù):" + i);
      e.printStackTrace();
    } finally {
    }
  }
}

會出現(xiàn)以下錯誤:

發(fā)生異常的次數(shù): 201

java.lang.OutOfMemoryError:Metaspace

注意

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

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

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

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

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。如有錯誤或未考慮完全的地方,望不吝賜教。

相關(guān)文章

  • Spring JPA整合QueryDSL的示例代碼

    Spring JPA整合QueryDSL的示例代碼

    這篇文章主要介紹了Spring JPA整合QueryDSL的示例代碼,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-10-10
  • Java銀行取錢線程安全問題實例分析

    Java銀行取錢線程安全問題實例分析

    這篇文章主要介紹了Java銀行取錢線程安全問題,結(jié)合具體實例形式分析了java使用線程操作模擬銀行取錢的相關(guān)安全問題,需要的朋友可以參考下
    2019-09-09
  • Java中將File轉(zhuǎn)化為MultipartFile的操作

    Java中將File轉(zhuǎn)化為MultipartFile的操作

    這篇文章主要介紹了Java中將File轉(zhuǎn)化為MultipartFile的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-10-10
  • 深入了解JVM(Java虛擬機)內(nèi)存結(jié)構(gòu)

    深入了解JVM(Java虛擬機)內(nèi)存結(jié)構(gòu)

    Java虛擬機(Java Virtual Machine,JVM)是Java程序的運行環(huán)境,它是一個抽象的計算機模型,通過解釋和執(zhí)行Java字節(jié)碼來運行Java程序,本將大家深入了解JVM(Java虛擬機)內(nèi)存結(jié)構(gòu),需要的朋友可以參考下
    2023-08-08
  • Java并發(fā)編程Callable與Future的應(yīng)用實例代碼

    Java并發(fā)編程Callable與Future的應(yīng)用實例代碼

    這篇文章主要介紹了Java并發(fā)編程Callable與Future的應(yīng)用實例代碼,具有一定借鑒價值,需要的朋友可以參考下
    2018-01-01
  • 詳解如何使用SpringBoot的緩存@Cacheable

    詳解如何使用SpringBoot的緩存@Cacheable

    這篇文章主要為大家介紹了如何使用SpringBoot的緩存@Cacheable詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-06-06
  • maven deploy時報錯的解決方法

    maven deploy時報錯的解決方法

    這篇文章主要介紹了maven deploy時報錯的解決方法,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-09-09
  • springboot項目完整后端請求Controller層優(yōu)雅處理

    springboot項目完整后端請求Controller層優(yōu)雅處理

    這篇文章主要為大家介紹了springboot項目Controller層代碼的優(yōu)雅處理實例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-07-07
  • Spring初始化和銷毀的實現(xiàn)方法

    Spring初始化和銷毀的實現(xiàn)方法

    這篇文章主要介紹了Spring初始化和銷毀的實現(xiàn)方法,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2019-10-10
  • SpringBoot實現(xiàn)接口版本控制的示例代碼

    SpringBoot實現(xiàn)接口版本控制的示例代碼

    這篇文章主要介紹了springboot如何實現(xiàn)接口版本控制,接口版本控制,比如微服務(wù)請求中某個接口需要升級,正常做法是升級我們的版本,文中有詳細的代碼示例供大家參考,具有一定的參考價值,需要的朋友可以參考下
    2024-03-03

最新評論