1. jvm - 常用調優啟動參數配置
可以看到堆內存為2G,新生代為768M老年代為1280M,新生代採用ParNew收集器
-XX:+UseConcMarkSweepGC:新生代使用ParNew回收器,老年代使用CMS
線程棧為512k(默認1024k調小可以增加創建線程數,增加並發量)
同時列印 GC 詳細信息和列印 GC 發生時間,當發生OOM時,Dump文件到指定路徑
棧空間參數設置
-Xss: 設置線程的最大銀山碰棧空間,棧空間越大,方法的遞歸深度越大
方法區參數設置(方法區大小的參數設置跟jdk版本相關)
jdk1.6,jdk1.7設置方法區永久代的大小
-XX:PermSize=5M
-XX:MaxPermSize=5M 最大永久代的大小默認是64M
jdk1.8及以上,永久代被移除,取而代之的是元數據區,元數據區是一塊堆外的直接內存
如果不指定大小,那麼會耗盡所有可用的系統內存
-XX:MaxMetaspaceSize=60M 設置最大元數據的大小
堆設置
-Xms:初始堆大小
-Xmx:最大堆大小
-Xmn:設置新生代大小,設置較大的新生代大小會減小老年代大小,新生代的大小一般為堆空間的1/3
-XX:NewSize=n: 設置年輕代大小
-XX:NewRatio=n:設置年輕代和年老代的比值(老年代/新生代)。如:為3,表示年輕代與年老代比值為1:3
年輕代占整個年輕代年老代和的1/4
-XX:SurvivorRatio=n:年輕代中Eden區與兩個Survivor區的比值。注意Survivor區有兩個。
-Xmn,-XX:NewSize/-XX:MaxNewSize,-XX:NewRatio 3組參數都可以影響年輕代的大小,混合使用的情況下,優先順序是什麼?
1.高優先順序:-XX:NewSize/-XX:MaxNewSize
2.中優先順序:-Xmn(默認等效 -Xmn=-XX:NewSize=-XX:MaxNewSize=?)
3.低優先順序:-XX:NewRatio
推薦使用-Xmn參數,原因是這個參數簡潔,相當於一次設定 NewSize/MaxNewSIze而且兩者相等,適用於生產環境。
-Xmn 配合 -Xms/-Xmx,即可將堆內存布局完成 -Xmn參數是在JDK 1.4 開始支持
下面兩個參數配合使用,當系統發生堆空間不足時,會導出整個堆的信息,且導出到指定的文件中去,後面用MAT工具分析
-XX:HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=d:/a.mp
直接內存配置
-XX:MaxDirectMemorySize
設置直接內存大小,如果不設置,默認值為最大堆空間,即-Xmx指定的大小,當達到指定值時,
會觸發垃圾回收,如果回收後也無法釋放空間,那麼將會拋出OOM
-XX:+UseSerialGC:新生代,老年代都使用串列收集器
-XX:+UseParNewGC:新生代使用ParNew收集器,老年代使用串鋒談行收集器Serial
(jdk9,10已經廢除,因為ParNew只能和CMS收集器配合使用,而jdk9,10使用的默認收集器是G1)
-XX:+UseParallelGC:新生代使用ParallelGC,老年代使用串列收集器Serial
-XX:+UseParallelGC:新生代使用ParallelGC回收器,老年代使用串列回收器Serial
-XX:+UseParallelOldGC:新生代使用ParallelGC回收唯者器,老年代使用ParallelOldGc回收器
兩個重要參數
-XX:MaxGCPasuseMillis:設置最大垃圾回收停頓時間,設置的過小,可能導致垃圾回收頻率加大
-XX:GCTimeRatio:設置吞吐量大小,取值范圍為0-100,系統回收垃圾的停頓時間花費不超過1/(1+n)%
設置線程數量
-XX:ParallelGCThreads
-XX:+UseConcMarkSweepGC:新生代使用ParNew回收器,老年代使用CMS
CMS默認啟動的並發線程數量為(parallelGCTheads+3)/4
設置並發線程數
-XX:ConcGCThreads=n
-XX:ParallelGCThreads=n
設置老年代空間使用率達到多少時執行CMS垃圾回收
-XX: 默認值為68
碎片整理參數,如果碎片不整理,可能造成沒達到閾值就會觸發老年代垃圾回收
-XX:+UseCMSCompactAtFullCollection :在CMS垃圾收集完成之後,進行一次內存碎片整理
-XX:+CMSFullGCsBeforeCompaction=n :在n次CMS回收後進行一次內存碎片整理
使用CMS回收方法去的perm區,默認情況下,還需要觸發一次FullGC
-XX:+CMSClassUnloadingEnabled
XX:UseG1GC 開啟G1垃圾收集器
兩個重要的參數
-XX:MaxGcPasuseMillis :指定目標最大停頓時間,如果停頓的時間過小,一次收集的區域數量也會變小
就會增加FullGC的可能
-XX:parallelGCThreads :設置並行回收的GC線程數量
-XX: :設置整個堆使用率達到多少時,觸發並發標記的執行,默認是45
-XX:+PrintGC 在程序運行期間,只要遇到GC,就會列印GC情況
佔用大小->gc後大小 GC消耗時間
jdk9,jdk10默認使用G1收集器,所以列印GC參數不同
-Xlog:gc
-XX:+PrintGCDetails 列印GC詳細信息(JDK8,9,10建議使用-Xlog:gc*)
-XX:+PrintGCTimeStamps 列印分析GC發生的時間
-XX:+ 列印應用程序的執行時間
-XX:+PrintGCApplicationStoppedTime 列印GC產生停頓的時間
-Xloggc:/usr/local/log/gc.log 讓gc日誌列印在log文件夾下的gc文件中,因為默認情況下gc日誌在控制台輸出
棧上分配,逃逸分析(方法內的變數被外部引用)
允許對象直接在棧上進行分配,隨線程停止而銷毀,這樣做可以加快分配速度,減少GC次數
棧空間較小,所以不適合大對象的棧上分配
-XX:+DoEscapeAnalysis 啟用逃逸分析
-XX:+EliminateAllocations 開啟標量替換(默認打開),允許對象打散分配在棧上
比如對象擁有id和name兩個欄位,那麼這兩個欄位將會被視為兩個獨立的變數進行分配。
對象晉升
-MaxTenuringThreshold=n ,當對象經歷了多少次GC次數後進入老年代
注意:大對象直接晉升到老年代
-PretenureSizeThreshold=n 這里的單位是位元組,新生對象大於這個值的時候,會直接分配到老年代
1、對存活對象標注時間過長:比如重載了Object類的Finalize方法,導致標注Final Reference耗時過長;或者String.intern方法使用不當,導致YGC掃描StringTable時間過長。
2、長周期對象積累過多:比如本地緩存使用不當,積累了太多存活對象;或者鎖競爭嚴重導致線程阻塞,局部變數的生命周期變長
當Eden區空間不足時,就會觸發YGC。結合新生代對象的內存分配看下詳細過程:
1、新對象會先嘗試在棧上分配,如果不行則嘗試在TLAB分配,否則再看是否滿足大對象條件要在老年代分配,最後才考慮在Eden區申請空間。
2、如果Eden區沒有合適的空間,則觸發YGC。
3、YGC時,對Eden區和From Survivor區的存活對象進行處理,如果滿足動態年齡判斷的條件或者To Survivor區空間不夠則直接進入老年代,如果老年代空間也不夠了,則會發生promotion failed,觸發老年代的回收。否則將存活對象復制到To Survivor區。
4、此時Eden區和From Survivor區的剩餘對象均為垃圾對象,可直接抹掉回收。
此外,老年代如果採用的是CMS回收器,為了減少CMS Remark階段的耗時,也有可能會觸發一次YGC,這里不作展開。
大多數情況下,對象直接在年輕代中的Eden區進行分配,如果Eden區域沒有足夠的空間,那麼就會觸發YGC(Minor GC),YGC處理的區域只有新生代。因為大部分對象在短時間內都是可收回掉的,因此YGC後只有極少數的對象能存活下來,而被移動到S0區(採用的是復制演算法)。當觸發下一次YGC時,會將Eden區和S0區的存活對象移動到S1區,同時清空Eden區和S0區。當再次觸發YGC時,這時候處理的區域就變成了Eden區和S1區(即S0和S1進行角色交換)。每經過一次YGC,存活對象的年齡就會加1。
下面4種情況,對象會進入到老年代中:
當晉升到老年代的對象大於了老年代的剩餘空間時,就會觸發FGC(Major GC),FGC處理的區域同時包括新生代和老年代。除此之外,還有以下4種情況也會觸發FGC:
2. jvm堆內存和非堆內存(小白入門文,各博客視頻基礎總結)
一:堆內存和非堆內存定義
java虛擬機具有一個堆(Heap),堆是運行時數據區域,所有類實例和數組的內存均從此處分配。堆是Java虛擬機啟動時創建的。在JVM中堆之外的內u你成為非堆內存(Non-heap memory)。
堆內存以及相應垃圾回收演算法
1.堆的大小可以固定,也可以擴大和縮小,堆內存不需要是連續空間。
2.對象創建後進入Eden。年輕代分為Eden和Survivor。Survivor由FromSpace和ToSpace組成。Eden區佔大容量,Survivor佔小容量,默認比例8:1:1。
MinorGC:採用復制演算法。首先把Eden和ServivorFrom區域中存活的對象賦值到ServivorTo區域(如果對象年齡達到老年標准/ServivorTo位置不夠了,則復制到老年代),同時對象年齡+1,然後清空Eden和ServivorFrom中的對象。然後ServivorTo和ServivorFrom互換。
3.老年代
老年代存放生命周期長的內存對象。
老年代對象相對穩定,所以不會頻繁GC。在進行MajorGC前一般都先進行一次MinorGC,使新生代的對象進入老年代,導致空間不夠用時才觸發。當無法找到足夠大的連續空間分配給新晉的對象也會提前觸發MajorGC進行垃圾回收。
MajorGC:如果使用CMS收集器,採用標記-清除演算法。首先掃描老年代,標記游笑孝所有可回收對象,標記完成後統一回收所有被標記對象。同時會產生不連續的內存碎片。碎片過多會導致以後程序運行需要分配較大對象時,無法找到足夠的連續內存,而不得已再次出發GC。否則採用標記-壓縮演算法。
標記-壓縮:在標記可回收對象後,將不可回收對象移向一端,然後清除標記對象。
當神稿老年代也滿了裝不下時,拋出OOM異常。
二:永久代
內存中永久保存的區域,主要存放Class和Meta(元數據)的信息,Class在被載入的時候被放入永久區域。他和存放實例的區域不同,GC不會再主程序運行期對永久區進行清理。所以也可可能導致永久代區域隨著載入Class的增多而脹滿,拋出OOM。
Java8中,永久代已經被移除,被一個成為「元數據區」(元空間)的區域所取代。
元空間的本質與永久代類似,都是JVM方法區的實現。不過元空間使用本地內存,永久代在JVM虛擬機中。因此,默認情況下,元空間的大小受本地內存限制。類的元數據放入native memory,字元串常量池和類的靜態變數放入java堆中,這樣可以載入多少類的元數據就不再由MaxPermSize控制,而是由系統實際可用空間控制。
1元空間解決了永久代的OOM問題,元數據和class對象在永久代容易出現性能問題和內存溢出。
2類的方法信息等比較難確定其大小,對於永久代的大小指定比較困難,小永久代溢出,大老年代溢出。
3永久代會為GC帶來不必要的復雜度,回收效率低。
三:堆內存參數調優
1.-Xms 設置初始分配內存大小,默認物理內存1/64
2.-Xmx 設置最大分升碰配內存,默認物理內存1/4
long maxMemory = Runtime.getRuntime().maxMemory(); long totalMemory = Runtime.getRuntime().totalMemory(); System.out.println("最大分配內存"+maxMemory/(double)1024/1024+"MB "+maxMemory/(double)1024/1024/1024+"GB"); System.out.println("默認分配內存"+totalMemory/(double)1024/1024+"MB "+totalMemory/(double)1024/1024/1024+"GB");
3. java線程存放在jvm的哪個區域方法又存放在哪個區呢
聊到JAVA中的方法,大多數人對於方法存儲在方法區還是棧區(虛擬機棧)是很迷茫的。其實方法是存在方法區的下面我們就細細說一下JVM中的 方法區 VS 棧區方法區:用於存儲已被虛擬機載入的類信息、常量、靜態變數、即時編譯器編譯後的代碼等數據,方法編譯出的位元組碼也是保存在這
4. jvm運行時數據區有哪些
JVM運行時數據區包括:Meta Space元空間(JDK1.8之前叫方法區或者永久代)、VM Stack虛宴消喊擬機棧、Native Method Stack本地方法棧橋鉛、Heap Space堆晌野空間、Program Couter Register程序計數器。
5. 重新理解jvm運行時的內存分布(堆棧方法區交互)
棧堆方法區的交互關系
java棧存儲的本地變數表,包括八種數據類型和引用類型,引用類型指向對象的地址,保存在reference,指向java堆,對象類型數據會保存變數名,變數類型,變數值等,這些會存在方法區中去查看(在初始化的時候)。
在java棧中會存放對象實例(s1),但是他對象旁汪實例中具體的數據會由java棧中的引用指向java堆中的地址,裡面的對象實例數據存放(實例名,實例相關類型,元數據信息。。。。),而靜態變數,常量,類載入後的信息等會存放在方法區,在運行時需要調用的時候去方法區取,所以方法區和java堆都是共享的。而java棧時線程獨有的數據(包括程序計數器,本地方法棧)。
一個jvm實例,只存在一個堆內存,堆內存的大小是可以調節的。類載入器讀取了類文件之後,需要把類,方法,常量放到堆內存中,保存所有的引用類型的真實信息,以方便執行器執行。堆內存分為三部分。
(養老區就是老年代)
堆內存 邏輯上 分為三部:新生 +養老 +方法區
eden+survivor+Spaces(元空間或者叫方法區或者Perm)
Perm 永久存儲區,是一個常駐內存的區域,用於存放jdk自身攜帶的Class,Interface的元數據,被裝載進此區域的數據是不會被垃圾回收器回收的,只有關閉jvm後才會釋放此區域所佔用的內存。
如果出現OutOfMemoryReeor: PermGen space 說明java虛擬機堆永久帶Perm內存設置不夠,答襪一半出現這種情況,都是程序啟動載入大量第三方jar呆滯的,
對於HotSpot虛擬機很多開發者習慣將方法區稱之為永久代(Parmenent
Gen),永久代是方法區的一個實現,這是不對的,方法區是邏輯上的部分。在jdk7中已經將原本放在永久代的字元串常量池移走了。
常量池( Constant Pool Constant PoolConstant Pool Constant Pool Constant Pool )是方法區的一部分, Class Class文件除了有類的版本、 欄位方法、介面等描述信息外,還有一項就是常量池這部分內容將在類載入後進入。
伊甸園區,所有對象剛new出來都會放在這里。
對象分兩種:
1.如果是大對象直接分配在Old區。
2.如果禁言了逃逸分析,會運舉仔在棧上分配。
以上兩種都不符合,放入伊甸園區。(Eden區)
看java7中如圖:
對比java8
6. JVM中常量池存放在哪裡
java8之前:
java8之後:元數據區 Metaspace
由於 PermGen 內存管理的效果遠沒有達到預期,所以JCP已經著手去除PermGen的工作。在JDK7中,字元串常量已經從永局褲久代移除。現今 JDK8 中 PermGen 已經被徹底移除,取而代之的是metaspace數據區,使用native內存,申請和釋放由虛擬機負責管理。
那麼,JVM中常量池到底存放在哪裡?
Java6和6之前,常量喚臘櫻池是存放在方法和叢區(永久代)中的。
Java7,將常量池是存放到了堆中。
Java8之後,取消了整個永久代區域,取而代之的是元空間。 運行時常量池和靜態常量池存放在元空間中,而字元串常量池依然存放在堆中。
7. 如何設置jvm伊甸區大小
一、設置方法區內存大小
1、方法區的大小不必是固定的,jvm可以根據應用的需要動態調整。
1.1、jdk7及以茄型前:
1、通過-XX:PermSize來設置永久代初始分配空間。默認值是20.75M。
2、-XX:MaxPermsize來設定永久代最大可分配空間。32位機器默認是64M,64位機器模式是82M。
3、當JVM載入的類信息容量超過了這個值,會報異常outofMemoryError:PermGenspace
1.2、jdk8及以後
1、元數據區大小可以使孝改用參數-XX:MetaspaceSize和-XX:MaxMetaspaceSize指定,替代上述原有的兩個參數。
2、默認值依賴於平台。windows下,-XX:MetaspaceSize是21M,-XX:MaxMetaspaceSize 的值是-1,即沒有限制。
3、與永久代不同,如果不指定大小,默認情況下,虛擬機會耗盡所有的可用系統內存。如果元數據區發生溢出,虛擬機一樣會拋出異常outofMemoryError: Metaspace
4、-XX:MetaspaceSize:設置初始的元空間大小。對於一個64位的伺服器端JVM來說,其默認的-XX:MetaspaceSize值為21MB。這就是初始的高水位線一旦觸顫慎猜及這個水位線,Full GC將會被觸發並卸載沒用的類(即這些類對應的類載入器不再存活),然後這個高水位線將會重置。新的高水位線的值取決於GC後釋放了多少元空間。如果釋放的空間不足,那麼在不超過MaxMetaspaceSize時,適當提高該值。如果釋放空間過多,則適當降低該值。
5、如果初始化的高水位線設置過低,上述高水位線調整情況會發生很多次。通過垃圾回收器的日誌可以觀察到Full GC多次調用。為了
8. jvm的理解
1
JVM內存區域
我們在編寫程序時,經常會遇到OOM(out of Memory)以及內存泄漏等問題。為了避免出現這些問題,我們首先必須對JVM的內存劃分有個具體的認識。JVM將內存主要劃分為:方法區、虛擬機棧、本地方法盯搏棧、堆、程序計數器。JVM運行時數據區如下:
1.1
程序計數器
程序計數器是線程私有的區域,很好理解嘛~,每個線程當然得有個計數器記錄薯升當前執行到那個指令。佔用的內存空間小,可以把它看成是當前線程所執行的位元組碼的行號指示器。如果線程在執行Java方法,這個計數器記錄的是正在執行的虛擬機位元組碼指令地址;如果執行的是Native方法,這個計數器的值為空(Undefined)。
此內存區域是唯一一個在Java虛擬機規范中沒有規定任何OutOfMemoryError情況的區域。
1.2
Java虛擬機棧
與程序計數器一樣,Java虛擬機棧也是線程私有的,其生命周期與線程相同。
如何理解虛擬機棧呢?
本質上來講,就是個棧。裡面存放的元素叫棧幀,棧幀好像很復雜的樣子,其實它很簡單!它裡面存放的是一個函數的上下文,具體存放的是執行的函數的一些數據。執行的函數需要的數據無非就是局部變數表(保存函數內部的變數)、操作數棧(執行引擎計算時需要),方法出口等等。
執行引擎每調用一個函數時,就為這個函數創建一個棧幀,並加入虛擬機棧。換個角度理解,每個函數從調用到執行結束,其實是對應一個棧幀的入棧和出棧。
注意這個區域可能出現的兩種異常:
一種是StackOverflowError,當前線程請求的棧深度大於虛擬機所允許的深度時,會拋出這個異常。製造這種異常很簡單:將一個函數反復遞歸自己,最終會出現棧溢出錯誤(StackOverflowError)。
另一種異常是OutOfMemoryError異常,當虛擬機棧可以動態擴展時(當前大部分虛擬機都可以),如果無法申請足夠多的內存就會拋出OutOfMemoryError,如何製作虛擬機棧OOM呢,參考一下代碼:
這段代碼有風險,可能會導致操作系統假死,請謹慎使用~~~
1.3
本地方法棧
本地方法棧與虛擬機所發揮的作用很相似,他們的區別在於虛擬機棧為執行Java代碼方法服務,而本地方法棧是為Native方法服務。與虛擬機棧一樣,本地方法棧也會拋出StackOverflowError和OutOfMemoryError異常。
1.4
Java堆
Java堆可以說是虛擬機中最大一塊內存了。它是所有線程所共享的內存區域,幾乎所有的實例對象都是在這塊區域中存放。當然,隨著JIT編譯器的發展,所有對象在堆上分配漸漸變得不那麼「絕對」了。
Java堆是垃圾收集器管理的主要區域。由於現在的收集器基本上採用的都是分代收集演算法,所有Java堆可以細分為:新生代和老年代。在細致分就是把新生代分為:Eden空間、From Survivor空間、To Survivor空間。當堆無法再擴展時,會拋出OutOfMemoryError異常。
1.5
方法區
方法區存放的是類信息、常量、靜態變數等。方法區是各個線程共享區域,很容易理解,我們在寫Java代碼時,每個線程度可以訪問同一個類的靜態變數對象。由於使用反射機制的原因,虛擬機很難推測那個類信息不再使用,因此這塊區域的回收很難。另外,對這塊區域主要是針對常量池回收,值得注意的是JDK1.7已經把常量池轉移到堆裡面了。同樣,當方法區無法滿足內存分配需求時,會拋出OutOfMemoryError。
製造方法區內存溢出,注意,必須在JDK1.6及之前版本才會導致方法區溢出,原因後面解釋,執行之前,可以把虛擬機的參數-XXpermSize和-XX:MaxPermSize限制方法區大小數則老。
運行後會拋出java.lang.OutOfMemoryError:PermGen space異常。
解釋一下,String的intern()函數作用是如果當前的字元串在常量池中不存在,則放入到常量池中。上面的代碼不斷將字元串添加到常量池,最終肯定會導致內存不足,拋出方法區的OOM。
下面解釋一下,為什麼必須將上面的代碼在JDK1.6之前運行。我們前面提到,JDK1.7後,把常量池放入到堆空間中,這導致intern()函數的功能不同,具體怎麼個不同法,且看看下面代碼:
這段代碼在JDK1.6和JDK1.7運行的結果不同。
JDK1.6結果是:false,false ,JDK1.7結果是true, false。
原因是:JDK1.6中,intern()方法會吧首次遇到的字元串實例復制到常量池中,返回的也是常量池中的字元串的引用,而StringBuilder創建的字元串實例是在堆上面,所以必然不是同一個引用,返回false。
在JDK1.7中,intern不再復制實例,常量池中只保存首次出現的實例的引用,因此intern()返回的引用和由StringBuilder創建的字元串實例是同一個。為什麼對str2比較返回的是false呢?這是因為,JVM中內部在載入類的時候,就已經有"java"這個字元串,不符合「首次出現」的原則,因此返回false。
9. java中,靜態方法被調用是,存儲在內存的哪個區域是棧還是放大區還是兩者都有
在JDK8之前,靜態成員(靜態變數和靜態方法)都是存儲在方法區(永久代)中的內靜態區中(這容里指類被載入後,靜態成員的存儲位置)。但在JDK8之後,永久代被移除了,取而代之的是元空間(metaspace)。但元空間中存儲的主要是.class文件的元數據信息,靜態成員的存儲位置由方法區轉到了堆內存(heap)中。
不過,不管是JDK8,還是更早的版本中,靜態方法的執行(不僅僅是靜態方法,還有普通的成員方法)都是在棧內存(stack)中進行的。每個線程都會在棧內存中開辟一個棧,在調用方法時,對應的方法都會在執行這個方法的線程的棧中創建一個「棧幀」,棧幀中保存了局部變數表(基本數據類型和對象引用)、操作數棧、動態連接和返回地址等信息。等到方法執行完畢,棧幀被銷毀,對應的內存也將被釋放。
10. jvm堆內存區域包括哪些
根據《Java虛擬機規范》的規定,運行時數據區通常包括這幾個部分:程序計數器(Program Counter Register)、Java棧(VM Stack)、本地方法棧(Native Method Stack)、方法區(Method Area)、堆(Heap)。
如上圖所示,JVM中的運行時數據區應該包括這些部分。在JVM規范中雖然規定了程序在執行期間運行時數據區應該包括這幾部分,但是至於具體如何實現並沒有做出規定,不同的虛擬機廠商可以有不同的實現方式。