【JVM】实例分析Java代码运行时内存布局

来源:互联网 发布:cdce.cf ad.js原理 编辑:程序博客网 时间:2024/04/25 22:55
Java内存模型对于我们实际分析Java代码有着无可替代的作用。用一个小例子来分析Java代码运行时,内存是如何布局的。
package test01;//日期类class BirthDate {    private int day;    private int month;    private int year;    public BirthDate(int d, int m, int y) {        day = d;        month = m;        year = y;    }    public void setDay(int d) {        day = d;    }    public void setMonth(int m) {        month = m;    }    public void setYear(int y) {        year = y;    }    public int getDay() {        return day;    }    public int getMonth() {        return month;    }    public int getYear() {        return year;    }    public void display() {        System.out.println                (day + " - " + month + " - " + year);    }}public class MemoryTest{    public static void main(String args[]){        MemoryTest test = new MemoryTest();        int date = 9;        BirthDate d1= new BirthDate(7,7,1970);        BirthDate d2= new BirthDate(1,1,2000);        test.change1(date);        test.change2(d1);        test.change3(d2);        System.out.println("date=" + date);        d1.display();        d2.display();    }    public void change1(int i){        i = 1234;    }    public void change2(BirthDate b) {        b = new BirthDate(22,2,2004);    }    public void change3(BirthDate b) {        b.setDay(22);    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70

注分析内存,首先我们必须要知道,Java中的内存主要在栈和堆中进行。同时在创建实例(new)时,对象会被分配在堆内存中 
为什么会在堆内存中创建实例对象呢?这是因为堆内存比较大,而且我们创建的对象大小是不定的,只有在Java运行时才能计算出真正的大小, 
而堆内存可以根据对象的大小动态的为对象分配内存。

一、实例创建

现在根据main方法中执行的代码步骤来分析内存块的情况。首先看对象实例化的代码前四行:
      MemoryTest test = new MemoryTest();        int date = 9;        BirthDate d1= new BirthDate(7,7,1970);        BirthDate d2= new BirthDate(1,1,2000);
  • 1
  • 2
  • 3
  • 4

如图: 
这里写图片描述

首先会在栈中分别分配一块内存空间存放test,date,d1,d2,同时在堆内存中分配三块内存空间,分别存放test,d1,d2所创建的实例。如图。由于date是一个int类型,属于Java基本类型,所以直接在栈内存中分配,不需要占用堆内存。这里在创建BirthDate实例时,运用到了BirthDate的构造方法。构造方法在使用时,会在栈内存中给构造方法的变量分配空间,并且给他们赋值,然后在堆内存中创建实例时,会将变量的值赋值给堆内存中创建的实例。当构造方法执行完成后,在栈内存为构造方法分配的变量会销毁。这个时候第一阶段就完成了。接下来看执行方法的部分。

二、change1方法

 test.change1(date);
  • 1
在执行change1方法时,由于有局部变量i,所以会现在栈中为i分配一块儿空间,如图:

这里写图片描述

接下来回去执行方法体,将i进行赋值为1234。

这里写图片描述 
当完成change1方法调用时,i在内存中的生命周期就结束了。会立即销毁。也就是说只要方法执行结束,在栈中的局部变量会立即销毁。 
最后执行完change1方法后,内存又恢复到了最开始的样子。跟chang1没执行之前是一样的。 
这里写图片描述

二、change2方法

接下来开始执行
test.change2(d1);
  • 1
首先在栈中创建change2方法的参数b变量,同时将d1的值赋给b,由于d1是引用类型,所以会将b的引用也指向d1的引用。如图:

这里写图片描述

这里其实b和d1的值应该是完全一样的。只是为b这个变量分配一块儿空间,没有变量值可言。并且b和d1的值是完全一样的。在执行方法体时b = new BirthDate(22,2,2004);又重复构造方法的过程,在堆中创建一个实例,将b的引用指向新的实例。

这里写图片描述

指向新的实例后,b在栈空间的值就和之前的不一样了。hashcode就会发生改变。

在执行完change2方法后,b就会销毁。b指向堆中的实例不会立即销毁,需要垃圾回收机制来将它回收。

三、change3方法

test.change3(d2);
  • 1
在执行change3方法时,也会在栈中创建一个b的变量,同时将d2的值复制给b,同时将b指向d2的实例。

这里写图片描述

当执行完change3后d2指向的实例的day变为22。同时b会随之销毁。最后内存的布局回事这个样子的: 
这里写图片描述

小结

根据上述的过程,可以总结出局部变量会在栈内存中分配临时的空间来存储,当方法执行完成后,分配的临时空间会立即销毁。同时指向堆中的实例也就无效了,等待GC进行回收。根据这点,我们为了程序的运行效率,在编写代码时,应该尽量少new对象实例,只在需要的时候才去new对象,否则当new出来的实例不再被用时,由于GC会去检查堆中的实例在栈中有没有被引用,如果被引用则不能被回收。所以new出来的对象会永久的存放在内存中,这样长时间会影响内存的利用率和程序的运行效率。最后总结一下内存各个区的主要职能:栈:存放局部变量和对象的引用。堆:对象的实例方法区:静态变量和常量字符串data:加载的代码class文件
阅读全文
0 0
原创粉丝点击