1. 程式人生 > >Java8虛擬機器(JVM)記憶體溢位實戰

Java8虛擬機器(JVM)記憶體溢位實戰

前言

相信很多JAVA中高階的同學在面試的時候會經常碰到一個面試題
你是如何在工作中對JVM調優和排查定位問題的?

事實上,如果使用者量不大的情況下,在你的程式碼還算正常的情況下,在工作中除非真正碰到與JVM相關的問題是少之又少,就算碰到了也是由公司的一些大牛去排查解決,那麼我們又如何積累這方面的經驗呢?下面由衝鍋帶大家一起來實踐JVM的調優吧

注意我們平常所說的JVM調優一般指Java堆,Java虛擬機器棧引數調優

Java堆溢位

先來一段程式碼示例,注意筆者用的是IDEA工具,需要配置一下VM options 為-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError,如果不清楚的百度一下如何配置idea的JVM執行引數

package com.example.demo.jvm;

import java.util.ArrayList;
import java.util.List;

/**
 * @Author: Wang Chong 
 * @Date: 2019/9/22 9:37
 * @Version: V1.0
 */
public class HeapOutMemoryTest {
    static class ChongGuo {

    }
    public static void main(String[] args) {
        List<ChongGuo> chongGuos = new ArrayList<>();
        while (true) {
            chongGuos.add(new ChongGuo());
        }
    }
}

執行結果如下:

java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid9352.hprof ...
Heap dump file created [28701160 bytes in 0.122 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at 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.example.demo.jvm.HeapOutMemoryTest.main(HeapOutMemoryTest.java:18)
Disconnected from the target VM, address: '127.0.0.1:54599', transport: 'socket'

可以看到控制檯出現java.lang.OutOfMemoryError: Java heap space的錯誤,這是為什麼呢,首先先解釋一下上面的執行引數

-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError


  • -Xms20m:設定JVM最小記憶體為20m。此值可以設定與-Xmx相同,以避免每次垃圾回收完成後JVM重新分配記憶體

  • -Xmx20m:設定JVM最大可用記憶體20M

  • -XX:+HeapDumpOnOutOfMemoryError 表示當JVM發生OOM時,自動生成DUMP檔案


下面我們分析一下出錯的原因,用JProfiler分析一下,開啟剛才生成的名為java_pid9352.hprof的dump檔案。可以看到根據(InstanceXcount和Size)基本可以確定哪個類的物件出現問題,在上面示例中,可以是ChongGuo這個例項生在數量的大小已經超過12M,但沒有超過20M,那麼新問題又來了?沒到20M為啥會報堆記憶體溢位呢?

答案就是JDK8中堆記憶體中還包括Metaspace,即元記憶體空間,在元空間出現前JDK1.7之前在JDK7以及其前期的JDK版本號中。堆記憶體通常被分為三塊區域Nursery記憶體(young generation)、長時記憶體(old generation)、永久記憶體(Permanent Generation for VM Matedata),如下圖

當中最上一層是年輕代,一個物件被建立以後首先被放到年輕代中的Eden記憶體中,假設存活期超兩個Survivor之後就會被轉移到長時記憶體中(Old Generation)中永久記憶體中存放著物件的方法、變數等元資料資訊。通過假設永久記憶體不夠。我們就會得到例如以下錯誤:java.lang.OutOfMemoryError: PermGen
而在JDK8中情況發生了明顯的變化,就是普通情況下你都不會得到這個錯誤,原因
在於JDK8中把存放元資料中的永久記憶體從堆記憶體中移到了本地記憶體(native memory)
中,JDK8中JVM堆記憶體結構就變成了例如以下:

如果我啟動VM引數加上:-XX:MaxMetaspaceSize=1m,重新執行一下上面的程式,

Connected to the target VM, address: '127.0.0.1:56433', transport: 'socket'
java.lang.OutOfMemoryError: Metaspace
Dumping heap to java_pid9232.hprof ...
Heap dump file created [1604635 bytes in 0.024 secs]
FATAL ERROR in native method: processing of -javaagent failed
Exception in thread "main" Disconnected from the target VM, address: '127.0.0.1:56433', transport: 'socket'

Process finished with exit code 1

可以發現報錯資訊變成了java.lang.OutOfMemoryError: Metaspace,說明元空間不夠,我改成到大概4m左右才能滿足啟動條件。

虛擬機器棧和本地方法棧棧溢位

在Java虛擬機器規範中描述了兩種異常:

  • 如果執行緒請求的棧深度大於虛擬機器所允許的最大深度,將丟擲StackOverflowError異常
  • 如果虛擬機器在擴充套件棧無法申請到足夠的記憶體空間,則丟擲OutOfMemoryError異常

    StackOverflowError比較好測試,測試程式碼如下:

package com.example.demo.jvm;

/**
 * @Author: Wang Chong
 * @Date: 2019/9/22 19:09
 * @Version: V1.0
 */
public class StackOverflowTest {

    /**
     * 棧大小
     */
    private int stackLength = 1;

    /**
     * 遞迴壓棧
     */
    public void stackLeak() {
        stackLength++;
        stackLeak();
    }

    public static void main(String[] args) {
        StackOverflowTest stackOverflowTest = new StackOverflowTest();
        try {
            stackOverflowTest.stackLeak();
        } catch (Throwable e) {
            System.out.println("stack length is :" + stackOverflowTest.stackLength);
            throw e;
        }

    }

}

執行結果如下:

Exception in thread "main" stack length is :20739
java.lang.StackOverflowError
    at com.example.demo.jvm.StackOverflowTest.stackLeak(StackOverflowTest.java:20)
    at com.example.demo.jvm.StackOverflowTest.stackLeak(StackOverflowTest.java:20)

在VM引數-Xss引數未設定的情況下,該執行緒的記憶體支援的棧深度為20739,該測試結果與機器的記憶體大小有關,不過上面的第二點如何測試呢?正常來說如果是單執行緒,則難以測試記憶體洩露的情況,那麼多執行緒呢?我們看一下以下測試程式碼:

package com.example.demo.jvm;

/**
 * @Author: Wang Chong
 * @Date: 2019/9/22 19:09
 * @Version: V1.0
 */
public class StackOOMTest implements Runnable{

    /**
     * 棧大小
     */
    private int stackLength = 1;

    /**
     * 遞迴壓棧
     */
    public void stackLeak() {
        stackLength++;
        stackLeak();
    }

    public static void main(String[] args) {
       while (true){
           StackOOMTest stackOverflowTest = new StackOOMTest();
           new Thread(stackOverflowTest).start();
       }

    }

    @Override
    public void run() {
       stackLeak();
    }
}

如果系統不假死的情況下,會出現Exception in thread "main" java.lang.OutOfMemoryError:unable to create new native thread

執行時常量池溢位

  • 字元型常量池溢位,在JAVA8中也是堆溢位,測試程式碼如下:
package com.example.demo.jvm;

import java.util.ArrayList;
import java.util.List;

/**
 * @Author: Wang Chong
 * @Date: 2019/9/22 19:44
 * @Version: V1.0
 */
public class RuntimePoolOOMTest {

    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        int i = 0;
        while (true) {
            list.add(String.valueOf(i).intern());
        }
    }
}

結果如下:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at 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.example.demo.jvm.RuntimePoolOOMTest.main(RuntimePoolOOMTest.java:17)
Disconnected from the target VM, address: '127.0.0.1:50253', transport: 'socket'

證明字元常量池已經在Java8中是在堆中分配的。

方法區溢位

在Java7之前,方法區位於永久代(PermGen),永久代和堆相互隔離,永久代的大小在啟動JVM時可以設定一個固定值,不可變;Java8仍然保留方法區的概念,只不過實現方式不同。取消永久代,方法存放於元空間(Metaspace),元空間仍然與堆不相連,但與堆共享實體記憶體,邏輯上可認為在堆中
測試程式碼如下,為快速看出結果,請加入VM引數-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError -XX:MaxMetaspaceSize=10m:

package com.example.demo.jvm;

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;

/**
 * @Author: Wang Chong
 * @Date: 2019/9/22 19:56
 * @Version: V1.0
 */
public class MethodAreaOOMTest {
    public static void main(String[] args) {
        while (true) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(OOMObject.class);
            enhancer.setUseCache(false);
            enhancer.setCallback((MethodInterceptor) (o, method, objects, methodProxy) -> methodProxy.invokeSuper(o,
                    objects));
            enhancer.create();
        }
    }

    static class OOMObject {

    }
}

執行結果如下:

java.lang.OutOfMemoryError: Metaspace
Dumping heap to java_pid8816.hprof ...
Heap dump file created [6445908 bytes in 0.039 secs]
Exception in thread "main" org.springframework.cglib.core.CodeGenerationException: java.lang.reflect.InvocationTargetException-->null
    at org.springframework.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:345)
    at org.springframework.cglib.proxy.Enhancer.generate(Enhancer.java:492)
    at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData.get(AbstractClassGenerator.java:114)
    at org.springframework.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:291)
    at org.springframework.cglib.proxy.Enhancer.createHelper(Enhancer.java:480)
    at org.springframework.cglib.proxy.Enhancer.create(Enhancer.java:305)
    at com.example.demo.jvm.MethodAreaOOMTest.main(MethodAreaOOMTest.java:19)
Caused by: java.lang.reflect.InvocationTargetException
    at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.springframework.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:459)
    at org.springframework.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:336)
    ... 6 more
Caused by: java.lang.OutOfMemoryError: Metaspace
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
    ... 11 more

Process finished with exit code 1

元空間記憶體報錯,證明方法區的溢位與元空間相關。

總結如下:

  • 正常JVM調優都是針對堆記憶體和棧記憶體、元空間的引數做相應的改變
  • 元空間並不在虛擬機器中,而是使用本地記憶體。因此,預設情況下,元空間的大小僅受本地記憶體限制,但可以通過以下引數來指定元空間的大小:
  • -XX:MetaspaceSize,初始空間大小,達到該值就會觸發垃圾收集進行型別解除安裝,同時GC會對該值進行調整:如果釋放了大量的空間,就適當降低該值;如果釋放了很少的空間,那麼在不超過MaxMetaspaceSize時,適當提高該值。
  • -XX:MaxMetaspaceSize,最大空間,預設是沒有限制的。
  • 字串池常量池在每個VM中只有一份,存放的是字串常量的引用值,存放在堆中

有更多的文章,請關注檢視,更有面試寶典相送

相關推薦

Java8虛擬機器JVM記憶體溢位實戰

前言 相信很多JAVA中高階的同學在面試的時候會經常碰到一個面試題 你是如何在工作中對JVM調優和排查定位問題的? 事實上,如果使用者量不大的情況下,在你的程式碼還算正常的情況下,在工作中除非真正碰到與JVM相關的問題是少之又少,就算碰到了也是由公司的一些大牛去排查解決,那麼我們又如何積累這方面的經驗呢?下

JAVA虛擬機器JVM劃重點 第二章 Java記憶體區域與記憶體溢位異常 之 虛擬機器物件

本部落格參考《深入理解Java虛擬機器》(第二版)一書,提取重點知識,再加以個人的理解編寫而成。轉載請標明來源。 JAVA虛擬機器(JVM)劃重點 第二章 Java記憶體區域與記憶體溢位異常 之 虛擬機器物件 Java物件的建立 1、類載入過程

JAVA虛擬機器JVM劃重點 第二章 Java記憶體區域與記憶體溢位異常 之 執行時資料區域

本部落格參考《深入理解Java虛擬機器》(第二版)一書,提取重點知識,再加以個人的理解編寫而成。轉載請標明來源。 JVM劃重點 第二章 Java記憶體區域與記憶體溢位異常 之 執行時資料區域 概述 執行時資料區域 程式計數器 Java虛擬機

Java虛擬機器JVM執行時記憶體區域劃分詳解

Java虛擬機器(JVM)記憶體區域劃分詳解 最近一直沒有怎麼更新自己的部落格,主要是由於老哥公司最近的一個招標專案忙得焦頭爛額,心力憔悴(ಥ_ಥ),趁著專案的空檔期來重構一下以前的一篇關於jvm記憶體區域劃分的部落格,仔細閱讀了一下之前的部落格,大量的文字敘

Java虛擬機器JVM中的記憶體設定詳解

在一些規模稍大的應用中,Java虛擬機器(JVM)的記憶體設定尤為重要,想在專案中取得好的效率,GC(垃圾回收)的設定是第一步。 PermGen space:全稱是Permanent Generation space.就是說是永久儲存的區域,用於存放Class和Meta資

java虛擬機器JVM

1.jvm虛擬機器概述和基本概念  (虛擬機器分為系統虛擬機器-》(VirtualBox   VMware ==)  和 程式虛擬機器-》(JVM  DVM == )   )    1.1什麼是jvm      &n

面試中關於Java虛擬機器jvm的問題看這篇就夠了

最近看書的過程中整理了一些面試題,面試題以及答案都在我的文章中有所提到,希望你能在以問題為導向的過程中掌握虛擬機器的核心知識。面試畢竟是面試,核心知識我們還是要掌握的,加油。 下面是按jvm虛擬機器知識點分章節總結的一些jvm學習與面試相關的一些東西。一般作為Java程式設

什麼是java虛擬機器JVM

什麼是java虛擬機器(JVM)? Java虛擬機器是一個可以執行Java位元組碼的虛擬機器程序。Java原始檔被編譯成能被Java虛擬機器執行的位元組碼檔案。 為什麼Java可以實現所謂的“一次編寫,到處執行”,主要是因為虛擬機器的存在。Java虛擬機器負責Java程式設計語言

詳細介紹Java虛擬機器JVM

1. JVM生命週期 啟動。啟動一個Java程式時,一個JVM例項就產生了,任何一個擁有public static void main(String[] args)函式的class都可以作為JVM例項執行的起點。 執行。main()作為該程式初始執行緒的起點,任何其他執行

Java虛擬機器記憶體模型與執行緒

JAVA記憶體模型 Java的記憶體模型的主要目標是定義程式中各個變數的訪問規則,即在虛擬機器中將變數儲存到記憶體和從記憶體中取出變數這樣的底層細節。這裡說的變數包含了例項欄位,靜態欄位和構成資料物件的元素(共享的),而不包括區域性變數和方法引數,因為他們是執行

究竟什麼是Java虛擬機器JVM

我們都知道,在Windows上,軟體包字尾有exe,而蘋果的Mac OS X系統上沒有安裝exe。類似地,Mac OSX系統上的軟體安裝包是dmg字尾,不能安裝在Windows系統上。 為什麼不能安裝不同系統上的軟體,因為作業系統的底層實現是不同的。對於Windows系統,exe字尾的軟體程式碼

Java虛擬機器——記憶體區域理解

說明:本文內容主要參考了《深入理解Java虛擬機器》第2版。   一,概述 最近因為辭職了,玩了一段時間了,有時間去學習。加上之前買了一二本書,有不少卻沒有仔細去看,今天狀態還不錯,剛好看到JVM相關的內容,覺得還是在部落格裡好好總結一下。 本文主要還是對JVM的記憶體區域

JAVA虛擬機器JVM——類載入的時機之類的初始化

類的生命週期 從類被載入到虛擬機器記憶體中開始,到卸載出記憶體為止,它的整個生命週期包括如上的7個階段。其中,驗證、準備、解析這三個部分又被統稱為“連線(Linking)。類的載入過程必須按照這種順序按部就班的開始,而解析階段則不一定,它在某些情況

Java虛擬機器JVM引數配置說明

如果你要在J2EE環境中配置這些引數,那麼你需要在J2EE應用伺服器或者Servlet容器相關啟動引數設定處指定,其啟動檔案中來配置,Tomcat是在catalina.bat中配置,weblogic和websphere是在其他地方,具體我就說了,相信玩過的這些大型伺服器的人都知道,沒玩過的看看這篇文章,玩玩

JAVA虛擬機器JVM——類載入的過程載入、驗證、準備、解析、初始化

載入 “載入”是”類載入”過程的一個階段。在載入階段,虛擬機器需要完成以下3件事情: 1.通過一個類的全限定名來獲取定義此類的二進位制位元組流。 2.將這個位元組流所代表的靜態儲存結構轉化為方法區的執行時資料結構。 3.在記憶體中生成一個代表這個類的java

Java教程02—Java虛擬機器JVM、和JDK,JRE之間的區別

JVM主要功能 Java是一種高階程式語言。 用高階語言編寫的程式不能直接在任何機器上執行。 首先,需要將其翻譯成特定的機器語言,javac編譯器就專門來幹這個事兒的,它把Java程式(含有的.java原始碼檔案)轉換成機器程式碼(稱為位元組碼或.c

JAVA虛擬機器JVM——虛擬機器位元組碼執行引擎

方法呼叫 方法呼叫並不等同於方法執行,方法呼叫階段唯一的任務就是確定呼叫哪一個方法,暫時還不涉及方法內部的具體執行過程。Class檔案的編譯過程中不包含傳統編譯中的連線步驟,一切方法呼叫在C

JAVA虛擬機器記憶體管理

一.記憶體分配。java虛擬機器在執行的時候,將會包含以下執行時資料區域: 1.執行緒私有的部分: 程式計數器:可以看成是當前執行緒所執行位元組碼的行號指示器。位元組碼直譯器就是通過改變這個計數器的值來確定下一條位元組碼指令。 虛擬機器棧:虛擬機器棧為虛擬機器使用的

學習虛擬機器jvm必須知道的東西

我一共寫了兩篇部落格,這一篇是為了讓大家頭腦清楚,哪些重要,如果誰有不懂得https://blog.csdn.net/weixin_40078053/article/details/80546846這是我的另一篇部落格,對虛擬機器有詳細介紹,當然啦,滿滿全是乾貨,總結,否則看

初識Java虛擬機器11記憶體模型

    Java記憶體模型的主要目標是定義程式中各個變數的訪問規則,即在虛擬機器中將變數儲存到記憶體和從記憶體中取出變數這樣的實現細節。它包括了例項欄位、靜態欄位和構成陣列物件的元素,但不包括區域性變