[深入理解JVM 一]---Java程式執行流程__Java

來源:互聯網
上載者:User

轉載:https://blog.csdn.net/sinat_33087001/article/details/76977437

本篇是《深入理解JVM》系列部落格的第一篇,旨在全域把控,先對整體流程有個認識,然後再分階段詳解.本篇部落格大部分內容來自http://www.cnblogs.com/dqrcsc/p/4671879.htmljava一些地方重新進行了整理,根據自己的理解重新規划了內容—TML 概述

程式執行流程我把它劃分為以下幾個步驟:編輯源碼、編譯產生class檔案、(載入class檔案、運行class位元組碼檔案),其中後兩個步驟都是在jvm虛擬機器上執行的。 
編輯 流程說明

編輯原始碼,就是我們在任何一個工具上編寫原始碼,可以是記事本,最後命名為Student.java。

這部分相當於我們在myeclipse這樣的ide上建立一個.java的Class然後寫內容。 源碼檔案

class Person{       private String name;       private int age;       public Person(int age, String name){              this.age = age;              this.name = name;       }       public void run(){       }}interface IStudyable{       public int study(int a, int b);}//public類,與java檔案同名public class Student extends Person implements IStudyable{       private static int cnt=5;       static{              cnt++;       }       private String sid;       public Student(int age, String name, String sid){              super(age,name);              this.sid = sid;       }       public void run(){              System.out.println("run()...");       }       public int study(int a, int b){              int c = 10;              int d = 20;              return a+b*c-d;       }       public static int getCnt(){              return cnt;       }       public static void main(String[] args){              Student s = new Student(23,"dqrcsc","20150723");              s.study(5,6);              Student.getCnt();              s.run();       }}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 編譯 流程說明

產生.class位元組碼檔案,輸入命令javac Student.java將該源碼檔案編譯產生.class位元組碼檔案。由於在源碼檔案中定義了兩個類,一個介面,所以產生了3個.clsss檔案。

這部分的操作就相當於我們在myeclipse這樣的ide上寫完代碼ctrl+s儲存 位元組碼檔案

位元組碼檔案,看似很微不足道的東西,卻真正實現了java語言的跨平台。各種不同平台的虛擬機器都統一使用這種相同的程式儲存格式。更進一步說,jvm啟動並執行是class位元組碼檔案,只要是這種格式的檔案就行,所以,實際上jvm並不像我之前想象地那樣與java語言緊緊地捆綁在一起。如果非常熟悉位元組碼的格式要求,可以使用二進位編輯器自己寫一個符合要求的位元組碼檔案,然後交給jvm去運行;或者把其他語言編寫的源碼編譯成位元組碼檔案,交給jvm去運行,只要是合法的位元組碼檔案,jvm都會正確地跑起來。所以,它還實現了跨語言……下面是一個位元組碼檔案Student.class.txt:

 
部分class檔案內容,從上面圖中,可以看到這些資訊來自於Student.class,編譯自Student.java,編譯器的主要版本號是52,也就是jdk1.8,這個類是public,然後是存放類中常量的常量池,各個方法的位元組碼等

它存放了這個類的各種資訊:欄位、方法、父類、實現的介面等各種資訊。 運行 流程說明

在命令列中輸入java Student這個命令,就啟動了一個java虛擬機器,然後載入Student.class位元組碼檔案到記憶體,然後運行記憶體中的位元組碼指令了。

這部分的操作就相當於我們在myeclipse這樣的ide上點擊運行按鈕 JVM基本結構介紹

Jvm的運行時記憶體分區和溢出處理見我的本系列第二篇博文(java底層分析—jvm記憶體分析)http://blog.csdn.net/sinat_33087001/article/details/76976027,有具體描述,這裡簡單說下。 
 
JVM中把記憶體分為方法區、Java棧、Java堆、本地方法棧、PC寄存器5部分資料區域。 
方法區:用於存放類、介面的中繼資料資訊,載入進來的位元組碼資料都儲存在方法區 
Java棧(虛擬機器棧):執行引擎運行位元組碼時的運行時記憶體區,採用棧幀的形式儲存每個方法的調用運行資料 
本地方法棧:執行引擎調用本地方法時的運行時記憶體區 
Java堆(堆):運行時資料區,各種對象一般都儲存在堆上 
PC寄存器(程式計數器):功能如同CPU中的PC寄存器,指示要執行的位元組碼指令。

JVM的功能模組主要包括類載入器、執行引擎和記憶體回收系統。 類載入

載入階段 
1)類載入器會在指定的classpath中找到Student.class(通過類的全限定名)這個檔案,然後讀取位元組流中的資料,將其儲存在方法區中。 
2)會根據Student.class的資訊建立一個Class對象,這個對象比較特殊,一般也存放在方法區中,用於作為運行時訪問Student類的各種資料的介面。 
驗證階段: 
3)必要的驗證工作,格式、語義等 
準備階段: 
4)為Student中的靜態欄位分配記憶體空間,也是在方法區中,並進行零初始化,即數字類型初始化為0,boolean初始化為false,參考型別初始化為null等。

              private static int cnt=5; 
1

此時,並不會執行賦值為5的操作,而是將其初始化為0。 
解析階段 
5)由於已經載入到記憶體了,所以原來位元組碼檔案中存放的部分方法、欄位等的符號引用可以解析為其在記憶體中的直接引用了,而不一定非要等到真正運行時才進行解析。 
初始化階段 
6)由於已經載入到記憶體了,所以原來位元組碼檔案中存放的部分方法、欄位等的符號引用可以解析為其在記憶體中的直接引用了,而不一定非要等到真正運行時才進行解析。 
在Student.java中只有一個靜態欄位:

一個類載入之前要載入它的父類及其實現的介面

直到第390行才看到自己定義的部分被載入,先是Student實現的介面IStudyable,然後是其父類Person,然後才是Student自身,然後是一個啟動類的載入,然後就是找到main()方法,執行了。 運行位元組碼指令

執行引擎找到main()這個入口方法,執行其中的位元組碼指令: 
只有當前正在啟動並執行方法的棧幀位於棧頂,當前方法返回,則當前方法對應的棧幀出棧,當前方法的調用者的棧幀變為棧頂;當前方法的方法體中若是調用了其他方法,則為被調用的方法建立棧幀,並將其壓入棧頂。

簡單查看Student.main()的運行過程:

   public static void main(String[] args){              Student s = new Student(23,"dqrcsc","20150723");              s.study(5,6);              Student.getCnt();              s.run();}
1 2 3 4 5 6 7 8 9 10 11

 
Mximum stack depth指定當前方法即main()方法對應棧幀中的運算元棧的最大深度,當前值為5

Maximum local variables指定main()方法中局部變數表的大小,當前為2,及有兩個slot用於存放方法的參數及局部變數。

Code length指定main()方法中代碼的長度。 
執行過程如下:

1,為main方法建立棧幀:

局部變數表長度為2,slot0存放參數args,slot1存放局部變數Student s,運算元棧最大深度為5。

2,new#7指令,在java堆中建立一個Student對象,並將其引用值放入棧頂。

3,初始化一個對象(通過執行個體構造的方式)

up指令:複製棧頂的值,然後將複製的結果入棧。

bipush 23:將單位元組常量值23入棧。

ldc #8:將#8這個常量池中的常量即”dqrcsc”取出,併入棧。

ldc #9:將#9這個常量池中的常量即”20150723”取出,併入棧。

4,invokespecial #10:調用#10這個常量所代表的方法,即Student.()這個方法,這步是為了初始化對象s的各項值

<init>()方法,是編譯器將調用父類的<init>()的語句、構造代碼塊、執行個體欄位指派陳述式,以及自己編寫的構造方法中的語句整合在一起產生的一個方法。保證調用父類的<init>()方法在最開頭,自己編寫的構造方法語句在最後,而構造代碼塊及執行個體欄位指派陳述式按出現的順序按序整合到<init>()方法中。

注意到Student.<init>()方法的最大運算元棧深度為3,局部變數表大小為4。

此時需注意:從dup到ldc #9這四條指令向棧中添加了4個資料,而Student.()方法剛好也需要4個參數:

public Student(int age, String name, String sid){              super(age,name);              this.sid = sid;}
1 2 3 4 5 6 7

雖然定義中只顯式地定義了傳入3個參數,而實際上會隱含傳入一個當前對象的引用作為第一個參數,所以四個參數依次為this,age,name,sid。

上面的4條指令剛好把這四個參數的值依次入棧,進行參數傳遞,然後調用了Student.<init>()方法,會建立該方法的棧幀,併入棧。棧幀中的局部變數表的第0到4個slot分別儲存著入棧的那四個參數值。

建立Studet.<init>()方法的棧幀:

Student.<init>()方法中的位元組碼指令:

 
aload_0:將局部變數表slot0處的引用值入棧

aload_1:將局部變數表slot1處的int值入棧

aload_2:將局部變數表slot2處的引用值入棧

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.