Java8虛擬機(jī)內(nèi)存溢出的示例分析

這篇文章給大家分享的是有關(guān)Java8虛擬機(jī)內(nèi)存溢出的示例分析的內(nèi)容。小編覺得挺實(shí)用的,因此分享給大家做個(gè)參考,一起跟隨小編過(guò)來(lái)看看吧。

創(chuàng)新互聯(lián)建站-專業(yè)網(wǎng)站定制、快速模板網(wǎng)站建設(shè)、高性價(jià)比吉陽(yáng)網(wǎng)站開發(fā)、企業(yè)建站全套包干低至880元,成熟完善的模板庫(kù),直接使用。一站式吉陽(yáng)網(wǎng)站制作公司更省心,省錢,快速模板網(wǎng)站建設(shè)找我們,業(yè)務(wù)覆蓋吉陽(yáng)地區(qū)。費(fèi)用合理售后完善,十余年實(shí)體公司更值得信賴。

Java8虛擬機(jī)(JVM)內(nèi)存溢出實(shí)戰(zhàn)

前言

相信很多JAVA中高級(jí)的同學(xué)在面試的時(shí)候會(huì)經(jīng)常碰到一個(gè)面試題
你是如何在工作中對(duì)JVM調(diào)優(yōu)和排查定位問(wèn)題的?

事實(shí)上,如果用戶量不大的情況下,在你的代碼還算正常的情況下,在工作中除非真正碰到與JVM相關(guān)的問(wèn)題是少之又少,就算碰到了也是由公司的一些大牛去排查解決,那么我們又如何積累這方面的經(jīng)驗(yàn)?zāi)??下面由沖鍋帶大家一起來(lái)實(shí)踐JVM的調(diào)優(yōu)吧

注意我們平常所說(shuō)的JVM調(diào)優(yōu)一般指Java堆,Java虛擬機(jī)棧參數(shù)調(diào)優(yōu)

Java堆溢出

先來(lái)一段代碼示例,注意筆者用的是IDEA工具,需要配置一下VM options 為-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError,如果不清楚的百度一下如何配置idea的JVM運(yùn)行參數(shù)

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());
        }
    }
}

運(yùn)行結(jié)果如下:

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'

可以看到控制臺(tái)出現(xiàn)java.lang.OutOfMemoryError: Java heap space的錯(cuò)誤,這是為什么呢,首先先解釋一下上面的運(yùn)行參數(shù)

-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError


  • -Xms20m:設(shè)置JVM最小內(nèi)存為20m。此值可以設(shè)置與-Xmx相同,以避免每次垃圾回收完成后JVM重新分配內(nèi)存


  • -Xmx20m:設(shè)置JVM最大可用內(nèi)存20M


  • -XX:+HeapDumpOnOutOfMemoryError 表示當(dāng)JVM發(fā)生OOM時(shí),自動(dòng)生成DUMP文件


下面我們分析一下出錯(cuò)的原因,用JProfiler分析一下,打開剛才生成的名為java_pid9352.hprof的dump文件??梢钥吹礁鶕?jù)(InstanceXcount和Size)基本可以確定哪個(gè)類的對(duì)象出現(xiàn)問(wèn)題,在上面示例中,可以是ChongGuo這個(gè)實(shí)例生在數(shù)量的大小已經(jīng)超過(guò)12M,但沒(méi)有超過(guò)20M,那么新問(wèn)題又來(lái)了?沒(méi)到20M為啥會(huì)報(bào)堆內(nèi)存溢出呢?

Java8虛擬機(jī)內(nèi)存溢出的示例分析

答案就是JDK8中堆內(nèi)存中還包括Metaspace,即元內(nèi)存空間,在元空間出現(xiàn)前JDK1.7之前在JDK7以及其前期的JDK版本號(hào)中。堆內(nèi)存通常被分為三塊區(qū)域Nursery內(nèi)存(young generation)、長(zhǎng)時(shí)內(nèi)存(old generation)、永久內(nèi)存(Permanent Generation for VM Matedata),如下圖

Java8虛擬機(jī)內(nèi)存溢出的示例分析

當(dāng)中最上一層是年輕代,一個(gè)對(duì)象被創(chuàng)建以后首先被放到年輕代中的Eden內(nèi)存中,假設(shè)存活期超兩個(gè)Survivor之后就會(huì)被轉(zhuǎn)移到長(zhǎng)時(shí)內(nèi)存中(Old Generation)中永久內(nèi)存中存放著對(duì)象的方法、變量等元數(shù)據(jù)信息。通過(guò)假設(shè)永久內(nèi)存不夠。我們就會(huì)得到例如以下錯(cuò)誤:java.lang.OutOfMemoryError: PermGen
而在JDK8中情況發(fā)生了明顯的變化,就是普通情況下你都不會(huì)得到這個(gè)錯(cuò)誤,原因
在于JDK8中把存放元數(shù)據(jù)中的永久內(nèi)存從堆內(nèi)存中移到了本地內(nèi)存(native memory)
中,JDK8中JVM堆內(nèi)存結(jié)構(gòu)就變成了例如以下:

Java8虛擬機(jī)內(nèi)存溢出的示例分析

如果我啟動(dòng)VM參數(shù)加上:-XX:MaxMetaspaceSize=1m,重新運(yùn)行一下上面的程序,

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

可以發(fā)現(xiàn)報(bào)錯(cuò)信息變成了java.lang.OutOfMemoryError: Metaspace,說(shuō)明元空間不夠,我改成到大概4m左右才能滿足啟動(dòng)條件。

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

在Java虛擬機(jī)規(guī)范中描述了兩種異常:

  • 如果線程請(qǐng)求的棧深度大于虛擬機(jī)所允許的最大深度,將拋出StackOverflowError異常

  • 如果虛擬機(jī)在擴(kuò)展棧無(wú)法申請(qǐng)到足夠的內(nèi)存空間,則拋出OutOfMemoryError異常

    StackOverflowError比較好測(cè)試,測(cè)試代碼如下:

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;
        }

    }

}

運(yùn)行結(jié)果如下:

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參數(shù)-Xss參數(shù)未設(shè)置的情況下,該線程的內(nèi)存支持的棧深度為20739,該測(cè)試結(jié)果與機(jī)器的內(nèi)存大小有關(guān),不過(guò)上面的第二點(diǎn)如何測(cè)試呢?正常來(lái)說(shuō)如果是單線程,則難以測(cè)試內(nèi)存泄露的情況,那么多線程呢?我們看一下以下測(cè)試代碼:

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();
    }
}

如果系統(tǒng)不假死的情況下,會(huì)出現(xiàn)Exception in thread "main" java.lang.OutOfMemoryError:unable to create new native thread

運(yùn)行時(shí)常量池溢出
  • 字符型常量池溢出,在JAVA8中也是堆溢出,測(cè)試代碼如下:

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());
        }
    }
}

結(jié)果如下:

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'

證明字符常量池已經(jīng)在Java8中是在堆中分配的。

方法區(qū)溢出

在Java7之前,方法區(qū)位于永久代(PermGen),永久代和堆相互隔離,永久代的大小在啟動(dòng)JVM時(shí)可以設(shè)置一個(gè)固定值,不可變;Java8仍然保留方法區(qū)的概念,只不過(guò)實(shí)現(xiàn)方式不同。取消永久代,方法存放于元空間(Metaspace),元空間仍然與堆不相連,但與堆共享物理內(nèi)存,邏輯上可認(rèn)為在堆中
測(cè)試代碼如下,為快速看出結(jié)果,請(qǐng)加入VM參數(shù)-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 {

    }
}

運(yùn)行結(jié)果如下:

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

元空間內(nèi)存報(bào)錯(cuò),證明方法區(qū)的溢出與元空間相關(guān)。

總結(jié)如下:

  • 正常JVM調(diào)優(yōu)都是針對(duì)堆內(nèi)存和棧內(nèi)存、元空間的參數(shù)做相應(yīng)的改變

  • 元空間并不在虛擬機(jī)中,而是使用本地內(nèi)存。因此,默認(rèn)情況下,元空間的大小僅受本地內(nèi)存限制,但可以通過(guò)以下參數(shù)來(lái)指定元空間的大?。?/p>

  • -XX:MetaspaceSize,初始空間大小,達(dá)到該值就會(huì)觸發(fā)垃圾收集進(jìn)行類型卸載,同時(shí)GC會(huì)對(duì)該值進(jìn)行調(diào)整:如果釋放了大量的空間,就適當(dāng)降低該值;如果釋放了很少的空間,那么在不超過(guò)MaxMetaspaceSize時(shí),適當(dāng)提高該值。

  • -XX:MaxMetaspaceSize,最大空間,默認(rèn)是沒(méi)有限制的。

  • 字符串池常量池在每個(gè)VM中只有一份,存放的是字符串常量的引用值,存放在堆中

感謝各位的閱讀!關(guān)于“Java8虛擬機(jī)內(nèi)存溢出的示例分析”這篇文章就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,讓大家可以學(xué)到更多知識(shí),如果覺得文章不錯(cuò),可以把它分享出去讓更多的人看到吧!

標(biāo)題名稱:Java8虛擬機(jī)內(nèi)存溢出的示例分析
標(biāo)題來(lái)源:http://bm7419.com/article38/pcospp.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供靜態(tài)網(wǎng)站外貿(mào)網(wǎng)站建設(shè)、網(wǎng)站建設(shè)軟件開發(fā)、品牌網(wǎng)站制作服務(wù)器托管

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

成都做網(wǎng)站