成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

【Java系列】從字節(jié)碼角度深度理解Java函數(shù)調(diào)用傳參方式

LdhAndroid / 2194人閱讀

摘要:下文將從字節(jié)碼的角度,分析中基本類型傳參和對(duì)象傳參。主函數(shù)執(zhí)行時(shí),操作棧會(huì)推入主函數(shù)棧幀,其中包含了主函數(shù)的局部變量表,字節(jié)碼,返回值等信息。主函數(shù)的棧幀會(huì)被推入棧,成為當(dāng)前操作棧。

個(gè)人網(wǎng)站地址: http://kailuncen.me/2017/06/0...

一個(gè)小問(wèn)題

在開(kāi)源中國(guó)看到這樣一則問(wèn)題

https://www.oschina.net/quest...,其中的變量a前后的輸出是什么?

我答錯(cuò)了,我認(rèn)為傳入function的就是main函數(shù)中的a,在function中修改了a的地址,因此回到主函數(shù)后,a的地址已經(jīng)變成了function中所賦予的a2的地址,因此經(jīng)過(guò)function處理后a的值已經(jīng)改變了。
但結(jié)果并不是,因?yàn)槲液雎粤薐ava的基礎(chǔ)知識(shí)點(diǎn)之一。

Java中傳參都是值傳遞,如果是基本類型,就是對(duì)值的拷貝,如果是對(duì)象,就是對(duì)引用地址的拷貝。

下文將從字節(jié)碼的角度,分析Java中基本類型傳參和對(duì)象傳參。

基本類型傳參

以下是處理類Porcess,代碼應(yīng)該已經(jīng)能夠自解釋了。function1是將傳參a變成2,function2是初始化int b,賦值為5,然后將b賦值給a。

public class Process {

    public void function3(int a) {
        a = 2;
    }

    public void function4(int a) {
        int b = 5;
        a = b;
    }
}

我們繼續(xù)看測(cè)試類TestPrimitive

public class TestPrimitive {

    public static void main(String[] args) {
        Process process = new Process();
        int age = 18;
        System.out.println(age);
        process.function3(age);
        System.out.println(age);
    }
}

結(jié)果是在經(jīng)過(guò)function3的處理后,輸出結(jié)果是

18
18

修改測(cè)試類代碼,在經(jīng)過(guò)function4處理后,仍然一致。

18
18

結(jié)論: 基本類型的傳參,對(duì)傳參進(jìn)行修改,不影響原本參數(shù)的值。

對(duì)象類型傳參

以下是處理類Porcess,function1,將參數(shù)car的顏色設(shè)置成blue。function2,新建了car2,將car2賦值給了參數(shù)car。

public class Process {

    public void function1(Car car) {
        car.setColor("blue");
    }

    public void function2(Car car) {
        Car car2 = new Car("black");
        car = car2;
        car.setColor("orange");
    }
}

我們繼續(xù)看測(cè)試類TestReference

public class TestReference {

    public static void main(String[] args) {
        Process process = new Process();
        Car car = new Car("red");
        System.out.println(car);
        process.function1(car);
        System.out.println(car);
    }
}

結(jié)果是在經(jīng)過(guò)function1的處理后,輸出結(jié)果是

Car{color="red"}
Car{color="blue"}

修改測(cè)試類,在經(jīng)過(guò)function2的處理后

Car{color="red"}
Car{color="red"}

結(jié)論: 對(duì)象類型的傳參,直接調(diào)用傳參set方法,可以對(duì)原本參數(shù)進(jìn)行修改。如果修改傳參的指向地址,調(diào)用傳參的set方法,無(wú)法對(duì)原本參數(shù)的值進(jìn)行修改。

綜上所述,基本類型的傳參,在方法內(nèi)部是值拷貝,有一個(gè)新的局部變量得到這個(gè)值,對(duì)這個(gè)局部變量的修改不影響原來(lái)的參數(shù)。對(duì)象類型的傳參,傳遞的是堆上的地址,在方法內(nèi)部是有一個(gè)新的局部變量得到引用地址的拷貝,對(duì)該局部變量的操作,影響的是同一塊地址,因此原本的參數(shù)也會(huì)受影響,反之,若修改局部變量的引用地址,則不會(huì)對(duì)原本的參數(shù)產(chǎn)生任何可能的影響。

上文已經(jīng)得到結(jié)論,我們從JVM的字節(jié)碼的角度看一下過(guò)程是怎么樣的。

首先大致JVM的基本結(jié)構(gòu),對(duì)基本類型,和對(duì)象存放的位置有一個(gè)大致的了解。下圖是JVM的基本組件圖。

介紹幾個(gè)基本的組件

程序計(jì)數(shù)器: 存儲(chǔ)每個(gè)線程下一步將執(zhí)行的JVM指令。

JVM棧(JVM Stack): JVM棧是線程私有的,每個(gè)線程創(chuàng)建的同時(shí)都會(huì)創(chuàng)建JVM棧,JVM棧中存放的為當(dāng)前線程中局部基本類型的變量(java中定義的八種基本類型:boolean、char、byte、short、int、long、float、double)、部分的返回結(jié)果以及Stack Frame(每個(gè)方法都會(huì)開(kāi)辟一個(gè)自己的棧幀),非基本類型的對(duì)象在JVM棧上僅存放一個(gè)指向堆上的地址

堆(heap): JVM用來(lái)存儲(chǔ)對(duì)象實(shí)例以及數(shù)組值的區(qū)域,可以認(rèn)為Java中所有通過(guò)new創(chuàng)建的對(duì)象的內(nèi)存都在此分配,Heap中的對(duì)象的內(nèi)存需要等待GC進(jìn)行回收。

方法區(qū)(Method Area): 方法區(qū)域存放了所加載的類的信息(名稱、修飾符等)、類中的靜態(tài)變量、類中定義為final類型的常量、類中的Field信息、類中的方法信息,當(dāng)開(kāi)發(fā)人員在程序中通過(guò)Class對(duì)象中的getName、isInterface等方法來(lái)獲取信息時(shí),這些數(shù)據(jù)都來(lái)源于方法區(qū)域。

本地方法棧(Native Method Stacks): JVM采用本地方法棧來(lái)支持native方法的執(zhí)行,此區(qū)域用于存儲(chǔ)每個(gè)native方法調(diào)用的狀態(tài)。

運(yùn)行時(shí)常量池(Runtime Constant Pool): 存放的為類中的固定的常量信息、方法和Field的引用信息等,其空間從方法區(qū)域中分配。JVM在加載類時(shí)會(huì)為每個(gè)class分配一個(gè)獨(dú)立的常量池,但是運(yùn)行時(shí)常量池中的字符串常量池是全局共享的。

下圖是從另一個(gè)角度解析JVM的結(jié)構(gòu),JVM是基于棧來(lái)操作的,每一個(gè)線程有自己的操作棧,遇到方法調(diào)用時(shí)會(huì)開(kāi)辟棧幀,它含有自己的返回值,局部變量表,操作棧,以及對(duì)常量池的符號(hào)引用。
如果是基本類型,則存放在棧里的是值,如果是對(duì)象,存放在棧上是對(duì)象在堆上存放的地址。

了解了JVM的基本結(jié)構(gòu),我們來(lái)看一下上述的兩種代碼,一種是基本類型傳參,一種是對(duì)象傳參,在字節(jié)碼表現(xiàn)上的不同。
使用javap對(duì)字節(jié)碼進(jìn)行反編譯

javap -verbose Main
基本類型傳參字節(jié)碼

以下是TestPrimitive類在執(zhí)行function3時(shí)的字節(jié)碼。

public static void main(java.lang.String[]);
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=1
         0: new           #2                  // class Process
         3: dup           
         4: invokespecial #3                  // Method Process."":()V
         7: astore_1      
         8: bipush        18
        10: istore_2      
        11: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
        14: iload_2       
        15: invokevirtual #5                  // Method java/io/PrintStream.println:(I)V
        18: aload_1       
        19: iload_2       
        20: invokevirtual #6                  // Method Process.function3:(I)V
        23: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
        26: iload_2       
        27: invokevirtual #5                  // Method java/io/PrintStream.println:(I)V
        30: return        
      LineNumberTable:
        ...........
      LocalVariableTable:
        Start  Length     Slot  Name          Signature
          0      31         0     args        [Ljava/lang/String;
          8      23         1    process      LProcess;
          11      20        2      age            I

主函數(shù)執(zhí)行時(shí),JVM操作棧會(huì)推入主函數(shù)棧幀,其中包含了主函數(shù)的局部變量表,字節(jié)碼,返回值等信息。LocalVariableTable就是局部變量表,以0為索引起點(diǎn),第0個(gè)是局部變量String數(shù)組 args,第1個(gè)是局部變量process,保存新創(chuàng)建的Process對(duì)象的引用地址。第2個(gè)是局部變量age。在字節(jié)碼第8行,通過(guò)bipush 18,將常量18直接壓入操作棧,然后第20行,是調(diào)用了process的function3方法,傳入了age作為參數(shù)。
然后JVM操作棧將function3棧幀推入JVM棧,使得function3棧幀成為當(dāng)前棧幀,開(kāi)始執(zhí)行。

public void function3(int);
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=2, args_size=2
         0: iconst_2      
         1: istore_1      
         2: return        
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
          0       3     0     this   LProcess;
          0       3     1       a      I

字節(jié)碼顯示,通過(guò)iconst_2,istore_1,將基本類型2推入棧,并保存在局部變量a中,這里就展示了我們?cè)诜椒▋?nèi)部的修改都是對(duì)function3的局部變量a的值修改,不影響主函數(shù)中的a。從主函數(shù)的字節(jié)碼中可以看到,它的值保存的還是第10行,通過(guò)istore_2保存到局部變量第2個(gè)索引處的18.

如果用圖示來(lái)表示上述字節(jié)碼執(zhí)行過(guò)程中,JVM棧,man函數(shù)棧幀,function3棧幀內(nèi)部變化的話,如下圖所示。

1.主函數(shù)的棧幀會(huì)被推入JVM棧,成為當(dāng)前操作棧。

2.然后進(jìn)去main函數(shù)棧幀,初始化完畢后如下圖所示。

3.主要看bipush 18,將基本變量18推入操作棧,基本變量類型是存儲(chǔ)在棧幀內(nèi)部的。

4.然后執(zhí)行istore_2, 將棧頂出棧,并且保存在局部變量索引2處。

5.然繼續(xù)執(zhí)行至18: aload_1,,將創(chuàng)建的process的地址保存在局部變量索引1處,19:iload_2,將局部變量2處保存的基本類型壓入棧。

6.然后執(zhí)行至20:invokevirtula #6,也就是調(diào)用function3,進(jìn)入function3的棧幀。執(zhí)行0: iconst_2,將常量2推入棧,此時(shí)function3的棧幀有一個(gè)局部變量1處保存著傳入的參數(shù)18。

7.繼續(xù)執(zhí)行1:istore_1,將棧頂推出,保存在局部變量1處,覆蓋了傳入的參數(shù)18,然后return,將function3函數(shù)棧幀彈出JVM棧,繼續(xù)執(zhí)行main函數(shù)棧幀。

之后會(huì)繼續(xù)執(zhí)行main函數(shù)棧幀,在function3函數(shù)棧幀中發(fā)生的一切都和Main Stack中的局部變量age的值沒(méi)有任何關(guān)系。

對(duì)象類型傳參字節(jié)碼

以下是TestReference類在執(zhí)行function2時(shí)的字節(jié)碼。

Code:
      stack=3, locals=3, args_size=1
         0: new           #2                  // class Process
         3: dup           
         4: invokespecial #3                  // Method Process."":()V
         7: astore_1      
         8: new           #4                  // class Car
        11: dup           
        12: ldc           #5                  // String red
        14: invokespecial #6                  // Method Car."":(Ljava/lang/String;)V
        17: astore_2      
        18: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
        21: aload_2       
        22: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
        25: aload_1       
        26: aload_2       
        27: invokevirtual #9                  // Method Process.function2:(LCar;)V
        30: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
        33: aload_2       
        34: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
        37: return        
      LocalVariableTable:
        Start  Length  Slot       Name         Signature
          0      38         0     args         [Ljava/lang/String;
          8      30         1     process      LProcess;
          18      20        2     car          LCar;

我們可以通過(guò)字節(jié)碼14-17行,看到局部變量索引2處存放的是Car的實(shí)例在堆上的地址,這和基本類型不同,基本類型的值都是直接存放在棧里面的。然后通過(guò)字節(jié)碼第27行將car的引用地址傳入function2。接下來(lái)我們看看function2的字節(jié)碼。

public void function2(Car);
    flags: ACC_PUBLIC
    Code:
      stack=3, locals=3, args_size=2
         0: new           #4                  // class Car
         3: dup           
         4: ldc           #5                  // String black
         6: invokespecial #6                  // Method Car."":(Ljava/lang/String;)V
         9: astore_2      
        10: aload_2       
        11: astore_1     
        12: aload_1       
        13: ldc           #7                  // String orange
        15: invokevirtual #3                  // Method Car.setColor:(Ljava/lang/String;)V
        18: return     
      LocalVariableTable:
        Start  Length  Slot  Name       Signature
          0      13     0          this   LProcess;
          0      13     1           car   LCar;
         10       3     2          car2   LCar;

題外話,因?yàn)檫@個(gè)是調(diào)用具體實(shí)例的函數(shù),所以索引0處保存的是實(shí)例的引用。索引1保存的是傳參car的引用地址,car2保存的是函數(shù)內(nèi)創(chuàng)建的Car實(shí)例的地址。字節(jié)碼0-9,完成了car2的引用地址保存,第10行將Car2的引用地址推入棧,第11行通過(guò)astore_1,將棧頂值保存到第一個(gè)局部變量,也就是修改了覆蓋了局部變量car的引用地址。因此第15行,修改的是car當(dāng)前引用的地址的實(shí)例的參數(shù)值。當(dāng)退出棧幀,回到主函數(shù),主函數(shù)的局部變量a保存的引用地址沒(méi)有改變。

如果用圖示來(lái)表示上述字節(jié)碼執(zhí)行過(guò)程中,JVM棧,man函數(shù)棧幀,function3棧幀內(nèi)部變化的話,如下圖所示。

1.main函數(shù)棧幀和上文測(cè)試基本類型傳參時(shí)的字節(jié)碼大致類似,不同的是局部變量處。局部變量2處保存的是main函數(shù)中新建的Car實(shí)例的堆上地址。對(duì)象的實(shí)際存放都是在堆中,棧幀的局部變量中保存的是他們?cè)诙焉系牡刂贰?/p>

2.一直執(zhí)行到調(diào)用function2,進(jìn)入function2棧幀。在執(zhí)行至9:astore_2時(shí),棧中新創(chuàng)建的Car實(shí)例的引用地址出棧,保存在局部變量2處。局部變量1保存的是傳參進(jìn)來(lái)的Car實(shí)例的引用地址。

3.然后執(zhí)行至10: aload_2,11:store_1,在這里,1236df被推入棧,然后保存在了局部變量1,覆蓋了局部變量car本來(lái)的引用地址。

**
因此,當(dāng)function2對(duì)局部變量2進(jìn)行相關(guān)操作時(shí),影響的都是1236df這塊地址,和main函數(shù)局部變量car中保存的1235df不是一塊地址,所以前后打印結(jié)果一致。**

測(cè)試類TestReference調(diào)用function1時(shí),function1沒(méi)有改變局部變量car的引用地址,保存的仍然是傳入的引用地址,所以function1中car進(jìn)行的操作影響了這塊地址保存的內(nèi)容,導(dǎo)致了前后打印結(jié)果不一致。

Code:
      stack=2, locals=2, args_size=2
         0: aload_1       
         1: ldc           #2                  // String blue
         3: invokevirtual #3                  // Method Car.setColor:(Ljava/lang/String;)V
         6: return        
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
         0       7       0    this    LProcess;
         0       7       1     car    LCar;

本文對(duì)Java基本類型傳參和對(duì)象傳參,從字節(jié)碼角度進(jìn)行了分析,現(xiàn)在不會(huì)再搞錯(cuò)了吧~

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/70142.html

相關(guān)文章

  • Java系列JVM角度深度解析Java核心類String的不可變特性

    摘要:性能,大量運(yùn)用在哈希的處理中,由于的不可變性,可以只計(jì)算一次哈希值,然后緩存在內(nèi)部,后續(xù)直接取就好了。這是目前的一個(gè)底層字節(jié)碼的實(shí)現(xiàn),那么是不是沒(méi)有使用或者的必要了呢。 凱倫說(shuō),公眾號(hào)ID: KailunTalk,努力寫(xiě)出最優(yōu)質(zhì)的技術(shù)文章,歡迎關(guān)注探討。 1. 前言 最近看到幾個(gè)有趣的關(guān)于Java核心類String的問(wèn)題。 String類是如何實(shí)現(xiàn)其不可變的特性的,設(shè)計(jì)成不可變的好處...

    afishhhhh 評(píng)論0 收藏0
  • Java開(kāi)發(fā)

    摘要:大多數(shù)待遇豐厚的開(kāi)發(fā)職位都要求開(kāi)發(fā)者精通多線程技術(shù)并且有豐富的程序開(kāi)發(fā)調(diào)試優(yōu)化經(jīng)驗(yàn),所以線程相關(guān)的問(wèn)題在面試中經(jīng)常會(huì)被提到。將對(duì)象編碼為字節(jié)流稱之為序列化,反之將字節(jié)流重建成對(duì)象稱之為反序列化。 JVM 內(nèi)存溢出實(shí)例 - 實(shí)戰(zhàn) JVM(二) 介紹 JVM 內(nèi)存溢出產(chǎn)生情況分析 Java - 注解詳解 詳細(xì)介紹 Java 注解的使用,有利于學(xué)習(xí)編譯時(shí)注解 Java 程序員快速上手 Kot...

    LuDongWei 評(píng)論0 收藏0
  • 讀書(shū)筆記之深入理解Java虛擬機(jī)

    摘要:前言本文內(nèi)容基本摘抄自深入理解虛擬機(jī),以供復(fù)習(xí)之用,沒(méi)有多少參考價(jià)值。此區(qū)域是唯一一個(gè)在虛擬機(jī)規(guī)范中沒(méi)有規(guī)定任何情況的區(qū)域。堆是所有線程共享的內(nèi)存區(qū)域,在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建。虛擬機(jī)上把方法區(qū)稱為永久代。 前言 本文內(nèi)容基本摘抄自《深入理解Java虛擬機(jī)》,以供復(fù)習(xí)之用,沒(méi)有多少參考價(jià)值。想要更詳細(xì)了解請(qǐng)參考原書(shū)。 第二章 1.運(yùn)行時(shí)數(shù)據(jù)區(qū)域 showImg(https://segment...

    jaysun 評(píng)論0 收藏0
  • 【修煉內(nèi)功】[JVM] 淺談虛擬機(jī)內(nèi)存模型

    摘要:也正是因此,一旦出現(xiàn)內(nèi)存泄漏或溢出問(wèn)題,如果不了解的內(nèi)存管理原理,那么將會(huì)對(duì)問(wèn)題的排查帶來(lái)極大的困難。 本文已收錄【修煉內(nèi)功】躍遷之路 showImg(https://segmentfault.com/img/bVbsP9I?w=1024&h=580); 不論做技術(shù)還是做業(yè)務(wù),對(duì)于Java開(kāi)發(fā)人員來(lái)講,理解JVM各種原理的重要性不必再多言 對(duì)于C/C++而言,可以輕易地操作任意地址的...

    sanyang 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<