• (转)java程序调用内存变化过程分析(详细)


    原博地址:

    https://blog.csdn.net/Myuhua/article/details/81385609

    (一)不含静态变量的java程序运行时内存变化过程分析

    代码:

     1 package oop;
     2  
     3 /**
     4  * 说明:实体类
     5  *
     6  * @author huayu
     7  * @date 2018/8/3 
     8  */
     9 public class Birthday {
    10     private int day;
    11     private int month;
    12     private int year;
    13     //有参构造方法
    14     public Birthday(int day, int month, int year) {
    15         this.day = day;
    16         this.month = month;
    17         this.year = year;
    18     }
    19  
    20     public int getDay() {
    21         return day;
    22     }
    23  
    24     public void setDay(int day) {
    25         this.day = day;
    26     }
    27  
    28     public int getMonth() {
    29         return month;
    30     }
    31  
    32     public void setMonth(int month) {
    33         this.month = month;
    34     }
    35  
    36     public int getYear() {
    37         return year;
    38     }
    39  
    40     public void setYear(int year) {
    41         this.year = year;
    42     }
    43  
    44     @Override
    45     public String toString() {
    46         return "Birthday{" +
    47                 "day=" + day +
    48                 ", month=" + month +
    49                 ", year=" + year +
    50                 '}';
    51     }
    52 }
    53  
    54  
    55  
    56  
    57  
    58  
    59  
    60  
    61 import oop.Birthday;
    62  
    63 /**
    64  * 说明:测试类
    65  *
    66  * @author huayu
    67  * @date 2018/8/3 
    68  */
    69 public class TestBirthday {
    70     public static void main(String[] args) {
    71           //new TestBirthday();相当于调用了TestBirthday类的无参构造方法(编译器默认加的)
    72 1.        TestBirthday test=new TestBirthday();
    73           //调到这句在栈内存分配一块名为date,值为9的内存
    74 2.        int date=9;
    75           //new Birthday(7,7,1970);相当于调用了Birthday类中的有参构造方法
    76 3.        Birthday d1=new Birthday(7,7,1970);
    77 4.        Birthday d2=new Birthday(1,1,2000);
    78 5.        test.change1(date);
    79 6.        test.change2(d1);
    80 7.        test.change3(d2);
    81 8.        System.out.println("date="+date);
    82 9.        d1.toString();
    83 10.       d2.toString();
    84  
    85     }
    86     public void change1(int i){
    87 11.        i=1234;
    88     }
    89     public void change2(Birthday b){
    90 12.        b=new Birthday(22,2,2004);
    91     }
    92     public void change3(Birthday b){
    93 13.        b.setDay(22);
    94     }
    95 }
    96  

    内存过程分析:

    在做分析以前我们应该预备的知识有:

    1)栈内存储的是局部变量,基础类型的局部变量也分配在栈中,而且它只占一块内存:如下图栈中的局部变量date,一个int类型变量分配了一块int类型空间,四个字节,里面装了个值9,名字叫做date。

    2)引用类型在内存中占两大块(栈中跟堆中),一块在栈中存的是指向对象的引用(对象在堆中的地址),一块在堆中存的是new出来的对象。(凡是new出东西来,则在堆内存中做分配):如下图栈中的局部变量test,一个引用类型分配了一块内存用于存test的引用(即对象在堆中的存储地址,后期利用这个存储地址去找到并操作堆中new出来这个引用的对象),它指向了在堆中new出来的对象的一块内存(即图中那块未存东西的空位置)。

    3)方法中的形参与局部变量等同

    4)方法调用的时候是值传递

    5)方法执行完毕后,在栈中为这个方法分配的局部变量的空间全部消失。

    6)在堆中new出来的对象若栈中没有了它的引用(也就是不用它了),后期会被GC清理掉那部分堆中的内存,而GC回收的具体时间不可控。

    上面代码中当执行到TestBirthday中main方法第四句时,内存里面的布局是这个样子的(下图1中所示堆中的d1对象显示的好像是3块,其实就是一个块,也就是堆中就一个d1对象,我为了看起来清楚才这么画的自己知道就可以了,d2同理也是就一个对象)。

    (1)执行change1(int i)方法

    接下来我们开始执行第五句chage1(int i)方法,而里面有个形参i,当我们调用这个方法的时候,首先应该在栈里面为形参(与局部变量等同)分配一块空间,如下分配了一个空间名字叫i,值的话是实参传了多少就是多少,在这实参是date且值为9,所以形参i的值是9(方法调用的时候是值传递,相当于把date的值9复制给了i),此时的内存布局为图2:

    执行到第11句,i=1234;到这时i的值由9变为了1234,但是date值不会遍,因为i当时的值9是date复制了一份给它的,所以i的改变对date无影响。此时的内存布局为图3:

    当执行完change1方法后,形参i在栈中分配的空间被收回(注意这时date依然不受影响哈),此时的内存布局为图4

    (2)执行change2(Birthday b)方法

    首先,形参要求传一个Birthday  b的引用类型,所以我们将d1传进来。Birthday  b是一个局部变量,二话不说在栈内存空间内分配一个变量b,b中装的是实参中传的内容(即d1中的内容),当执行了这个方法之后,它会把d1中的值复制给b。此时b中的地址跟d1是一样的,他们指向的是同一个对象(堆中的d1对象)。此时的内存布局为图5:

    当执行第12句b=new Birthday(22,2,2004);这一句时,堆中这是又会new出一个新的对象(凡是new出东西来,则在堆内存中做分配),此时栈中的引用地址指向新new出来的b的对象(注意到栈中b的引用地址也变化了)。此时的内存布局为图6:

    当方法change2执行完毕后,为其在栈中分布的局部变量空间也随之消失。注意,此时栈中为方法执行分布的局部变量被收回了,但是它在堆上new出来的对象b不一定消失,它会等待GC来回收它,具体回收时间不可控,所以我们也不确定它什么时候能消失,但是弱栈中没有了指向它的引用,这个b对象迟早会被GC清理掉,早晚会消失。此时的内存布局为图7:

    (3)执行change3(Birthday b)方法

    首先,形参要求传一个Birthday  b的引用类型,所以我们将d2传进来。Birthday  b是一个局部变量,二话不说在栈内存空间内分配一个变量b,b中装的是实参中传的内容(即d2中的内容),当执行了这个方法之后,它会把d2中的值复制给b。此时b中的地址跟d2是一样的,他们指向的是同一个对象(堆中的d2对象)。此时的内存布局为图8:

    当执行到第13句b.setDay(22);(是Birthday类中的public void setDay(int day) {this.day = day;}这个方法),它会利用b这个引用去操作d2对象,当传入实参22给int day后它会把值传给this.day,则day的值真正被改变了(注意,此时的栈中b引用跟d2引用的值都没变,还是指向同一个对象)。此时的内存布局为图9:

    当方法change3执行完毕后,为其在栈中分布的局部变量空间也随之消失。注意,虽然此时栈中b引用虽然消失了,但它指向的对象d2还有栈中的d2引用在,所以堆中对象d2不会消失。此时的内存布局为图10:

    change1,change2方法调用完后,或早或晚的栈内存,堆内存中分配的空间的都会被回收,没起任何实质性作用,相当与白调了。而change3方法调用了实体类Birthday中的具体方法,确实并对堆内存中仍然被栈空间的引用d2所引用的堆内对象做了修改产生了实质性作用。

    (二)含static静态变量的java程序运行时内存变化过程分析

    预备知识:static关键字

    1)在类中,用static声明的成员变量为静态成员变量,它为该类的公用变量。在第一次使用时候被初始化,对于该类的所有对象来说,static成员变量只有一份。

    2)用static声明的方法为静态方法,在调用该方法时,不会将对象的引用传递给它,所以在static方法中不可访问非static的成员。(静态方法只能访问静态成员,因为非静态方法的调用要先创建对象,在调用静态方法时可能对象并没有被初始化)。

    3)可以通过对象引用或类名(不需要实例化)访问静态成员。

    4)static静态变量存在在data seg数据区,就算是不new它,它也会在data seg部分保留一份。静态变量是属于整个类的,它不属于某一个对象。  

    知识链接:字符串常量在内存中分配也是在data segment部分。

     1 /**
     2  * 说明:静态变量内存分析举例
     3  * 
     4  * @author huayu
     5  * @date 2018/8/3 
     6  */
     7 public class Cat {
     8     //静态成员变量,就算不new对象它也会在data seg里面保存一份,它属于整个类
     9     //不属于某个对象。int静态变量可以用来计数。
    10     //对静态值访问:1.任何一个对象都可以访问这个静态对象,访问的时候都是同一块内存
    11     //2.即便是没有对象,也可以通过 类名. 来访问 如:System.out  out是个静态变量
    12 1.    private static  int sid=0;
    13     //非静态成员变量 new对象的时候在堆内存对象中保存,每new一个对象产生一块
    14 2.    private  String name;
    15     //非静态成员变量 new对象的时候在堆内存对象中保存,每new一个对象产生一块
    16 3.    private int id;
    17  
    18     public Cat(String name) {
    19 4.        this.name = name;
    20 5.        id=sid++;
    21     }
    22  
    23     public void info(){
    24 6.        System.out.println("My name is "+name+" No."+id);
    25     }
    26  
    27     public static void main(String[] args) {
    28         //静态变量sid属于整个Cat类,不属于某个对象,可以用类名.来访问,所以这儿没有new任何对 
    29         //象,直接用类名.(Cat.sid)来访问的。
    30 7.        Cat.sid=100;
    31         //字符串常量分配在data seg
    32 8.        Cat mimi=new Cat("mimi");
    33 9.        Cat pipi=new Cat("pipi");
    34 10.       mimi.info();
    35 11.       pipi.info();
    36  
    37     }
    38 }
    39 My name is mimi No.id=100 sid= 102
    40 My name is pipi No.id=101 sid= 102

    执行完7Cat.sid时,静态变量sid值为100。内存分布状态如下:

    (1)执行第7句构造方法

    第一步:执行第7句Cat mimi=new Cat("mimi");字符串常量“mimi”分布在data segment部分,内存分布如下:

    第二步:调到上面后就该到Cat的构造方法了,执行第4句this.name = name;这时根据传入构造方法的name形参,栈中就会为其开辟一块名为name的空间,实参“mimi”传了进来,这时候栈中name的引用指向了data segment中的字符串常量“mimi”,因为this.name = name,所以自身成员变量的this.name也指向了data segment中的字符串常量“mimi”。

    第三步:执行id=sid++;mimi对象的成员变量i值为原来sid的值100,接下来sid++,将sid的值改为101,内存状态如下图:

    第四步:构造方法执行完成后,为执行这个方法在栈中分配的形参变量的内存空间收回,name在栈中的空间消失(当然,为执行方法而在栈中分配的局部变量空间,方法执行完毕后都应该被收回了)

    (2)执行Cat pipi=new Cat("pipi"); 方法跟执行上面那个构造方法原理是一样的(当然,为执行方法而在栈中分配的局部变量空间,方法执行完毕后都应该被收回了),大家自己画一下,我这边把最后的内存分布状态给一下大家:

    从以上sid(static id)的变化不难看出,int的静态变量可以用作计数用。

    (3)将以上Cat程序的static静态变量static int sid;再改为非静态变量 int sid;后做内存分析对比

    代码:

     1 /**
     2  * 说明:将上面代码静态变量改为非静态的再做内存分析
     3  *
     4  * @author huayu
     5  * @date 2018/8/3 下午6:32
     6  */
     7 public class Cat1 {
     8     private  int sid=0;
     9     private  String name;
    10     private int id;
    11  
    12     public Cat1(String name) {
    13         this.name = name;
    14         id=sid++;
    15     }
    16  
    17     public void info(){
    18         System.out.println("My name is "+name+" No.id="+id+" sid= "+sid);
    19     }
    20  
    21     public static void main(String[] args) {
    22 //        Cat1.sid=100;
    23         Cat1 mimi=new Cat1("mimi");
    24         Cat1 pipi=new Cat1("pipi");
    25         mimi.info();
    26         pipi.info();
    27  
    28     }
    29 }
    30 输出结果:
    31 My name is mimi No.id=0 sid= 1
    32 My name is pipi No.id=0 sid= 1

    对以上程序进行内存分析:这儿上面以前详细的讲过,大家亲自动手去画一画,感受了解一下静态变量跟非静态变量的区别,下面我给贴个最终状态的图(记得,为执行方法而在栈中分配的局部变量空间,最终方法执行完毕后都应该被收回了):

     从以上过程不难看出当静态变量static int sid;改为非静态变量int sid;后,每new一个对象,sid不再发生变化,故用它来计数是不可能了。

  • 相关阅读:
    强制开启Android webview debug模式
    JavaScript DOM操作案例自定义属性的设置跟获取
    JavaScript innerText跟innerHTML的区别
    JavaScript DOM操作案例封装innerText跟textContent函数(浏览器兼容)
    JavaScript其他获取元素的方式
    JavaScript DOM操作案例根据类样式的名字获取元素
    JavaScript DOM操作案例根据name属性获取元素
    Java throws 使用
    理解 Android Build 系统
    理解Android编译命令
  • 原文地址:https://www.cnblogs.com/rgever/p/9535023.html
Copyright © 2020-2023  润新知