JVM-内存模型
我在寫程序的時候想過的以下這些問題,不知道大家是不是都是這樣:
1. 類是怎么加載的,存儲在哪里?類的對象存儲在哪里,類和類對象怎么關聯、對應的?
2. 方法存在哪里?子類繼承父類之后覆蓋父類的方法之后JVM什么機制執行子類or父類方法?
3.?static變量和普通變量是放著一起嗎?
4.?為什么共享變量要做線程同步控制,而函數內變量不用?
5.?JVM什么時候會做內存回收,怎么收的?
6. JVM怎么實現平臺無關性的?做到一處編寫,到處運行的?
下面就是我找了很多資料,寫了點程序搞明白這些問題,可能一篇文件寫不完,會寫很多,最終一定是把所有問題都搞清楚。
1. Java類的編譯過程
? ?機器碼:是機器語言的指令集體系結構的表示方式。好比"加"在匯編中用add表示,類似的在這個中則是用1100表示(1100只是舉例用,實際不是),操作系統看到1100就會執行add指令。
? ?Java字節碼:相信應該很多人都聽過字節碼,JAVA不同于c/c++直接編譯成機器碼,而是編譯成字節碼,字節碼是平臺無關的,它是介于Java和機器語言的中間語言,JAVA字節碼是Java代碼部署的最小單元。這也就解釋了上面的第6個問題,Java如何實現平臺無關性的,編寫好的Java字節碼如果在window平臺,window平臺的JVM(準確說是JRE)會將字節碼最后翻譯成符合window平臺的機器碼,在linux平臺JVM(準確說是JRE)會將字節碼最后翻譯成符合linux平臺的機器碼。
? ?字節碼長什么樣呢?我寫了段小程序,如下所示:
class Test{public static void main(String[] args){Apple apple = new Apple("wo");apple.eat("jige");} }class Apple{private String name;public Apple(String name){this.name = name;}public void eat(String who){System.out.println(who +" eat " + name + "' apple");} }java命令編譯后的字節碼如下:
cafe babe 0000 0034 000f 0a00 0300 0c07 000d 0700 0e01 0006 3c69 6e69 743e 0100 0328 2956 0100 0443 6f64 6501 000f 4c69 6e65 4e75 6d62 6572 5461 626c 6501 0004 6d61 696e 0100 1628 5b4c 6a61 7661 2f6c 616e 672f 5374 7269 6e67 3b29 5601 000a 536f 7572 6365 4669 6c65 0100 0954 6573 742e 6a61 7661 0c00 0400 0501 0004 5465 7374 0100 106a 6176 612f 6c61 6e67 2f4f 626a 6563 7400 2000 0200 0300 0000 0000 0200 0000 0400 0500 0100 0600 0000 1d00 0100 0100 0000 052a b700 01b1 0000 0001 0007 0000 0006 0001 0000 0001 0009 0008 0009 0001 0006 0000 002d 0002 0004 0000 0009 043c 053d 1b1c 603e b100 0000 0100 0700 0000 1200 0400 0000 0300 0200 0400 0400 0500 0800 0600 0100 0a00 0000 0200 0b詐一看還以為是機器碼,但是不是,我們通過javap -c查看匯編語言,如下圖所示:
class Test {Test();Code:0: aload_01: invokespecial #1 // Method java/lang/Object."<init>":()V4: returnpublic static void main(java.lang.String[]);Code:0: new #2 // class Apple3: dup4: ldc #3 // String wo6: invokespecial #4 // Method Apple."<init>":(Ljava/lang/String;)V9: astore_110: aload_111: ldc #5 // String jige13: invokevirtual #6 // Method Apple.eat:(Ljava/lang/String;)V16: return }以上就是平臺無關性的字節碼,Java 字節碼指令集包括操作碼和操作數,上面的指令就是JVM編譯指令。
2. 類加載及運行過程
? 1. Test類加載:在編譯好在命令行敲java Test,系統會啟動一個jvm進程,jvm從classpath類Test所在路徑中找到Test.class字節碼文件,將Java的類信息加載到運行時數據區的方法區中
? 2. 執行main:jvm找到Test的main方法,開始執行;
? 3. main方法執行?new Apple時,JVM發現沒有Apple類信息,所以JVM馬上加載Apple類,在Apple類信息放到方法區,初始化Apple類對象(Class extend Object)。
? 4. 加載完Apple類之后,JVM開始在堆上為Apple對象分配內存,然后調用構造函數初始化Apple實例,這個Apple實例持有指向方法區的Apple類的類型信息的引用(方法表,java動態綁定的實現)。
?5. 當調用apple.eat("jige") 的時候,JVM會根據apple引用找到Apple對象,然后在Apple對象中引用找到方法區Apple類的方法表,獲取eat() 函數的字節碼的地址,在線程私有的線程棧中push棧幀,運行。
這個流程回答了上面第1個問題。
3. Java內存模型
? ?1. Java虛擬機在執行Java程序時會把內存分成若干個不同的數據區域,如下圖所示,內存分成線程私有(紅色)的和線程共有的(綠色)。
? 2. 程序計數器
? ? ? 程序計數器(Program Counter Register)是一塊較小的內存空間,它的作用可以看做是當前線程所執行的字節碼的行號指示器。在虛擬機的概念模型里(僅是概念模型,各種虛擬機可能會通過一些更高效的方式去實現),字節碼解釋器工作時就是通過改變這個計數器的值來選取下一條需要執行的字節碼指令,分支、循環、跳轉、異常處理、線程恢復等基礎功能都需要依賴這個計數器來完成。由于Java?虛擬機的多線程是通過線程輪流切換并分配處理器執行時間的方式來實現的,在任何一個確定的時刻,一個處理器(對于多核處理器來說是一個內核)只會執行一條線程中的指令。因此,為了線程切換后能恢復到正確的執行位置,每條線程都需要有一個獨立的程序計數器,各條線程之間的計數器互不影響,獨立存儲,我們稱這類內存區域為“線程私有”的內存。如果線程正在執行的是一個Java?方法,這個計數器記錄的是正在執行的虛擬機字節碼指令的地址;如果正在執行的是Natvie?方法,這個計數器值則為空(Undefined)。此內存區域是唯一一個在Java?虛擬機規范中沒有規定任何OutOfMemoryError?情況的區域。JVM的程序計數器和機器語言的PC程序計數器是不一樣的,PC程序計數器是CPU中的寄存器,保存的是程序下一條指令的地址。
? 3.? Java虛擬機棧
與程序計數器一樣,Java虛擬機棧(Java Virtual Machine Stacks)也是線程私有的,我覺得應該換個名,應該叫做線程棧,所以實際上是有多少個線程,就有多少個虛擬機棧,虛擬機棧生命周期和線程相同,線程產生時,虛擬機棧隨之產生,線程銷毀時,虛擬機棧也會銷毀。每個方法被執行時,同時會創建一個棧幀(Stack Frame),用于存儲局部變量表、操作數棧、指向當前方法所屬類的運行時常量池(方法區)的引用,方法返回地址push到棧中,執行完畢之后pop出棧。
? ? ?3.1 局部變量表:用來存儲函數中的局部變量,包括函數中定義的非靜態變量和函數形參,基本變量直接存值,引用類型存儲指向對象的引用,局部變量表的大小在編譯器就可以確定其大小了,因此在程序執行期間局部變量表的大小是不會改變的。
? ? ?3.2 操作數棧:一個線程執行方法的過程中,實際上就是不斷執行語句的過程,而歸根到底就是進行計算的過程。因此可以這么說,程序中的所有計算過程都是在借助于操作數棧來完成的。
? 3.3 指向運行時常量池的引用,因為在方法執行的過程中有可能需要用到類中的常量,所以必須要有一個引用指向運行時常量。
? 3.4?方法返回地: 當一個方法執行完畢之后,要返回之前調用它的地方,因此在棧幀中必須保存一個方法返回地址。
4.? ?本地方法棧
本地方法棧與Java棧的作用和原理非常相似。區別只不過是Java棧是為執行Java方法服務的,而本地方法棧則是為執行本地方法(Native Method)服務的。在JVM規范中,并沒有對本地方發展的具體實現方法以及數據結構作強制規定,虛擬機可以自由實現它。在HotSopt虛擬機中直接就把本地方法棧和Java棧合二為一。也就是說本地方法也會創建棧幀,押棧出棧,線程私有。
5. 堆
在C語言中,堆這部分空間是唯一一個程序員可以管理的內存區域。程序員可以通過malloc函數和free函數在堆上申請和釋放空間。那么在Java中是怎么樣的呢?
Java中的堆是用來存儲對象本身的以及數組(當然,數組引用是存放在Java棧中的)。只不過和C語言中的不同,在Java中,程序員基本不用去關心空間釋放的問題,Java的垃圾回收機制會自動進行處理。因此這部分空間也是Java垃圾收集器管理的主要區域。另外,堆是被所有線程共享的,在JVM中只有一個堆。
6.? 方法區
方法區是JVM中非常非常重要的區域,取名為方法區我覺得不是很恰當,應該存放的內容是以Class為單元的,存放了每個類的信息(類名、方法信息、字段信息)、運行時常量池(靜態變量、常量、字面量和符號引用),編譯后的代碼。
? ? ?在JVM規范中,沒有強制要求方法區必須實現垃圾回收。很多人習慣將方法區稱為“永久代”,是因為HotSpot虛擬機以永久代來實現方法區,從而JVM的垃圾收集器可以像管理堆區一樣管理這部分區域,從而不需要專門為這部分設計垃圾回收機制。不過自從JDK7之后,Hotspot虛擬機便將運行時常量池從永久代移除了。
?
轉載于:https://www.cnblogs.com/zhengwangzw/p/9384039.html
總結
- 上一篇: POJ-3281 Dining 网络流最
- 下一篇: (1)编译安装lamp三部曲之apach