轉 https://www.java123.vip/wp/java
備註:下圖爲JVM7的內存結構,JVM8的perm空間作了以下修改:Java7→Java8 Permanent→物理內存的Metaspace 後續討論將基於JVM7的內存結構展開。算法
JVM爲Java運行時環境,也就是Java程序運行時的數據都由JVM來管理,如何管理好這批數據就是咱們平常所說的JVM調優。編程
能夠將JVM的存儲區域做以下劃分(只列舉了咱們要分析的區域,其餘區域請自行查閱資料)緩存
咱們來看一下下面的代碼函數
我用顏色表示了上面代碼在內存中存儲狀況。也就是局部變量存在於棧區;new的對象存在於堆區;類的定義文件存在於方法區;性能
棧區spa
棧區的特色是快,因此局部變量存在於這塊區域,供CPU在執行指令的時候直接操做。
採用入棧出棧的方式來進行空間的分配與釋放,遇到左括號變量進棧,遇到右括號變量出棧。
由於變量出棧後,這塊區域就被釋放了,因此這個局部變量在右括號外也就沒法使用了。這也解釋了變量的做用域問題。操作系統
咱們看到的StackOverFlow異常就是這塊區域空間已滿,因爲遇到右括號就會釋放空間,因此這塊區域不多會由於單純的一個方法內變量多致使這個異常。
通常是由於方法的遞歸調用層次太深,致使全部的局部變量都沒有釋放,繼而出現StackOverFlow異常。
咱們能夠經過設置-Xss參數來制定這塊空間的大小。.net
Java程序中經過new指令在堆內存分配一塊空間來存儲對象數據,可是並無把這塊數據刪除的可編程指令,也就是咱們寫程序的時候只管new對象。若是沒有任何程序來清除對象的話,那麼將會很快的把堆內存耗盡,報出OutOfMemoryException:heap space。線程
既然Java語言讓咱們省去了以編程的方式來管理內存可能帶來的某些內存未被釋放的危險,那麼Java在運行時就要提供一套對再也不使用的內存空間回收的方法。
咱們稱這個爲Java的垃圾回收機制。
JVM的垃圾回收由垃圾回收器完成,能夠制定不一樣的垃圾回收器來按不一樣的規則進行垃圾回收,咱們能夠根據須要指定最適合咱們的垃圾回收器。
咱們能夠經過-XX參數來指定垃圾回收器。
本文中咱們介紹經常使用分代回收算法。
把開始的圖再貼一下:
Heap(堆內存)=eden+2survivor(年輕代)+ParOldGen(老生代)+Perm(jdk8之前)。jdk8之後將永久代替換爲MetaSpace(元空間)存在於本地內存。from survivor 和 to survivor大小相同,且保證一個爲empty。
Heap Memory就是咱們說的堆內存,存儲對象,咱們首先把討論範圍限定在這塊區域。
一個對象從建立到消亡的過程以下:
1. 咱們用new關鍵字建立的對象,首先出生在伊甸園,即圖中的綠色部分(eden),因爲他剛出生,因此他屬於新生代(Young Generation)。
2. 隨着程序的運行,伊甸園中的對象將會急速增加,很快這塊區域將被填滿,這時要保證程序繼續運行,就必須清除再也不使用的對象。
也就是要進行一次新生代的垃圾回收(Young GC),首先計算那些對象(假設eden和to區)再也不被使用,而後還在使用中的對象咱們成爲倖存者。
在新生代提供了兩塊倖存區域(survior spaces)供倖存者使用:from和to,假設首先這部分倖存者被安排在from區(利用copy算法始終保持一個爲empty,併爲對象age+1.),而後eden和to區的全部對象被無情的清除,伊甸園開始繼續接收新生對象。
3. 立刻又到了不起不YoungGC的時間,這次檢查eden和from區,倖存的對象被放入to區。
4. 不斷重複2,3的過程期間一些對象經歷了屢次倖存遊戲,咱們稱這些對象已經成熟,將會被移動到老年區(Old)
5. 逐漸老年區的對象也被填滿,咱們系統將對整個堆內存進行一次垃圾回收(full GC),這個時間明顯比YoungGC的時間要長,發生的頻率要小。
Java虛擬機就是不停的重複上面1,2,3,4,5的過程來維持Java世界的環境。咱們經過上面的描述能夠看到,想提升性能,就要儘可能減小fullGC發生的頻率
上圖的reserved區域表明預留區域,咱們先來看如何制定這幾塊區域的大小:
-Xmx能夠指定整個堆內存的最大值。
-Xms能夠指定整個堆內存的最小值,也就是初始化值,系統啓動時按初始化值分配堆內存空間,內存使用超過初始值會啓動兩邊的reserve區域,超過最大值彙報OutOfMemory:heap space異常
-XX:NewSize能夠指定新生代的最小值。
-XX:MaxNewSize能夠指定新生代的最大值。
老年代的最小值能夠用Xms-XX:NewSize計算得出,因此不須要進行特別指定。
老年代的最大值能夠用Xms-XX:MaxNewSize計算得出,因此不須要進行特別指定。
方法區(Perm Space)
這部分主要存儲的類的定義,只有在進行類加載的時候纔會往這個區域內增長內容,因此這個區域基本上不涉及到太多的變化因素。
在系統啓動時和動態加載jar文件,class文件時,會增長這塊區域的數據,若是Jar包過多,撐破這塊區域則彙報OutOfMemory:perm space異常。
咱們能夠經過-XX:PermSize和-XX:MaxPermSize來指定這塊區域的最小,最大值。
-Xmx 指定java程序的最大堆內存
-Xms 指定初始堆內存, 一般設置成跟最大堆內存同樣,減小GC
-Xmn 設置年輕代大小。整個堆大小=年輕代大小 + 老年代大小。因此增大年輕代後,將會減少年老代大小。此值對系統性能影響較大,Sun官方推薦配置爲整個堆的3/8。
-Xss 指定線程的最大棧空間, 此參數決定了java函數調用的深度, 值越大調用深度越深, 若值過小則容易出棧溢出錯誤(StackOverflowError)
-XX:PermSize 指定方法區(永久區)的初始值,默認是物理內存的1/64, 在Java8永久區移除, 代之的是元數據區, 由-XX:MetaspaceSize指定
-XX:MaxPermSize 指定方法區的最大值, 默認是物理內存的1/4, 在java8中由-XX:MaxMetaspaceSize指定元數據區的大小
-XX:NewRatio=n 老年代與年輕代的比值,-XX:NewRatio=2, 表示老年代與年輕代的比值爲2:1
-XX:SurvivorRatio=n Eden區與Survivor(2個)區的大小比值,-XX:SurvivorRatio=8表示Eden區與Survivor區的大小比值是8:1:1,由於Survivor區有兩個(from, to)
JVM 調優建議
經過設置咱們但願達到一些目標:
前兩個目前是相悖的,要想GC時間小必需要一個更小的堆,要保證GC次數足夠少,必須保證一個更大的堆,咱們只能取其平衡。
1)針對JVM堆的設置通常,能夠經過-Xms -Xmx限定其最小、最大值,爲了防止垃圾收集器在最小、最大之間收縮堆而產生額外的時間,咱們一般把最大、最小設置爲相同的值。
2)年輕代和年老代將根據默認的比例(1:2)分配堆內存,能夠經過調整兩者之間的比率NewRadio來調整兩者之間的大小,爲了防止年輕代的堆收縮,咱們一般會把-XX:newSize -XX:MaxNewSize設置爲一樣大小
年輕代和年老代設置多大才算合理?
更大的年輕代必然致使更小的年老代,大的年輕代會延長普通GC週期,但會增長每次GC的時間;小的年老代會致使更頻繁的Full GC
更小的年輕代必然致使更大年老代,小的年輕代會致使普通GC很頻繁,但每次的GC時間會更短;大的年老代會減小Full GC的頻率。
如何選擇應該依賴應用程序對象生命週期的分佈狀況:
若是應用存在大量的臨時對象,應該選擇更大的年輕代;若是存在相對較多的持久對象,年老代應該適當增大。但不少應用都沒有這樣明顯的特性,在抉擇時應該根據如下兩點:
A.本着Full GC儘可能少的原則,讓年老代儘可能緩存經常使用對象,JVM的默認比例1:2也是這個道理。
B.經過觀察應用一段時間,看其餘在峯值時年老代會佔多少內存,在不影響Full GC的前提下,根據實際狀況加大年輕代,好比能夠把比例控制在1:1。但應該給年老代至少預留1/3的增加空間。
4)在配置較好的機器上(好比多核、大內存),能夠爲年老代選擇並行收集算法: -XX:+UseParallelOldGC ,默認爲Serial收集。
5)線程堆棧的設置:每一個線程默認會開啓1M的堆棧,用於存放棧幀、調用參數、局部變量等,對大多數應用而言這個默認值太了,通常256K就足用。理論上,在內存不變的狀況下,減小每一個線程的堆棧,能夠產生更多的線程,但這實際上還受限於操做系統。
待續。。。
調優相關
http://www.javashuo.com/article/p-xkhfchaz-o.html
https://blog.csdn.net/u013380694/article/details/82908786
https://blog.csdn.net/weixin_34327223/article/details/85600787