• 转载:java程序调用内存的变化过程


     前文知道了java程序运行时在内存中的大概分布,但是对于具体程序是如何运行的,看到一篇文章,直接转载过来。

    (一)不含静态变量的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 }

    内存过程分析:

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

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

    原文详见:https://blog.csdn.net/Myuhua/article/details/81385609

  • 相关阅读:
    Balanced Binary Tree
    Swap Nodes in Pairs
    Reverse Nodes in k-Group
    Reverse Linked List II
    Remove Nth Node From End of List
    Remove Duplicates from Sorted List II
    Remove Duplicates from Sorted List
    Partition List
    Merge Two Sorted Lists
    【Yii2.0】1.2 Apache检查配置文件语法
  • 原文地址:https://www.cnblogs.com/029zz010buct/p/10567901.html
Copyright © 2020-2023  润新知