JAVA能夠實現跨平台的一個根本原因,是定義了class文件的格式標准,凡是實現該標准的JVM都能夠加載並解釋該class文件,據此也可以知道,為啥Java語言的執行速度比C/C++語言執行的速度要慢了,當然原因肯定不止這一個,如在JVM中沒有數據寄存器,指令集使用的是棧來保存中間數據...等,盡管Java的貢獻者們為執行速度的提高想了各種辦法,如JIT、動態編譯器等,以下是Leetcode中一道題目用不同的語言實現時的執行性能對比圖...
以下是JVM的一個基本架構圖,在這個基本架構圖中,棧有兩部份,Java線程棧以及本地方法棧,棧的概念與C/C++程序基本上都是一個概念,裡面存放的都是棧幀,一個棧幀代表的就是一個函數的調用,在棧幀裡面存放了函數的形參,函數的局部變量, 返回地址等,但是與C/C++的一個重要區別是,C/C++裡面有傳值以及傳址的區別,當傳的是一個對象時( 結構體也可以當成對象,其實就是對象~,只不過裡面的方法默認都是public的,不信你可以試試,在結構體中加一個函數,編譯器也不會報錯,程序依舊運行~~~),會將對象復到到棧中,而Java中只有基本類型才是傳值的,其他類型傳的都是引用,什麼是引用,學過C/C++的就把引用當作指針理解吧~~~,在這個基本架構圖中,可以看出JVM還定義了一個本地方法棧,本地方法棧是為Java調用本地方法【這些本地方法是由其他語言編寫的】服務的
上面的圖中看到的是JVM中棧有兩個,但是堆只有一個,每一個線程都有自已的線程棧【線程棧的大小可以通過設置JVM的-xss參數進行配置,32位系統下,一般默認的大小是512K】,線程棧裡面的數據屬於該線程私有,但是所有的線程都共享一個堆空間,堆中存放的是對象數據,什麼是對象數據,排除法,排除基本類型以及引用類型以外的數據都將放在堆空間中,下面來具體分析一下堆空間...
在JVM中堆空間劃分如下圖所示
上圖中,刻畫了Java程序運行時的堆空間,可以簡述成如下2條
1.JVM中堆空間可以分成三個大區,新生代、老年代、永久代
2.新生代可以劃分為三個區,Eden區,兩個幸存區
在JVM運行時,可以通過配置以下參數改變整個JVM堆的配置比例
1.JVM運行時堆的大小
-Xms堆的最小值
-Xmx堆空間的最大值
2.新生代堆空間大小調整
-XX:NewSize新生代的最小值
-XX:MaxNewSize新生代的最大值
-XX:NewRatio設置新生代與老年代在堆空間的大小
-XX:SurvivorRatio新生代中Eden所占區域的大小
3.永久代大小調整
-XX:MaxPermSize
4.其他
-XX:MaxTenuringThreshold,設置將新生代對象轉到老年代時需要經過多少次垃圾回收,但是仍然沒有被回收
在上面的配置中,老年代所占空間的大小是由-XX:SurvivorRatio這個參數進行配置的,看完了上面的JVM堆空間分配圖,可能會奇怪,為啥新生代空間要劃分為三個區Eden及兩個Survivor區?有何用意?為什麼要這麼分?要理解這個問題,就得理解一下JVM的垃圾收集機制(復制算法也叫copy算法),步驟如下:
復制(Copying)算法
將內存平均分成A、B兩塊,算法過程:
1. 新生對象被分配到A塊中未使用的內存當中。當A塊的內存用完了, 把A塊的存活對象對象復制到B塊。
2. 清理A塊所有對象。
3. 新生對象被分配的B塊中未使用的內存當中。當B塊的內存用完了, 把B塊的存活對象對象復制到A塊。
4. 清理B塊所有對象。
5. goto 1。
優點:簡單高效。缺點:內存代價高,有效內存為占用內存的一半。
圖解說明如下所示:(圖中後觀是一個循環過程)
對復制算法進一步優化:使用Eden/S0/S1三個分區
平均分成A/B塊太浪費內存,采用Eden/S0/S1三個區更合理,空間比例為Eden:S0:S1==8:1:1,有效內存(即可分配新生對象的內存)是總內存的9/10。
算法過程:
1. Eden+S0可分配新生對象;
2. 對Eden+S0進行垃圾收集,存活對象復制到S1。清理Eden+S0。一次新生代GC結束。
3. Eden+S1可分配新生對象;
4. 對Eden+S1進行垃圾收集,存活對象復制到S0。清理Eden+S1。二次新生代GC結束。
5. goto 1。
默認Eden:S0:S1=8:1:1,因此,新生代中可以使用的內存空間大小占用新生代的9/10,那麼有人就會問,為什麼不直接分成兩個區,一個區占9/10,另一個區占1/10,這樣做的原因大概有以下幾種
1.S0與S1的區間明顯較小,有效新生代空間為Eden+S0/S1,因此有效空間就大,增加了內存使用率
2.有利於對象代的計算,當一個對象在S0/S1中達到設置的XX:MaxTenuringThreshold值後,會將其分到老年代中,設想一下,如果沒有S0/S1,直接分成兩個區,該如何計算對象經過了多少次GC還沒被釋放,你可能會說,在對象裡加一個計數器記錄經過的GC次數,或者存在一張映射表記錄對象和GC次數的關系,是的,可以,但是這樣的話,會掃描整個新生代中的對象, 有了S0/S1我們就可以用S0/S1記錄對象經過的代數【當然這取決於具體的Java虛擬機的實現了】~~~