《深入理解Java虚拟机:JVM高级特性与最佳实践》第2章 Java内存区域与内存溢出异常

本文详细介绍了Java虚拟机的内存区域,包括程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区、运行时常量池和直接内存。通过实例分析了对象创建的过程及内存布局,并探讨了不同类型的内存溢出异常,如Java堆溢出、虚拟机栈和本地方法栈溢出、方法区和运行时常量池溢出以及直接内存溢出。同时,提供了相应的代码示例和异常处理,帮助读者理解和诊断这些问题。
摘要由CSDN通过智能技术生成

前言

在这里插入图片描述

2.1 概述

在这里插入图片描述

2.2 运行时数据区域

在这里插入图片描述

图片一

2.2.1 程序计数器:

在这里插入图片描述


  • 程序计数器是线程私有的,主要目的是存放字节码执行行号,为线程完成上下文切换后可以回到正确的位置。

2.2.2 Java虚拟机栈

在这里插入图片描述
通俗理解

线程的内部模型,在执行Java方法的时候创建栈帧存储方法的相关信息。

2.2.3 本地方法栈(Native Method Stacks)

在这里插入图片描述
通俗理解

线程的内部模型,在执行本地方法的时候创建栈帧存储方法的相关信息。

2.2.4 Java堆(Heap)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

-Xms20m
-Xmx20m

通俗理解

堆存储实例对象,所有线程共享空间。

2.2.5 方法区(Method Area)

在这里插入图片描述
在这里插入图片描述
通俗理解

方法区存放 静态变量、常量、类型信息等内容。 java8 开始存放在元空间里面。基本不会进行垃圾回收。

2.2.6 运行时常量池(Runtime Constant Pool)

在这里插入图片描述

方法区的一部分,

2.2.7 直接内存(Direct Memory)

在这里插入图片描述
java操作Unsafe类来直接分配堆外内存。

在这里插入图片描述


堆外内存主要是由 NIO 来开辟。

2.3 HotSpot虚拟机对象探秘

在这里插入图片描述

2.3.1 对象的创建

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
通俗理解

当遇到new关键字,首先类的加载过程,然后是内存空间分配,这里涉及到两种方法,指针碰撞和空闲列表,
而采取哪种方法跟是否规整有关系,是否规整又跟垃圾收集相关知识有关,内存空间的分配涉及到线程安全,可以采用 CAS失败重试或本地线程分配缓冲,内存分配完毕后进行初始化,然后进行对象头的信息设置,执行init方法,这样对象就创建完毕了。

2.3.2 对象的内存布局

在这里插入图片描述
在这里插入图片描述
路径:openjdk\hotspot\src\share\vm\oops\markOop.cpp

在这里插入图片描述

在这里插入图片描述


对象头 Header
实例数据 instance Data
对齐填充 padding ,对象必须是8字节的整数倍,如果没有对齐则需要填充。
oops: ordinary object pointers 普通对象指针

2.3.3 对象的访问定位

在这里插入图片描述
在这里插入图片描述

2.4 实战:OutOfMemoryError异常

在这里插入图片描述

2.4.1 Java堆溢出

在这里插入图片描述

-Xms20m
-Xmx20m
-XX:+HeapDumpOnOutOfMemoryError
-------------------------
public class C2D1 {

    public static void main(String[] args) {
        List<C2D1> list = new ArrayList<>();
        while (true) {
            list.add(new C2D1());
        }
    }
}
-------
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid2196.hprof ...
Heap dump file created [28427774 bytes in 0.076 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:265)
	at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:239)
	at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:231)
	at java.util.ArrayList.add(ArrayList.java:462)
	at com.example.common.jvm.c2.C2D1.main(C2D1.java:18)

转储文件 java_pid2196.hprof 如图所示

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
通俗理解

内存泄漏是出现了非必须存活的对象,而内存溢出是所有必须存活的对象占用过多的内存,应该检查堆的相关参数,尽量减少运行期的内存消耗。

2.4.2 虚拟机栈和本地方法栈溢出

在这里插入图片描述

-Xss128k
-------
public class C2D2 {
    private int stackLength = 1;

    public void stackLeak() {
        stackLength++;
        stackLeak();
    }

    public static void main(String[] args) throws Throwable {
        C2D2 oom = new C2D2();
        try {
            oom.stackLeak();
        } catch (Throwable e) {
            System.out.println("stack length:" + oom.stackLength);
            throw e;
        }
    }
}
--------
stack length:989
Exception in thread "main" java.lang.StackOverflowError
	at com.example.common.jvm.c2.C2D2.stackLeak(C2D2.java:12)
	at com.example.common.jvm.c2.C2D2.stackLeak(C2D2.java:13)
	at com.example.common.jvm.c2.C2D2.stackLeak(C2D2.java:13)
	at com.example.common.jvm.c2.C2D2.stackLeak(C2D2.java:13)
	at com.example.common.jvm.c2.C2D2.stackLeak(C2D2.java:13)
	at com.example.common.jvm.c2.C2D2.stackLeak(C2D2.java:13)

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

public class C2D3 {
    private static int stackLength = 0;

    public static void test() {
        long unused1, unused2, unused3, unused4, unused5, unused6, unused7, unused8, unused9, unused10, unused11, unused12, unused13, unused14, unused15, unused16, unused17, unused18, unused19, unused20, unused21, unused22, unused23, unused24, unused25, unused26, unused27, unused28, unused29, unused30, unused31, unused32, unused33, unused34, unused35, unused36, unused37, unused38, unused39, unused40, unused41, unused42, unused43, unused44, unused45, unused46, unused47, unused48, unused49, unused50, unused51, unused52, unused53, unused54, unused55, unused56, unused57, unused58, unused59, unused60, unused61, unused62, unused63, unused64, unused65, unused66, unused67, unused68, unused69, unused70, unused71, unused72, unused73, unused74, unused75, unused76, unused77, unused78, unused79, unused80, unused81, unused82, unused83, unused84, unused85, unused86, unused87, unused88, unused89, unused90, unused91, unused92, unused93, unused94, unused95, unused96, unused97, unused98, unused99, unused100;
        stackLength++;
        test();
        unused1 = unused2 = unused3 = unused4 = unused5 = unused6 = unused7 = unused8 = unused9 = unused10 = unused11 = unused12 = unused13 = unused14 = unused15 = unused16 = unused17 = unused18 = unused19 = unused20 = unused21 = unused22 = unused23 = unused24 = unused25 =
                unused26 = unused27 = unused28 = unused29 = unused30 = unused31 = unused32 = unused33 = unused34 = unused35 = unused36 = unused37 = unused38 = unused39 = unused40 = unused41 = unused42 = unused43 = unused44 = unused45 = unused46 = unused47 = unused48 = unused49 = unused50 = unused51 = unused52 = unused53 = unused54 = unused55 = unused56 = unused57 = unused58 = unused59 = unused60 = unused61 = unused62 = unused63 = unused64 = unused65 = unused66 = unused67 = unused68 = unused69 = unused70 = unused71 = unused72 = unused73 = unused74 = unused75 = unused76 = unused77 = unused78 = unused79 = unused80 = unused81 = unused82 = unused83 = unused84 = unused85 = unused86 = unused87 = unused88 = unused89 = unused90 = unused91 = unused92 = unused93 = unused94 = unused95 = unused96 = unused97 = unused98 = unused99 = unused100 = 0;
    }

    public static void main(String[] args) {
        try {
            test();
        } catch (Error e) {
            System.out.println("stack length:" + stackLength);
            throw e;
        }
    }
}

------
stack length:7672
Exception in thread "main" java.lang.StackOverflowError
	at com.example.common.jvm.c2.C2D3.test(C2D3.java:13)
	at com.example.common.jvm.c2.C2D3.test(C2D3.java:13)
	at com.example.common.jvm.c2.C2D3.test(C2D3.java:13)

在这里插入图片描述
在这里插入图片描述

-Xss2M
--------
public class C2D4 {  

    private void dontStop() {
        while (true) {
        }
    }

    public void stackLeakByThread() {
        while (true) {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    dontStop();
                }
            });
            thread.start();
        }
    }

    public static void main(String[] args) throws Throwable {
        C2D4 c2D4 = new C2D4();
        c2D4.stackLeakByThread();
    }
}

在这里插入图片描述
在这里插入图片描述

2.4.3 方法区和运行时常量池溢出

在这里插入图片描述
jdk6 通过限制永久代大小来限制常量池的容量。

-XX:PermSize=6M
-XX:MaxPermSize=6M

在这里插入图片描述
在这里插入图片描述

jdk8版本

-Xmx5m
-------
public class C2D5 {
    public static void main(String[] args) {
        // 使用Set保持着常量池引用,避免Full GC回收常量池行为
        Set<String> set = new HashSet<>();
        short i = 0;
        while (true) {
            set.add(String.valueOf(i++).intern());
        }
    }
}
--------
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
	at java.util.HashMap.newNode(HashMap.java:1750)
	at java.util.HashMap.putVal(HashMap.java:631)
	at java.util.HashMap.put(HashMap.java:612)
	at java.util.HashSet.add(HashSet.java:220)
	at com.example.common.jvm.c2.C2D5.main(C2D5.java:18)

在这里插入图片描述

public class C2D6 {
    public static void main(String[] args) {
        String str1 = new StringBuilder("计算机").append("软件").toString();
        System.out.println(str1.intern() == str1);

        String str2 = new StringBuilder("ja").append("va").toString();
        System.out.println(str2.intern() == str2);
    }
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在JDK 8中的运行结果

public class C2D7 {

    public static void main(String[] args) {
        while (true) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(OOMObject.class);
            enhancer.setUseCache(false);
            enhancer.setCallback((MethodInterceptor) (obj, method, args1, proxy) -> proxy.invokeSuper(obj, args1));
            enhancer.create();
        }
    }

    static class OOMObject {
    }
}

在这里插入图片描述
在这里插入图片描述

查询元空间相关VM选项
java -XX:+PrintFlagsFinal -version | findstr Metaspace

结果

在这里插入图片描述

2.4.4 本机直接内存溢出

在这里插入图片描述

-Xmx20M
-XX:MaxDirectMemorySize=10M
-----
public class C2D8 {

    private static final int _1MB = 1024 * 1024;

    public static void main(String[] args) throws Exception {
        Field unsafeField = Unsafe.class.getDeclaredFields()[0];
        unsafeField.setAccessible(true);
        Unsafe unsafe = (Unsafe) unsafeField.get(null);
        while (true) {
            unsafe.allocateMemory(_1MB);
        }
    }
}
------
Exception in thread "main" java.lang.OutOfMemoryError
	at sun.misc.Unsafe.allocateMemory(Native Method)
	at com.example.common.jvm.c2.C2D8.main(C2D8.java:22)

在这里插入图片描述

查询直接内存VM参数

java -XX:+PrintFlagsFinal -version | findstr Direct
MaxDirectMemorySize

在这里插入图片描述

本章小结

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值