為什么Java能夠跨平臺
是因為Java程序編譯之后的代碼不是能夠被硬件系統(tǒng)直接運行的代碼,而是一種"中間碼"-字節(jié)碼, 然后不同的的硬件平臺上安裝有不同的Java虛擬機(JVM),有JVM來把字節(jié)碼再翻譯成對應(yīng)的硬件平臺能夠執(zhí)行的代碼,所以Java是夸平臺的。
Java虛擬機及內(nèi)存分區(qū)
1、 jvm簡介
Java虛擬機(Java Virtual Machine 簡稱JVM)是運行所有Java程序的抽象計算機,是Java語言的運行環(huán)境。Java虛擬機有自己完善的硬體架構(gòu),如處理器、堆棧、寄存器等,還具有相應(yīng)的指令系統(tǒng)。JVM屏蔽了與具體操作系統(tǒng)平臺相關(guān)的信息,使得Java程序只需生成在Java虛擬機上運行的目標(biāo)代碼(字節(jié)碼),就可以在多種平臺上不加修改地運行。
如下圖所示,JVM體系中包含了幾個主要的子系統(tǒng)和內(nèi)存區(qū)
垃圾回收器(Garbage Collection):負責(zé)回收堆內(nèi)存(Heap)中已經(jīng)不再使用的對象,即這些對象已經(jīng)沒有被引用了。
類裝載子系統(tǒng)(Classloader Sub-System):除了要定位和導(dǎo)入二進制class文件外,還必須負責(zé)驗證被導(dǎo)入類的正確性,為類變量分配并初始化內(nèi)存,以及幫助解析符號引用。
執(zhí)行引擎(Execution Engine):負責(zé)執(zhí)行那些包含在被裝載類的方法中的指令。
** 運行時數(shù)據(jù)區(qū)(Java Memory Allocation Area):**又叫虛擬機內(nèi)存或者Java內(nèi)存,虛擬機運行時需要從整個計算機內(nèi)存劃分一塊內(nèi)存區(qū)域存儲許多東西。例如:字節(jié)碼、從已裝載的class文件中得到的其他信息、程序創(chuàng)建的對象、傳遞給方法的參數(shù),返回值、局部變量等等。

2、內(nèi)存分區(qū)
從上節(jié)知道,運行時數(shù)據(jù)區(qū)即是java內(nèi)存,而且數(shù)據(jù)區(qū)要存儲的東西比較多,如果不對這塊內(nèi)存區(qū)域進行劃分管理,會顯得比較雜亂無章。根據(jù)存儲數(shù)據(jù)的不同,java內(nèi)存通常被劃分為5個區(qū)域:程序計數(shù)器(Program Count Register)、本地方法棧(Native Stack)、方法區(qū)(Methon Area)、棧(Stack)、堆(Heap)。
程序計數(shù)器(Program Count Register):又叫程序寄存器。JVM支持多個線程同時運行,當(dāng)每一個新線程被創(chuàng)建時,它都將得到它自己的PC寄存器(程序計數(shù)器)。如果線程正在執(zhí)行的是一個Java方法(非native),那么PC寄存器的值將總是指向下一條將被執(zhí)行的指令,如果方法是 native的,程序計數(shù)器寄存器的值不會被定義。 JVM的程序計數(shù)器寄存器的寬度足夠保證可以持有一個返回地址或者native的指針。
方法區(qū)(Method Area):當(dāng)虛擬機裝載一個class文件時,它會從這個class文件包含的二進制數(shù)據(jù)中解析類型信息,然后把這些類型信息(包括類信息、常量、靜態(tài)變量等)放到方法區(qū)中,該內(nèi)存區(qū)域被所有線程共享。雖然JVM規(guī)范把方法區(qū)描述為堆得一個邏輯部分,但是他有一個別名叫Non-heap(非堆),目的應(yīng)該是與Java堆區(qū)分開。
方法區(qū)用于存儲已經(jīng)被虛擬機加載的類信息、常量、靜態(tài)變量、即時編譯器編譯后的代碼等數(shù)據(jù)。
堆(Heap):Java堆(Java Heap)是Java虛擬機所管理的內(nèi)存中最大的一塊。Java堆是被所有線程共享的一塊內(nèi)存區(qū)域。在此區(qū)域的唯一目的就是存放對象實例,幾乎所有的對象實例都是在這里分配內(nèi)存,但是這個對象的引用卻是在棧(Stack)中分配。
注:jvm只有一個堆區(qū)(heap)被所有線程共享,堆中不存放基本類型和對象引用,只存放對象本身
棧(Stack):又叫堆棧。JVM為每個新創(chuàng)建的線程都分配一個棧。也就是說,對于一個Java程序來說,它的運行就是通過對棧的操作來完成的。棧以幀為單位保存線程的狀態(tài)。JVM對棧只進行兩種操作:以幀為單位的壓棧和出棧操作。我們知道,某個線程正在執(zhí)行的方法稱為此線程的當(dāng)前方法。我們可能不知道,當(dāng)前方法使用的幀稱為當(dāng)前幀。當(dāng)線程激活一個Java方法,JVM就會在線程的 Java堆棧里新壓入一個幀,這個幀自然成為了當(dāng)前幀。在此方法執(zhí)行期間,這個幀將用來保存參數(shù)、局部變量、中間計算過程和其他數(shù)據(jù)。從Java的這種分配機制來看,堆棧又可以這樣理解:棧(Stack)是操作系統(tǒng)在建立某個進程時或者線程(在支持多線程的操作系統(tǒng)中是線程)為這個線程建立的存儲區(qū)域,該區(qū)域具有先進后出的特性。其相關(guān)設(shè)置參數(shù):
-Xss --設(shè)置方法棧的最大值
1.每個線程包含一個棧區(qū),棧中只保存基礎(chǔ)數(shù)據(jù)類型的對象和自定義對象的引用(不是對象),對象都存放在堆區(qū)中
2.每個棧中的數(shù)據(jù)(原始類型和對象引用)都是私有的,其他棧不能訪問。
3.棧分為3個部分:基本類型變量區(qū)、執(zhí)行環(huán)境上下文、操作指令區(qū)(存放操作指令)。
本地方法棧(Native Stack):存儲本地方方法的調(diào)用狀態(tài)。
JAVA虛擬機有一條在堆中分配新對象的指令,卻沒有釋放內(nèi)存的指令,正如你無法用Java代碼區(qū)明確釋放一個對象一樣。虛擬機自己負責(zé)決定如何以及何時釋放不再被運行的程序引用的對象所占據(jù)的內(nèi)存,通常,虛擬機把這個任務(wù)交給垃圾收集器(Garbage Collection)。其相關(guān)設(shè)置參數(shù):
-Xms -- 設(shè)置堆內(nèi)存初始大小
-Xmx -- 設(shè)置堆內(nèi)存最大值
-XX:MaxTenuringThreshold -- 設(shè)置對象在新生代中存活的次數(shù)
-XX:PretenureSizeThreshold -- 設(shè)置超過指定大小的大對象直接分配在舊生代中
3、類加載過程
啟動一個Java程序時,會通過Javac編譯程序調(diào)用Java啟動JVM(編譯過程沒有深入研究)
類加載過程其實就是JVM把class文件加載到內(nèi)存,并對數(shù)據(jù)進行校驗,解析和初始化,最終形成JVM可以直接使用Java的過程。總共可以分為加載、鏈接、初始化三部。
- 加載
將class文件字節(jié)碼內(nèi)容加載到內(nèi)存中,并將這些靜態(tài)數(shù)據(jù)轉(zhuǎn)換成方法區(qū)中的運行時2進制數(shù)據(jù)結(jié)構(gòu)。在堆中生成一個代表這個類的Java.lang.Class對象,作為方法區(qū)類數(shù)據(jù)的訪問入口。 - 鏈接
將Java類的二進制代碼合并到JVM的運行狀態(tài)之中的過程,鏈接有分為三個小步
1、驗證
確保加載的類的信息符合JVM規(guī)范,有沒有安全方面的問題
2、準(zhǔn)備
正式為類變量(static變量)分配內(nèi)存并設(shè)置類變量初始值的階段,這些內(nèi)存都將在方法去中分配
3、解析
虛擬機常量池內(nèi)的符號引用替換為直接引用的過程 - 初始化
初始化階段是執(zhí)行類構(gòu)造器<clinit>()方法的過程,類構(gòu)造器<clinit>()方法是由編譯器自動收集類中的所有類變量的賦值動作和靜態(tài)語句塊(static塊)中的語句合并產(chǎn)生的。
當(dāng)初始化一個類的時候,如果發(fā)現(xiàn)其父類還沒有進行過初始化,則需要先對其父類初始化
虛擬機會保證一個類的<clinit>()方法在多線程環(huán)境中被正確的加鎖和同步
當(dāng)訪問一個類的靜態(tài)域時,只有真正聲明這個域的類才會被初始化。
下面通過具體的例子來分析下類加載過程
public class AppMain // 運行時, jvm 把appmain的信息都放入方法區(qū)
{
public static void main(String[] args) // main 方法本身放入方法區(qū)。
{
Sample test1 = new Sample(" 測試1 "); // test1是引用,所以放到棧區(qū)里,
// Sample是自定義對象應(yīng)該放到堆里面
Sample test2 = new Sample(" 測試2 ");
test1.printName();
test2.printName();
}
}
類sample
public class Sample // 運行時, jvm 把appmain的信息都放入方法區(qū)
{
/** 范例名稱 */
private String name; // new Sample實例后, name 引用放入棧區(qū)里, name 對象放入堆里
/** 構(gòu)造方法 */
public Sample(String name) {
this.name = name;
}
/** 輸出 */
public void printName() // print方法本身放入 方法區(qū)里。
{
System.out.println(name);
}
}

如上圖所示,系統(tǒng)收到我們發(fā)出的指令后,啟動一個Java虛擬機進程,這個進程首先從classpath中找到AppMain.class文件,讀取這個文件中的二進制數(shù)據(jù),然后把APPMain類的類信息存放到運行時數(shù)據(jù)區(qū)的方法區(qū)中,然后在棧里面執(zhí)行main()方法。
如果想深入理解Java虛擬機可以閱讀深入理解Java虛擬機這本書