Java--JVM詳解
java 整體執行結構以及JVM的結構
java的源程式 --》編譯(位元組碼)JDK的功能 ----》JVM解釋這個位元組碼 怎麼找到的呢?
classLaoder --->java程式的執行JVM環境 執行引擎--》可移植性 JNI(本地方法介面)---》本地函式庫
jdk1.8以後
依然是雙親載入機制不可改變
bootstrap 載入器
新版本的類載入器已經發生了變化的為AppClassLoader
執行時的資料區是整個JVm的關鍵所在,
執行時資料區域組成;
方法區:
棧 java的引用傳遞依靠的是堆記憶體,同一塊堆記憶體空間可以被不同的棧記憶體所指向的。
堆 程式執行的單位,裡面儲存的資訊都是與當前執行緒相關的內容,包括區域性變數,程式的執行狀態,方法的返回值
程式的計數器 是一個非常小的空間,物件的晉級問題(依靠的就是計數器)
本地方法棧 在進行遞迴呼叫的時候,儲存棧幀內容(區域性變量表,運算元棧 當前方法所屬類的執行時的常量引用,返回地址)
JVm的關鍵部分是討論堆記憶體的優化。既然要進行優化,必須清楚java的物件訪問方式。
java進行物件引用的時候,並沒有使用到控制代碼的概念,它直接採用的是hotspot的虛擬機器標準的指標引用。
java是一個開源的程式語言,實際上有三個所謂的虛擬機器標準,:
SUN 標準的hotSpot虛擬機器
JRockeit 虛擬機器
IBM :J9
mixed mode 混合模式
java -Xcomp 改變所謂的JVm的執行模式
當前的java行業不在適合客戶端的程式開發了。
預設的jdk配置是server模式
優化的關鍵就是堆記憶體的空間的控制
堆記憶體就會產生大量的垃圾,我們需要考慮關於Gc的問題,真正導致我們程式變慢的原因就是堆的控制的策略上,控制我們的回收策略
jdk1.9 預設策略是G1回收器。
jdk1.8 之後
堆記憶體的空間 劃分為年輕代 (eden伊甸園區, survivor1 survivor2) 老年代 元空間(實體記憶體)這是新的架構
在1.8之前的
堆記憶體的空間 劃分為年輕代 (eden伊甸園區, survivor1 survivor2) 老年代 永久區(方法區)
堆記憶體組織結構以及記憶體有關的
年輕代:
伊甸園:新生的小物件。每當使用關鍵字new的時候,預設的話都在此空間進行物件的建立。
如果建立的物件過多,那麼最終的結果造成伊甸園的記憶體空間佔滿,此時發生晉升的操作(若干次MinorGC執行還保留的物件,晉升到存活區)
存活區(survivor): 進行一些Gc後儲存的物件。s1 s2 有一個是空的,是向老年代晉升。
老年代物件:這些物件都已經經歷了無數次的GC之後依然被保留下來的物件,很難被清除
對於一個很大物件直接儲存在老年代的。 如果現在一個老年代的不足,出現FullGC
也是我們最核心的問題,堆的結構優化,每一塊空間都是有一個伸縮區
年輕代到老年代之間具有伸縮區
伸縮區的考慮在某個記憶體空間不足的時候,會自動開啟伸縮區擴大記憶體,當發現當前的區域記憶體可以,滿足要求的時候,就可以進行收縮了。
如果不進行收縮的話優點是:可以提升堆記憶體的結構優化。
如果不進行收縮的缺點:空間太大了。那麼沒有選擇合適的GC演算法,就會造成堆記憶體的效能下降。
package com.jvm.test;
public class TestGCDemo {
public static void main(String[] args) {
Runtime run = Runtime.getRuntime(); //單例物件
System.out.println("max_MEmory" + run.maxMemory()); //當前程式的最大記憶體
System.out.println("total_MEmory" + run.totalMemory()); //預設的可用記憶體
maxMemory 實際上當前可用的是等於當前實體記憶體的1/4
totalMemory是當前實體記憶體的1/64
剩下的都是伸縮區的空間,這麼大的伸縮區的空間,所以在進行堆記憶體分配的過程中裡面當前使用者訪問量增加的時候就一定會導致不斷的判斷空間是否充足。
不斷的進行記憶體空間的增長,不斷進行記憶體空間的收縮和釋放
-Xms: 設定初始化的記憶體空間大小
-Xmx:設定最大的可用記憶體空間。 這兩個引數很重要
怎麼使用這兩個引數 ? 可以使用的單位 KB MB
可以減少堆記憶體的收縮處理操作,這兩個引數針對整個堆記憶體的。
-Xms2g -Xms2g
當堆記憶體空間很大的情況下,據需要考慮到Gc執行的效率了。所以我們說呢在這個環節裡面我們考慮兩個技術名詞:
BTP(年輕代)、TLAB
年輕代優化的結構:在伊甸園是採用棧的形式將建立的物件放到棧頂的位置,計算每一個物件的長度,當空間不足的時候,MinorGC
TLAB 技術(分塊儲存)多塊的空間多執行緒處理操作,
-Xmn: 設定年輕代的空間大小預設是實體記憶體的1/64; 這個建議不要調
-Xss:設定每一個執行緒的棧空間,
-X:SurvivorRator: 設定伊甸園與兩個存活區的記憶體分配比,預設 8:1:1
老年代:
與年輕代的比率-XX:NewRatio
當物件很大的時候,往往不在年輕代儲存,直接進入老年代儲存,有個引數"-XX: PretenureSizeThreshold"
分水嶺:jdk1.8取消了永久代,變成了元空間 ,直接利用實體記憶體了。
}
}
gc演算法
演算法的選擇決定了程式的執行效能。
傳統意義上的回收處理操作,只是認為有了垃圾產生,而後自動進行GC操作(MinorGC, FullGC),或者手工利用System.gc()操作(MIniorGc, FullGc)
java的GC機制,經歷了20年的發展,對於電腦的硬體技術產生了很大的變化,最初在一塊cpu上進行多執行緒分配,現在多核cpu,多執行緒支援,
對於GC演算法裡面就需要考慮不同的記憶體分代:
年輕代GC策略: 序列GC 並行GC
老年代GC策略:序列GC 並行GC
【年輕代序列GC的操作流程】
掃描年輕代的所有存活的物件:
使用MinorGC進行垃圾回收,同時將還能夠存活下來的物件儲存到存活區(s0, s1)裡面;
每一次進行MinorGC的時候,都會引起s0和s1的交換,
經過若干次MinorGc還能夠儲存下來的進入到老年代。
【年輕代並行Gc流程】
演算法:複製-清理演算法,在掃描和複製的時候均採用了多執行緒的處理模式來完成。
在年輕代進行MinorGc的時候實際上也可能觸發到老年代的Gc操作。
CMS引出老年代的操作的問題 缺點在第一次和重新標記的時候,影響會比較小,但是在後續的併發回收中會產生記憶體碎片
【老年代的並行】
在最早的時候主要是使用了此種GC演算法,但是這種演算法會有一個嚴重的問題:STW(產生中斷,因為需要進行垃圾回收的標記)。
-- 暫停當前的所有執行的程式(掛起)
-- 標記出垃圾,標記的時間越長,那麼掛起的時間越長,如果此時堆記憶體很大的話,那麼時間一定會更長;
-- 預清除處理;
-- 重新標記過程,看看還有沒有垃圾;
-- 進行垃圾的處理;
--程式的恢復執行
[【老年代序列GC】
演算法:標記-清除-壓縮(碎片整理)
掃面老年代的存活物件。進行標記物件;
遍歷老年代的儲存空間,回收標記的物件,
還需要進行老年代的記憶體壓縮,也就是所謂的碎片整理(把空間都整理到一塊去)
程式設定引數; -Xms48m -Xmx48m -XX:+PrintGCDetails
-Xms48m -Xmx48m -XX:+PrintGCDetails
-Xms48m -Xmx48m -Xlog:gc* -XX:+UseSerialGC:(手工式的修改GC)
不用序列GC -Xms48m -Xmx48m -Xlog:gc* -XX:+UseSerialGC
不用並行GC -Xms48m -Xmx48m -Xlog:gc* -XX:+UseParallelGC
並行老年代 -Xms48m -Xmx48m -Xlog:gc* -XX:+UserParallelOldGC
最終的Gc 不在使用上面的演算法 上面的序列並行演算法會影響程式的效能, 最初的電腦沒有這麼高的配置,記憶體是K為單位的,
現在的電腦配值,jdk9開始是G1演算法
準備多塊的記憶體空間 在其中的某一塊上面執行Gc演算法 G1支援的最大記憶體是64GB, 每一個小的區域裡面可以設定的範圍1-32MB
G1收集:-Xms48m -Xmx48m -Xlog:gc* -XX:+UseG1GC
JVM核心優化的問題 :減少伸縮區的使用 提升GC的效率 ,G1是現在最好用的GC演算法, 不過這個裡面還是在每一塊子空間上面結合之前的序列和並行的演算法的。
執行緒池的棧的大小的配置;
執行緒池的配置
在tomcat下面tomcat優化
在tomcat的bin目錄下面;找到catalina.sh
CMS適合記憶體小的 G1的主要特徵是可以進行一個大記憶體的使用。