Java方法調用機制


最近在編程時,修改方法傳入對象的對象引用,並沒有將修改反映到調用方法中。奇怪為什么結果沒有變化,原因是遺忘了Java對象引用和內存分配機制。本文介紹3個點:

  ① 該問題舉例說明

  ② 簡要闡述Java內存區域

  ③ 介紹JVM中方法調用的機制

1. Java方法調用傳參實例解析

Java中參數傳遞是值傳遞,即調用方法時,所有參數的傳遞都是值傳遞。基本類型直接將值拷貝給方法參數,引用類型將引用地址拷貝給方法參數。先看兩個String類型和對象引用的實例。

(1)字符串對象引用

public static void main(String[] args) {
        String a = "123"; app(a); System.out.println(a); } private static void app(String a) { //String不可修改,只會重新創建,故main中a不變 a += "456"; }

 輸出:123

分析:結果並沒有因為調用了app方法,而輸出123456。如注釋中描述,String(由字符數組實現)是不可修改的,所有的修改都會重新創建新的String對象,並且字符串拼接也會重新創建String對象(具體見下文)。也就是說app中的a字符串引用已不再指向main中a指向的內存塊,即main方法中a指向的內存塊中字符串的值沒有發送變化。

下圖展示了對象引用與內存塊的關系,可以看出來main方法中的與app方法中的a沒關系,只是剛調用賦值的時候指向同一個內存塊。

 

 (2)字符串拼接源碼實現

通過下圖中字節碼命令可以看到,字符串拼接是通過StringBuilder實現的。

  • 讀取的數據經第8行構建為String對象;
  • 字符串拼接拆分為11-19,第11行構建StringBuilder對象,第16行調append方法,將字段串拼接到后邊(底層通過將字符串中的字符放入原字符串字符數組中)
  • 第19行,調StringBuilder.toString方法返回拼接好的字符串。

toString()的源碼如下:

@Override
    public String toString() { // Create a copy, don't share the array return new String(value, 0, count); }

 

重新構建String對象,並且注釋中說明創建拷貝,但不共享字符數組。String構造函數底層會調Arrays.copyOfRange(char[] original, int from, int to)方法,將original字符數組拷貝到一個新的字符數組中,源碼如下:

public static char[] copyOfRange(char[] original, int from, int to) {
        int newLength = to - from; if (newLength < 0) throw new IllegalArgumentException(from + " > " + to); char[] copy = new char[newLength]; System.arraycopy(original, from, copy, 0, Math.min(original.length - from, newLength)); return copy; }

(3)普通對象引用

public static void main(String[] args) {
        ListNode node = new ListNode(4); System.out.println(node); chg(node); System.out.println(node.val); } private static void chg(ListNode node) { node.val+=1; System.out.println(node); node= new ListNode(2); System.out.println(node); } static class ListNode{ private int val; private ListNode next; ListNode(int val){ this.val = val; } }

輸出:

test.InsertSortTest$ListNode@2a139a55
test.InsertSortTest$ListNode@2a139a55
test.InsertSortTest$ListNode@15db9742
5

分析:①  test.InsertSortTest$ListNode@2a139a55地址對應的對象,在main方法調chg方法傳遞參數的時候,將地址拷貝給chg方法的參數node,在chg方法中修改對象的值,此時兩個方法中的node仍指向統一內存塊,故main方法中輸出為5。

②  node= new ListNode(2) 語句將chg方法中的node重新指向另一個對象地址test.InsertSortTest$ListNode@15db9742,此時main方法和chg方法中的node指向不同的對象。

2. Java內存區域

具體Java虛擬機運行時數據區的划分,網上有很多相關資料,還可以看《深入理解Java虛擬機》,在此就不贅述了。但需要說明一點JDK7和JDK8稍有不同,就是JDK8中將原有的方法區(Method Area)或永久代改為元空間(MetaSpace),即將存儲類信息、靜態變量等元數據信息從方法區(也是堆內存)移動到本地內存(native memory)中。將不會出現java.lang.OutOfMemoryError: PermGen異常,如果該區域設置了大小,可能會出現java.lang.OutOfMemoryError: Metadata space異常,如果不設置大小,默認是自增的。

JDK7中JVM運行時數據區的划分如下圖:(JDK7時,已將字符串常量池從方法區移到堆中)

JDK8中JVM運行時數據區的划分:

這兩張圖分別參考自選擇JDK1.8的理由之JVM內存變化深入理解系列之JDK8下JVM虛擬機(1)——JVM內存組成,其中對變化也闡述的比較清楚。

3. Java方法調用機制(字節碼執行引擎)

棧幀是支持虛擬機方法調用和方法執行的數據結構,每個方法調用都對應一個棧幀。棧幀中包含局部變量表、操作棧、動態連接和方法返回地址等信息,結構如下圖所示(圖摘自Java —— 運行時棧幀結構): 

具體內容的介紹參考書《深入理解Java虛擬機》。簡單總結如下:

  1. 基本概念

  • 棧幀中局部變量表的大小、操作數棧的大小在編譯期確定;
  • 局部變量表用於存儲方法參數和方法內部定義的局部變量,以槽(slot)為最小單位; 
  • 操作數棧用於存儲指令計算對應的數據元素;
  • 動態連接是指向運行時常量池中該棧幀所屬方法放入引用,在運行期間可以轉化為直接引用;
  • 返回地址保存棧幀退出時,返回到方法被調用的位置,有正常退出(由PC計數器確定)和異常退出(由異常處理器表確定);

  2. 方法調用

  (1)解析

  • 方法調用在Class文件中存儲的都是符號引用,而不是方法在實際運行時內存布局中的入口地址(或直接引用)
  • 在類加載的解析階段,會將其中一部分符號引用轉為直接引用,如編譯期可知、運行期不可變的方法,包括靜態方法和私有方法兩大類,他們不可能被繼承或重寫;
  • 5條字節碼指令:invokestatic(調靜態方法)、invokespecial(調構造方法<init>、私有方法和父類方法)、invokevirtual(調虛方法)、invokeinterface(調接口方法,運行時確定實現該接口的對象)、invokedynamic(用於動態類型語言,暫不深究)。
  • 前2種對應的為非虛方法,解析階段可以將符號引用轉為直接引用,不會延遲到運行期;invokevirtual和invokeinterface作用於虛方法(除final方法外)

   (2)分派

  • Java多態性主要通過重載和重寫實現,他們在JAVA虛擬機中是通過分派完成,包含靜態分派(對應重載)和動態分派(對應重寫)
  • 定義一個變量,其有靜態類型和實際類型,靜態類型變量本身不會被改變,在編譯期可知;編譯時程序不知道一個對象實際類型是什么。
  • 重載時,是通過參數的靜態類型作為判斷依據的;重寫時,是在運行時根據實際類型作為判斷依據,根據操作數棧頂所指的實際類型,去類型和父類型的常量池中查找對應方法。

  3. 方法執行

  Java虛擬機采用基於棧的字節碼解釋執行,過程涉及字節碼指令、程序計數器、局部變量表和操作棧等,具體例子可參考書《深入理解Java虛擬機》。

4. 總結

  • 方法調用時,方法參數是通過值傳遞的,並且方法參數會存儲在棧幀中的局部變量表中,當修改該參數變量的指針時,與原來變量所指的內存塊不同
  • Java虛擬機在JDK8時,將原來的永久代(方法區)改為元空間,放入本地內存。其中一個好處是防止永久代空間溢出問題
  • Java方法調用和執行是基於棧的字節碼指令解釋執行引擎,調用過程中涉及什么時機將符號引用轉為直接引用,非虛方法調用發生在解析階段,重載發生在編譯期,重寫發生在運行時

5.參考

《深入理解Java虛擬機》


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM