Java虚拟机 内存区域(JVM运行时数据区)理论+实战

本文会对JVM运行时数据区(java内存区域)进行详细的介绍,并对介绍中提到的OOM异常进行实战演示。

运行编译器: Intellij IDEA
虚拟机类型: HotSpot
操作系统: win11
JDK: 1.8

理论介绍

JVM运行时数据区

1. 程序计数器

  • 作用: 是当前线程所执行的字节码的行号指示器,就是标志着当前线程执行到哪了。
  • 是否线程私有: 是。
  • 是否会发生OOM: 不会,因为其需要的空间很小,是JVM运行时数据区中唯一一块JVM规范中没有规定OOM情况的区域。

2. Java 虚拟机栈

  • 作用: 描述了java方法执行的内存模型,每执行一个java方法都会在java虚拟机栈创建一个栈帧,其中存有局部变量表、操作数栈、动态链接、方法出口等信息。随着方法的执行对应的进行出栈入栈操作。
  • 是否线程私有: 是。
  • 是否会发生OOM: 是。

3. 本地方法栈

  • 作用: 与java虚拟机栈及其类似,过执行的并非java字节码,而是其他语言。
  • 是否线程私有: 是。
  • 是否会发生OOM: 是。

4. Java 堆

  • 作用: java对象进行分配的地方,GC垃圾回收算法主要工作的地方。
  • 是否线程私有: 否。
  • 是否会发生OOM: 是。

5. 方法区(元数据区MetaSpace)

  • 作用: 存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。也会有GC的工作,主要进行针对常量池的回收和对类型的卸载。注意,JDK1.8之后,用MetaSpace取代了方法区的概念,它不在属于JVM的运行时数据区,受限于物理内存。
  • 是否线程私有:
  • 是否会发生OOM: 是(受限于物理内存)

6. 字符串常量池

  • 作用: 它在JDK1.8之前是方法区的一部分,最大的特征就是程序运行期间,可以将新的常量放入其中。例如,String类的intern()方法。但JDK1.8之后就存储在JAVA堆上。
  • 是否线程私有:
  • 是否会发生OOM: 是(受JAVA堆的大小限制)

7. 直接内存

  • 作用: 不属于JVM 运行时数据区域的一部分,java程序会频繁进行访问。
  • 是否线程私有:
  • 是否会发生OOM: 是(受限于本机内存)

实战演示

1. Java 堆 溢出

JVM参数设置(注意参数之间有空格):
-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
设置位置:

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

测试代码:
import java.util.ArrayList;
import java.util.List;

public class HeapOOM {
    // 建立静态内部类
    static class OOMObject{}
    public static void main(String[]args) {
        List<OOMObject> list=new ArrayList<OOMObject>();
        // 不断的创建对象保存
        while(true){
            list.add(new OOMObject());
        }
    }
}
结果展示

在这里插入图片描述

结果分析

我们在JVM参数设置上指定了-XX:+HeapDumpOnOutOfMemoryError 参数,此参数的作用在于JVM会储存生成发生异常时的java内存堆的快照。我们可以看到结果中打印了输出文件的log。
在这里插入图片描述
并且在我们的项目目录下也发现了这个文件:
在这里插入图片描述
之所以要保存下内存堆的快照是因为在工程中,我们的OOM异常并不会像今天我们写的这样明显,我们需要根据快照发现内存泄漏的位置或是OOM的具体原因。

2. 虚拟机栈和本地方法栈溢出

JVM参数设置(注意参数之间有空格):
-Xss128k
设置位置:同上
测试代码:
public class JavaVMStackSOF{
    private int stackLength=1;
    public void stackLeak(){
        stackLength++;
        stackLeak();
    }
    public static void main(String[]args) throws Throwable{
        JavaVMStackSOF oom=new JavaVMStackSOF();
        try{
            oom.stackLeak();
        } catch(Throwable e) {
            System.out.println("stack length:"+oom.stackLength);
            throw e;
        }
    }
}
结果展示:

在这里插入图片描述

结果分析:

我们发现程序出现的异常并非是OOM,而是StackOverflowError异常。StackOverflowError代表的是我们栈的调用过深,栈的深度和所占内存也是正相关的。但是在单线程情况下,不管是栈过深还是JVM栈空间不够,都会报StackOverflowError异常。

而如果真的想测试出JAVA虚拟机栈的OOM,就要用多线程的方式进行,随着不断的创建线程内存就会不断被切分出去,为每个线程的运行的虚拟机栈分配空间,最终随着每个线程的无休止递归调用,就会发生JVM栈的OOM异常。测试代码如下(谨慎运行可能导致系统假死):

public class JavaVMStackOOM{
    private void dontStop(){
        while(true){}
    }
    public void stackLeakByThread(){
        while(true){
            Thread thread=new Thread(() ->{dontStop();});
            thread.start();
        }
    }
    public static void main(String[]args)throws Throwable{
        JavaVMStackOOM oom=new JavaVMStackOOM();
        oom.stackLeakByThread();
    }
}

3. 字符串常量池溢出

JVM参数设置(注意参数之间有空格):
-Xms5m -Xmx5m
设置位置:同上
测试代码:
import java.util.ArrayList;
import java.util.List;

public class RuntimeConstantPoolOOM{
    public static void main(String[]args) {
        //使用List保持着常量池引用,避免Full GC回收常量池行为
        List<String> list=new ArrayList<String>();
        int i=0;
        while(true) {
            System.out.println(i);
            list.add(String.valueOf(i++).intern());
        }
    }
}
结果展示:

在这里插入图片描述

结果分析:

上面我们讲了,字符串常量池存储在堆中,因此我们这里报了一个堆的OOM异常。

4. 本机直接内存溢出

JVM参数设置(注意参数之间有空格):
-Xmx20M -XX:MaxDirectMemorySize=10M
设置位置:同上
测试代码:
import sun.misc.Unsafe;

import java.lang.reflect.Field;

public class DirectMemoryOOM {
    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);
        }
    }
}
结果展示:

在这里插入图片描述

结果分析:

我们用Unsafe类进行了内存的申请分配。

总结:

我们已经和这些异常混了个脸熟,我们当然希望在工作中快速的分析出导致这些异常发生的根源。希望随着文章的更新和大家一起达到这个目的,下一章讲讲JVM为了不出现这些问题都做了哪些努力。大家加油啊!

参考

《深入理解Java虚拟机》,并针对JDK1.8之后版本做出了改进。

预告

接下来讲JVM的垃圾回收机制哦~
Java虚拟机(JVM)的垃圾收集机制(GC)详解(上)
Java虚拟机(JVM)垃圾回收机制(GC)详解(下)——垃圾收集器

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

从前慢慢慢死了

打钱!一分也行啊!!!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值