• 对象实例化之后一定会存放在堆内存中?


    目录

    前言

    实验得出的结论

    逃逸分析的概念

    逃逸的情况下对象实例化之后存放

    测试不逃逸的情况下对象实例化的存放(占用内存大)

    测试不逃逸的情况下对象实例化的存放(占用内存小)

    测试对象从栈内存转移到堆内存


    前言

    最近在学JVM,学到内存结构的时候产生一个疑问。

    疑问主要来自这样一句话:"所有的对象实例以及数组都应当在堆上分配——《Java虚拟机规范》"

    乍一看好像没什么,仔细一想,前面说的栈内存是线程私有,也就是说在方法体里面,局部变量是线程私有的也就是存放于栈内存里面。但是如果在方法体里面创建对象,让对象实例化呢?

    于是动手做起了小实验

    期间用到了几个命令:

    1. jps -->查看运行中的线程id
    2. jmap -heap 线程id -->查看堆内存情况

    实验得出的结论

    1.实例化后存在堆中的情况

    • 作为共享变量,需要被其它线程使用,所以是在堆内存之中。

    • 作为局部变量但是有发生逃逸的情况(return 出去了或者是作为参数传进来)

    • 作为局部变量但是占用内存空间较大(超出栈内存)

    2.实例化对象后存在栈中的情况

    • 作为局部变量并且不发生逃逸现象,并且占用内存空间不能超出栈内存限制。

    所以说,对象实例化之后不一定会存放于堆内存之中,也可能被优化存放于栈内存之中。

    逃逸分析的概念

    先以官方的形式来说下什么是逃逸分析。逃逸分析就是:一种确定指针动态范围的静态分析,它可以分析在程序的哪些地方可以访问到指针。

    在JVM的即时编译语境下,逃逸分析将判断新建的对象是否逃逸。即时编译判断对象是否逃逸的依据:一种是对象是否被存入堆中(静态字段或者堆中对象的实例字段),另一种就是对象是否被传入未知代码。

    逃逸的情况下对象实例化之后存放

    经过测试,发现如果后续没有其它线程参与使用共享变量的情况下,JVM总是不会对堆内存有想法。

    测试了对象实例化之后return逃逸出当前方法,如果没有其它线程使用的话,堆内存仍然没有什么变化。

    只有当对象实例化之后,被其它线程使用的情况下它才会跑到堆内存里面去。

    1. // TODO: 2022/8/4 测试逃逸的情况下对象实例化的存放(return list)
    2. public static List testHeap3() throws InterruptedException {
    3.    System.out.println("new了对象之前...");//jps 命令查看线程id
    4.    Thread.sleep(20000);
    5.    Listlist=new ArrayList<>(10);
    6.    String a="hello ";
    7.    for (int i = 0; i < 10; i++) {
    8.        list.add(a);
    9.        a=a + a;
    10.   }
    11.    System.out.println("准备return了...");//jmap -heap id
    12.    Thread.sleep(20000);
    13.    return list;
    14. }

    main方法:

    1. private static Listlist;
    2. public static void main(String[] args) throws InterruptedException {
    3.    list=testHeap3();
    4.    new Thread(()->{
    5.        try {
    6.            System.out.println("准备使用共享变量..");
    7.            Thread.sleep(10000);
    8.            for (String s : list) {
    9.                System.out.println(s);
    10.           }
    11.       } catch (InterruptedException e) {
    12.            e.printStackTrace();
    13.       }
    14.   }).start();
    15. }

    1.没有其它线程参与之前

    2.其它线程参与之后

     

     

    测试不逃逸的情况下对象实例化的存放(占用内存大)

    使用比较大的空间测试

    结论: 不逃逸的情况下,虽然是线程私有,但是由于实例化对象的时候发现栈内存不足以存放该对象,所以还是分配到堆中创建,因为10MB早已超出栈内存

    1. public static void testHeap1() throws InterruptedException {
    2.    System.out.println("new了对象之前...");//jps 命令查看线程id
    3.    Thread.sleep(20000);
    4.    byte[] bys=new byte[1024 * 1024 *10];//10MB 堆中创建
    5.    System.out.println("new了对象之后...");//jmap -heap id
    6.    Thread.sleep(20000);
    7. }

    1.

     

    2.

     

    测试不逃逸的情况下对象实例化的存放(占用内存小)

    如果new的对象占用空间比较小呢?-->会被JIT即时编译器优化在栈中运行

    1. public static void testHeap1() throws InterruptedException {
    2.    System.out.println("new了对象之前...");//jps 命令查看线程id
    3.    Thread.sleep(20000);
    4.    byte[] bys=new byte[256]; //256b -->优化到栈中创建
    5.    System.out.println("new了对象之后...");//jmap -heap id
    6.    Thread.sleep(20000);
    7. }

    1.

     

    2.

     

    测试对象从栈内存转移到堆内存

    如果一开始是在栈里面,有没有可能后期转移到堆中呢?

    猜想: 如果占用内存变大,是会转移过去的

    测试代码:

    1. // TODO: 2022/8/4 测试堆内存溢出的现象
    2. public static void testHeap2() throws InterruptedException {
    3.    System.out.println("new了对象之前...");//jps 命令查看线程id
    4.    Thread.sleep(20000);
    5.    Listlist=new ArrayList<>();
    6.    System.out.println("new了对象之后...");//jmap -heap id
    7.    Thread.sleep(20000);
    8.    String a="hello ";
    9.    int i=0;
    10.    try {
    11.        while (true){
    12.            list.add(a);
    13.            a=a + a;
    14.            i++;
    15.            if (i==20){
    16.                System.out.println("达到20次了...");//todo 此时再查发现 以及从栈内存转移到堆内存中了
    17.                Thread.sleep(20000);
    18.           }
    19.       }
    20.   }catch (OutOfMemoryError e){
    21.        e.printStackTrace();
    22.        System.out.println(i);
    23.   }
    24. }

    1.一开始对象实例化之前,栈内存占用情况

     

    2.对象实例化之后,堆内存占用情况

     

    可以看到堆内存占用情况没有改变,说明此时实例化是在栈内存中。

    3.当循环了20次之后,占用空间已经不小了,此时已经被转移到堆内存

     

    4.由于是死循环,最后抛出异常是堆内存溢出

     

  • 相关阅读:
    K8S中的ingress
    鲁棒局部均值分解 (RLMD)附Matlab代码
    矩阵类运算(运算符重载)
    数字示波器verilog设计实现
    stata的异方差检验
    C语言编程陷阱(五)
    Advantage Actor-Critic优势演员-评论员(A2C)
    git基本使用
    【C++ Primer Plus】第12章 类和动态内存分配
    c++builder6.0 数据库查询函数select * into 功能的实现
  • 原文地址:https://blog.csdn.net/Onthr/article/details/126162799