目录
最近在学JVM,学到内存结构的时候产生一个疑问。
疑问主要来自这样一句话:"所有的对象实例以及数组都应当在堆上分配——《Java虚拟机规范》"
乍一看好像没什么,仔细一想,前面说的栈内存是线程私有,也就是说在方法体里面,局部变量是线程私有的也就是存放于栈内存里面。但是如果在方法体里面创建对象,让对象实例化呢?
于是动手做起了小实验
期间用到了几个命令:
- jps -->查看运行中的线程id
- jmap -heap 线程id -->查看堆内存情况
1.实例化后存在堆中的情况
作为共享变量,需要被其它线程使用,所以是在堆内存之中。
作为局部变量但是有发生逃逸的情况(return 出去了或者是作为参数传进来)
作为局部变量但是占用内存空间较大(超出栈内存)
2.实例化对象后存在栈中的情况
作为局部变量并且不发生逃逸现象,并且占用内存空间不能超出栈内存限制。
所以说,对象实例化之后不一定会存放于堆内存之中,也可能被优化存放于栈内存之中。
先以官方的形式来说下什么是逃逸分析。逃逸分析就是:一种确定指针动态范围的静态分析,它可以分析在程序的哪些地方可以访问到指针。
在JVM的即时编译语境下,逃逸分析将判断新建的对象是否逃逸。即时编译判断对象是否逃逸的依据:一种是对象是否被存入堆中(静态字段或者堆中对象的实例字段),另一种就是对象是否被传入未知代码。
经过测试,发现如果后续没有其它线程参与使用共享变量的情况下,JVM总是不会对堆内存有想法。
测试了对象实例化之后return逃逸出当前方法,如果没有其它线程使用的话,堆内存仍然没有什么变化。
只有当对象实例化之后,被其它线程使用的情况下它才会跑到堆内存里面去。
- // TODO: 2022/8/4 测试逃逸的情况下对象实例化的存放(return list)
- public static List testHeap3() throws InterruptedException {
- System.out.println("new了对象之前...");//jps 命令查看线程id
- Thread.sleep(20000);
- List
list=new ArrayList<>(10); - String a="hello ";
- for (int i = 0; i < 10; i++) {
- list.add(a);
- a=a + a;
- }
- System.out.println("准备return了...");//jmap -heap id
- Thread.sleep(20000);
- return list;
- }
main方法:
- private static List
list; - public static void main(String[] args) throws InterruptedException {
- list=testHeap3();
- new Thread(()->{
- try {
- System.out.println("准备使用共享变量..");
- Thread.sleep(10000);
- for (String s : list) {
- System.out.println(s);
- }
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }).start();
-
- }
1.没有其它线程参与之前

2.其它线程参与之后
使用比较大的空间测试
结论: 不逃逸的情况下,虽然是线程私有,但是由于实例化对象的时候发现栈内存不足以存放该对象,所以还是分配到堆中创建,因为10MB早已超出栈内存
- public static void testHeap1() throws InterruptedException {
- System.out.println("new了对象之前...");//jps 命令查看线程id
- Thread.sleep(20000);
- byte[] bys=new byte[1024 * 1024 *10];//10MB 堆中创建
- System.out.println("new了对象之后...");//jmap -heap id
- Thread.sleep(20000);
-
- }
1.

2.

如果new的对象占用空间比较小呢?-->会被JIT即时编译器优化在栈中运行
- public static void testHeap1() throws InterruptedException {
- System.out.println("new了对象之前...");//jps 命令查看线程id
- Thread.sleep(20000);
- byte[] bys=new byte[256]; //256b -->优化到栈中创建
- System.out.println("new了对象之后...");//jmap -heap id
- Thread.sleep(20000);
-
- }
1.

2.

如果一开始是在栈里面,有没有可能后期转移到堆中呢?
猜想: 如果占用内存变大,是会转移过去的
测试代码:
- // TODO: 2022/8/4 测试堆内存溢出的现象
- public static void testHeap2() throws InterruptedException {
- System.out.println("new了对象之前...");//jps 命令查看线程id
- Thread.sleep(20000);
- List
list=new ArrayList<>(); - System.out.println("new了对象之后...");//jmap -heap id
- Thread.sleep(20000);
- String a="hello ";
- int i=0;
- try {
- while (true){
- list.add(a);
- a=a + a;
- i++;
- if (i==20){
- System.out.println("达到20次了...");//todo 此时再查发现 以及从栈内存转移到堆内存中了
- Thread.sleep(20000);
- }
- }
- }catch (OutOfMemoryError e){
- e.printStackTrace();
- System.out.println(i);
- }
-
- }
1.一开始对象实例化之前,栈内存占用情况

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

可以看到堆内存占用情况没有改变,说明此时实例化是在栈内存中。
3.当循环了20次之后,占用空间已经不小了,此时已经被转移到堆内存

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