国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

【Java系列】從字節碼角度深度理解Java函數調用傳參方式

LdhAndroid / 2184人閱讀

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

個人網站地址: http://kailuncen.me/2017/06/0...

一個小問題

在開源中國看到這樣一則問題

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

我答錯了,我認為傳入function的就是main函數中的a,在function中修改了a的地址,因此回到主函數后,a的地址已經變成了function中所賦予的a2的地址,因此經過function處理后a的值已經改變了。
但結果并不是,因為我忽略了Java的基礎知識點之一。

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

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

基本類型傳參

以下是處理類Porcess,代碼應該已經能夠自解釋了。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;
    }
}

我們繼續看測試類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);
    }
}

結果是在經過function3的處理后,輸出結果是

18
18

修改測試類代碼,在經過function4處理后,仍然一致。

18
18

結論: 基本類型的傳參,對傳參進行修改,不影響原本參數的值。

對象類型傳參

以下是處理類Porcess,function1,將參數car的顏色設置成blue。function2,新建了car2,將car2賦值給了參數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");
    }
}

我們繼續看測試類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);
    }
}

結果是在經過function1的處理后,輸出結果是

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

修改測試類,在經過function2的處理后

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

結論: 對象類型的傳參,直接調用傳參set方法,可以對原本參數進行修改。如果修改傳參的指向地址,調用傳參的set方法,無法對原本參數的值進行修改。

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

上文已經得到結論,我們從JVM的字節碼的角度看一下過程是怎么樣的。

首先大致JVM的基本結構,對基本類型,和對象存放的位置有一個大致的了解。下圖是JVM的基本組件圖。

介紹幾個基本的組件

程序計數器: 存儲每個線程下一步將執行的JVM指令。

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

堆(heap): JVM用來存儲對象實例以及數組值的區域,可以認為Java中所有通過new創建的對象的內存都在此分配,Heap中的對象的內存需要等待GC進行回收。

方法區(Method Area): 方法區域存放了所加載的類的信息(名稱、修飾符等)、類中的靜態變量、類中定義為final類型的常量、類中的Field信息、類中的方法信息,當開發人員在程序中通過Class對象中的getName、isInterface等方法來獲取信息時,這些數據都來源于方法區域。

本地方法棧(Native Method Stacks): JVM采用本地方法棧來支持native方法的執行,此區域用于存儲每個native方法調用的狀態。

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

下圖是從另一個角度解析JVM的結構,JVM是基于棧來操作的,每一個線程有自己的操作棧,遇到方法調用時會開辟棧幀,它含有自己的返回值,局部變量表,操作棧,以及對常量池的符號引用。
如果是基本類型,則存放在棧里的是值,如果是對象,存放在棧上是對象在堆上存放的地址。

了解了JVM的基本結構,我們來看一下上述的兩種代碼,一種是基本類型傳參,一種是對象傳參,在字節碼表現上的不同。
使用javap對字節碼進行反編譯

javap -verbose Main
基本類型傳參字節碼

以下是TestPrimitive類在執行function3時的字節碼。

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

主函數執行時,JVM操作棧會推入主函數棧幀,其中包含了主函數的局部變量表,字節碼,返回值等信息。LocalVariableTable就是局部變量表,以0為索引起點,第0個是局部變量String數組 args,第1個是局部變量process,保存新創建的Process對象的引用地址。第2個是局部變量age。在字節碼第8行,通過bipush 18,將常量18直接壓入操作棧,然后第20行,是調用了process的function3方法,傳入了age作為參數。
然后JVM操作棧將function3棧幀推入JVM棧,使得function3棧幀成為當前棧幀,開始執行。

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

字節碼顯示,通過iconst_2,istore_1,將基本類型2推入棧,并保存在局部變量a中,這里就展示了我們在方法內部的修改都是對function3的局部變量a的值修改,不影響主函數中的a。從主函數的字節碼中可以看到,它的值保存的還是第10行,通過istore_2保存到局部變量第2個索引處的18.

如果用圖示來表示上述字節碼執行過程中,JVM棧,man函數棧幀,function3棧幀內部變化的話,如下圖所示。

1.主函數的棧幀會被推入JVM棧,成為當前操作棧。

2.然后進去main函數棧幀,初始化完畢后如下圖所示。

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

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

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

6.然后執行至20:invokevirtula #6,也就是調用function3,進入function3的棧幀。執行0: iconst_2,將常量2推入棧,此時function3的棧幀有一個局部變量1處保存著傳入的參數18。

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

之后會繼續執行main函數棧幀,在function3函數棧幀中發生的一切都和Main Stack中的局部變量age的值沒有任何關系。

對象類型傳參字節碼

以下是TestReference類在執行function2時的字節碼。

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;

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

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;

題外話,因為這個是調用具體實例的函數,所以索引0處保存的是實例的引用。索引1保存的是傳參car的引用地址,car2保存的是函數內創建的Car實例的地址。字節碼0-9,完成了car2的引用地址保存,第10行將Car2的引用地址推入棧,第11行通過astore_1,將棧頂值保存到第一個局部變量,也就是修改了覆蓋了局部變量car的引用地址。因此第15行,修改的是car當前引用的地址的實例的參數值。當退出棧幀,回到主函數,主函數的局部變量a保存的引用地址沒有改變。

如果用圖示來表示上述字節碼執行過程中,JVM棧,man函數棧幀,function3棧幀內部變化的話,如下圖所示。

1.main函數棧幀和上文測試基本類型傳參時的字節碼大致類似,不同的是局部變量處。局部變量2處保存的是main函數中新建的Car實例的堆上地址。對象的實際存放都是在堆中,棧幀的局部變量中保存的是他們在堆上的地址。

2.一直執行到調用function2,進入function2棧幀。在執行至9:astore_2時,棧中新創建的Car實例的引用地址出棧,保存在局部變量2處。局部變量1保存的是傳參進來的Car實例的引用地址。

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

**
因此,當function2對局部變量2進行相關操作時,影響的都是1236df這塊地址,和main函數局部變量car中保存的1235df不是一塊地址,所以前后打印結果一致。**

測試類TestReference調用function1時,function1沒有改變局部變量car的引用地址,保存的仍然是傳入的引用地址,所以function1中car進行的操作影響了這塊地址保存的內容,導致了前后打印結果不一致。

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;

本文對Java基本類型傳參和對象傳參,從字節碼角度進行了分析,現在不會再搞錯了吧~

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/70142.html

相關文章

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

    摘要:性能,大量運用在哈希的處理中,由于的不可變性,可以只計算一次哈希值,然后緩存在內部,后續直接取就好了。這是目前的一個底層字節碼的實現,那么是不是沒有使用或者的必要了呢。 凱倫說,公眾號ID: KailunTalk,努力寫出最優質的技術文章,歡迎關注探討。 1. 前言 最近看到幾個有趣的關于Java核心類String的問題。 String類是如何實現其不可變的特性的,設計成不可變的好處...

    afishhhhh 評論0 收藏0
  • Java開發

    摘要:大多數待遇豐厚的開發職位都要求開發者精通多線程技術并且有豐富的程序開發調試優化經驗,所以線程相關的問題在面試中經常會被提到。將對象編碼為字節流稱之為序列化,反之將字節流重建成對象稱之為反序列化。 JVM 內存溢出實例 - 實戰 JVM(二) 介紹 JVM 內存溢出產生情況分析 Java - 注解詳解 詳細介紹 Java 注解的使用,有利于學習編譯時注解 Java 程序員快速上手 Kot...

    LuDongWei 評論0 收藏0
  • 讀書筆記之深入理解Java虛擬機

    摘要:前言本文內容基本摘抄自深入理解虛擬機,以供復習之用,沒有多少參考價值。此區域是唯一一個在虛擬機規范中沒有規定任何情況的區域。堆是所有線程共享的內存區域,在虛擬機啟動時創建。虛擬機上把方法區稱為永久代。 前言 本文內容基本摘抄自《深入理解Java虛擬機》,以供復習之用,沒有多少參考價值。想要更詳細了解請參考原書。 第二章 1.運行時數據區域 showImg(https://segment...

    jaysun 評論0 收藏0
  • 【修煉內功】[JVM] 淺談虛擬機內存模型

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

    sanyang 評論0 收藏0

發表評論

0條評論

最新活動
閱讀需要支付1元查看
<