1. 程式人生 > >JVM隨筆分類(JVM堆的記憶體回收)

JVM隨筆分類(JVM堆的記憶體回收)

開發十年,就只剩下這套架構體系了! >>>   

  JVM常用的回收演算法是:
  
  標記/清除演算法
  
  標記/複製演算法
  
  標記/整理演算法
  
  其中上訴三種演算法都先具備,標記階段,通過標記階段,得到當前存活的物件,然後再將非標記的物件進行清除,而物件記憶體中物件的標記過程,則是使用的
  
  “根搜尋演算法”,通過遍歷整個堆中的GC ROOTS,將所有可到達的物件標記為存活的物件的一種方式,則是 “根搜尋演算法”,其中根是指的“GC ROOTS”,在JAVA中,充當GC ROOTS的物件分別有:“虛擬機器棧中的引用物件”,“方法區中的類靜態屬性引用的物件”,“方法區中的常量引用物件”,“本地方法棧中JNI的引用物件”,凡是於上述物件存在可到達物件時,則該物件將標記為可存活物件,否則則為不可達物件,即可回收物件。在根搜尋演算法之前,還存在一個“引用計數演算法”,即根據物件的被引用次數來進行計算,凡是物件的引用次數為0時,則表示為可回收物件,但對於互相引用的物件,如果不手動將互相引用的物件置空時,則該物件的引用次數永遠將不會為0,則永久不會回收,則必然是錯誤,可參考:https://www.cnblogs.com/zuoxiaolong/p/jvm3.html
  
  根據上述所提到的演算法,可知:“根搜尋演算法”解決了標記那些物件是可回收,那些是不可回收物件的一個作用,但是對於具體在標記後的,回收行為,則是上述前三個演算法的具體應用了,分別是“標記後清除”,以及“標記後複製”,及“標記後整理”,
  
  “標記清除演算法”的優勢以及劣勢:由於標記清除演算法只需要兩種動作行為,分別是:1.通過根搜尋演算法,標記可到達的存活物件,2.清除不可到達的物件記憶體;通過使用前面的兩步,則將對應的不可達記憶體物件,進行了快速的清理,但是對於被回收後剩餘的空閒記憶體的空間則是不連續的,因為被回收物件都是隨機存在於記憶體的各個角落,在被回收後,記憶體的空間自然是存活的物件各自佔據在各自原有的物件記憶體位置中,而並沒有將剩餘的存活物件進行相關的記憶體空間的整理,所以對於後續 分配陣列物件時,尋找一個連續的記憶體空間則是一個較為麻煩的事情,。故:"標記/清除"的優勢則是,記憶體空間的整理快速,效率較高,但劣勢則是:對應的清理後空間則是不連續的記憶體空間。
  
  “標記/複製演算法”:通過維護一份空閒記憶體的方式,來進行物件的回收,如:當前記憶體分為兩份,分別為活動記憶體T1和非活動記憶體T2,在使用中時使用活動記憶體,當活動記憶體滿的時候,進行 物件的標記,得到當前的存活物件,此時將對應的存活物件,複製到對應的非活動記憶體T2當中,且嚴格按照物件記憶體地址進行依次排列,與此同時,GC執行緒將更新存活物件的記憶體引用地址指向新的記憶體地址; 物件複製的同時T1記憶體中的物件全部進行清除,此時的T2則扮演著活動記憶體的角色,而T1則是非活動記憶體,通過上述可知,使用複製演算法的方式避免了“標記/清除”演算法對於空間連續性的弊端,但複製演算法的劣勢則是,一直保留著一份空閒的記憶體,作為對應的備用記憶體,這整整浪費了一半的記憶體,相對來時還是比較浪費的。
  
  “標記/整理演算法”:1. 標記:它的第一個階段與標記/清除演算法是一模一樣的,均是遍歷GC Roots,然後將存活的物件標記。2.  移動所有存活的物件,且按照記憶體地址次序依次排列,然後將末端記憶體地址以後的記憶體全部回收。因此,第二階段才稱為整理階段。 可以看出標記整理演算法,是通過標記所有的存活物件,然後再嚴格按照記憶體地址移動對應的存活物件後,再將末端的記憶體地址全部回收的方式,來進行的記憶體的空間整理,,所以,標記整理演算法,並非是單單的:標記/清除/整理的方式,而是通過整理存活物件的連續性地址後,再進行末端地址回收的方式進行的記憶體的整理。;通過上述也可以看出 標記整理演算法,彌補了標記清除對於不連續空間的記憶體整理的特性,也避免了複製演算法對於一半空閒記憶體的浪費的特性。儘管標記整理擁有較好的特性,但沒有特別完美的演算法,所以,在劣勢上:標記整理演算法的整體執行效率是要低於標記複製演算法的。
  
  此處想要說明一下:JVM對於記憶體的清理上來看:標記整理演算法,分別是先通過掃描GC ROOT得到存活物件,然後 移動對應的存活物件的地址,使其進行以此排列,然後將 依次進行排列的記憶體地址,往後的所有的末端記憶體,直接進行回收 的方式來進行具體的操作的。所以,“標記整理演算法”實際上的操作方式可以分為三步,分別是:1. 標記 2. 移動物件所在記憶體地址,3.  將末尾記憶體直接全部清除。而,“複製演算法”則是:1. 標記存活物件,2. 移動存活物件所在地址,將其移動到空閒記憶體中即可。相同操作的情況下,可以看出:複製演算法的效率是大於標記整理演算法的。畢竟整理演算法除了和複製演算法都操作了具體的記憶體地址的移動以外,還比複製演算法多出了一個末尾清除的步驟,所以:複製演算法的效率>整理演算法,,而“標記清除演算法”,1. 標記所有存活物件,2. 清除所有“不連續的”空間記憶體。通過對比一些時間複雜度和執行效率上來看,JVM對於不連續的記憶體空間的清理的執行時間,似乎是要大於整理演算法直接將末尾記憶體直接清除的執行時間的,所以簡單的去看執行效率和時間複雜度上來看:標記複製演算法>標記整理演算法>標記清除演算法(也可能是由於標記清除演算法是比較老的演算法的緣故,導致標記清除演算法的執行效率對於其餘的兩種演算法,但實際情況則不見得一定是這樣,此處的效率只是簡單的對比了時間複雜度來看,實際情況lz總還是覺得標記清除演算法的執行時間和效率是大於整理演算法的,畢竟單單從執行步驟來看,標記清除演算法的執行是佔據優勢的,除非jvm對於非連續記憶體的清除方式真的是過於較低而導致,此處先做一下簡單的記錄罷了)
  
  最終的演算法,分代收集演算法,通過將jvm的記憶體區域進行劃分所進行執行的一直演算法方式:
  
  JVM中執行時記憶體區域分別有:堆,棧,本地棧,方法區,暫存器;其中棧和暫存器指標,是執行緒執行時的私有記憶體,執行緒結束後則棧記憶體同步釋放, 所以JVM的記憶體回收,則共需關注的是堆以及方法區的記憶體回收,其中堆是各個物件建立時的記憶體區域,而方法區則包含類的calss以及常量,靜態資源所對應的各個記憶體的儲存區域。所以集中在堆中的不存活物件以及方法區的物件的回收,便是整體GC記憶體回收時的重點,;
  
  “分代收集演算法”:JVM中將堆劃分為不同的區域,分別是 新生代,老年代,以及永久代,根據物件的宣告週期不同,所以針對不同生命週期的物件的回收方式也不同,以此來增加回收效率。
  
  在Java堆中:大多數物件是在新生代中被建立,當新生代中的物件在經歷過多次Minor GC後,且仍然為存活物件的資料,則將會晉升到老年代,(其中包含了晉升閥值和JVM自動調節晉升閥值的一個概念),當簡單了接了上述概念後,則已經基本了接了新生代以及老年代的作用,下面詳細進行下相關的介紹:
  
  在Java中,新建立的物件資料則都是在新生代中進行建立,一般表現特徵為生命週期較短,通過新生代的垃圾回收後只要少量的物件存活,所以新生代更加適合 執行效率較高的複製演算法,針對複製演算法的執行特徵,所以要存在一份備用的記憶體區域來作為新生代在記憶體回收後的臨時物件的儲存場地,於是 新生代中便又劃分為了對應的記憶體區域分別為:Eden區,以及 兩個 Survivor區域,其中Eden區域的記憶體特徵和新生代最初的記憶體特徵不變,是用於存放物件在初始建立時的記憶體區域,當Eden區中的新生代物件佔滿了對應的Eden區域記憶體空間時,便會發生對應的Minor GC,即對應的記憶體回收,由於Eden 區域中的物件生命週期普遍較短,在經歷第一次的Minor GC後,則將對應的存活物件,移動到對應的Survivor區域,其中兩個Survivor區域中,選擇任意一個,作為存活物件的新的儲存空間,所以此處由此可知,Survivor作為Eden GC後的備用倉庫,Survivor的大小設定只需要可以儲存下Eden區的存活物件即可,一般推薦,Survivor區域的記憶體大小佔整個年輕代的1/6即可,即:-XX:SuvrivorRatio=4,當然,所有的記憶體值的設定,都可以在後續根據專案的具體情況進行對應的GC的優化,當第一個from Survivor區域空間滿時,則將會把對應的物件轉移到對應的to Survivor中,然後清空對應的from Survivor區域,然後依次進行復制演算法的迴圈,在物件不斷的從From Suvrivor轉移到to Survivor以及從to Survivor轉移到from Suivivor的同時,Survivor的作用除了是Eden區的備用倉庫外,還具備篩選“老物件”的作用,當Survivor中的物件在經歷過多次的Minor GC時,還沒有被清除時,則便可以晉升為“老年代”,老年代一般用於儲存存活時間更長的物件資料,而如何識別物件具備晉升為“老年代”的數值,則可以通過MaxTenuringThrehold進行設定,預設閥值為15,即年輕代中的物件在經歷過15次的Minor GC還存在於物件空間的資料,則可以晉升到年老代,,,,但:如果年輕代的物件資料不斷增長,而Survivor區域的物件還遲遲不滿足MaxTenuringThrehold所設定的晉升閥值,此時一旦Survivor記憶體溢位,則無論物件的年齡閥值是多大,則都會全部晉升到年老代中,這對於年老代來說是個噩夢,因為這將導致不斷的Full GC,且會不斷降低程式的執行效能,,,,所以為了不存在MaxTenuringThrehold設定過大,而導致的晉升失敗的情況,JVM則引入了動態的年齡計算,當累計的某個年齡大小的物件,超過了Survivor的一半時,則取當前的物件年齡作為新的物件晉升閥值,可參考:https://mp.weixin.qq.com/s/t1Cx1n6irN1RWG8HQyHU2w
  
  上面簡單介紹了下相關的年輕代的回收的一些知識和問題後,後面陸續再分析下當前常用的GC的收集器分別有哪些:,以及各收集器的作用和各個引數的調節及注意事項等。
  
  關於GC收集器的更多簡介可以參考該連結:https://www.cnblogs.com/zuoxiaolong/p/jvm8.html
  
  首先;常用的收集器分別是:序列,並行,併發 收集器,其中序列一般用於Cliend模式,即當前程式碼開發過程除錯過程時所設定的模式,序列收集器分別包含:Serial Garbage Collector 序列年輕代收集器(複製演算法),和 Serial Old Garbage Collector 序列老年代收集器(標記/整理演算法),
  
  而並行收集器:則包括:ParNew Garbage Collector ,Paraller Scavenge,這兩個是專門為年輕代設計的並行收集器,皆為複製演算法,其中Paraller Scavenge則是-Server模式下的預設年輕代收集器,除此之外,並行收集器還剩餘:Paraller Old,此收集器是老年代的並行收集器為 標記/整理演算法,也是-Server模式下的預設老年代收集器。
  
  唯一的一個併發收集器:是專門用於年老代回收時的併發收集器:concurrent mark sweep(簡稱CMS),真正做到了GC程式和應用程式併發執行,不會暫停應用的執行程式的一款收集器。(所使用的執行演算法為:標記/清除演算法)。
  
  ---------------------------- 注:所有的GC收集器在執行過程當中,都會暫停應用執行緒,只是一般年輕代使用並行收集器的GC,由於並行執行,則應用的停頓時間則相對較短,所以感受不到對應的應用暫停的特徵,但其實的確是先暫停對應的應用執行緒在GC執行過後,再喚醒對應的應用執行緒繼續執行,可以通過檢視GC日誌,來檢視當前GC時的實際耗費時間,。,,,,而CMS則是唯一一個,在GC收集時和應用程式執行緒同步進行的一款收集器,只是只適用於年老代的併發收集,,所以合適的收集器的組合,才可以出現更優的效果;,並且,在HotSport 中,除了CMS之外,其他的老年代收集器,在執行的過程中,都會同時收集整個GC堆,包括新生代,(此處是需要注意的)。
  
  合適的收集器的選擇:
  
  對於對響應時間有較高的要求的系統,可以選擇ParNew 作為新生代並行收集器,& CMS 作為對應的老年代收集器, 由於ParNew是並行收集,因此新生代的GC速度會非常快,停頓時間很短。而年老代的GC採用併發蒐集,大部分垃圾蒐集的時間裡,GC執行緒都是與應用程式併發執行的,因此造成的停頓時間依然很短。
  
  對於對系統吞吐量有要求的系統,可選擇Paraller Scavenge作為 年輕代的並行收集器,使用Paraller Old 作為年老代的並行收集器,由於年輕代和年老代都是使用並行收集器,所以對系統停頓時間較短,且Paraller Scavenge收集器可以更加精準的控制GC的停頓時間和吞吐量的設定,所以對於在單位時間對系統可完成的指令數(吞吐量)有要求,但是對系統的響應時間沒有過大要求的系統可以使用上述的兩種結合處理器;(要想在單位時間內處理的請求更多,即系統的吞吐量更高,則設定相關的年輕代的大小,可以有效的增加系統的吞吐量和處理時間。)
  
  JVM的可參考配置:
  
  ParNew & CMS:
  
  Paraller Scavenge & Paraller Old:
  
  假設當前專案所部屬伺服器記憶體為8G,且總活躍物件資料為1G(1G的活躍物件資料已經很大了),
  
  則當前總堆的物件資料設定為:(初始jvm預設記憶體設定與 總的JVM堆的可分配記憶體Xmx一致,可避免JVM GC後堆的重新分配);
  
  堆的大小設定:-Xms8192m,-Xmx8192m
  
  新生代的設定比例為:-Xmn1536m,
  
  老年代的設定比例為:-XX:NewRatio用於設定年輕代與年老代所佔比例值,當上述新生代採用Xmn進行設定時,此處NewRatio可以不用設定,則預設為 總堆記憶體-新生代記憶體 = 老年代記憶體;
  
  永久代設定比例為:-XX:PermSize=1536m,-XX:MaxPermSize=1536m
  
  新生代中Eden與from to記憶體區的比例:-XX:SurvivorRatio=4,表示當前Eden區和兩個From區的比值為:4:2,則當前eden區佔整個年輕代的4/6;(說明一下:JVM中的動態年齡計算,則是根據對應的From區的大小和物件的年齡進行閥值的計算的)
  
  新生代物件晉升年齡閥值的設定:-XX:MaxTenuringThreshold=15,預設情況下物件的晉升年齡閥值為15,上面已經提到過了JVM則會根據新生代中倖存區Survivor(及From 和to區域)的大小以及倖存區中物件的年齡動態計算晉升閥值的數值,那麼?是不是此處設定XX:MaxTenuringThreshold則無效了嗎?錯!,JVM在設定晉升閥值是根據所計算出的年齡值和XX:MaxTenuringThreshold的年齡值進行對比,那個值越小,則使用當前更小的年齡值作為新的晉升閥值,所以如果設定XX:MaxTenuringThreshold的值為0,或者更小的值1,2,等,則將更快的增加新生代進入老年代的頻率,(舉個例子,對於年老代比較多的應用可以直接將物件晉升到年老代,且由於CMS的年老代回收是隻回收年老代且併發收集過程中不影響應用執行緒的執行,所以直接晉升年老代,對於GC的回收的時間和效率似乎也是個不錯的選擇,不過目前沒有遇到過這種類似的情況的應用),
  
  單個執行緒所佔用堆疊記憶體的大小設定:-Xss256k,用於表示當前每個執行緒在建立時所對應的執行緒棧的空間記憶體的大小,具體可詳細看下 (書寫)java虛擬機器的記憶體區域分配(一個不斷記錄和推翻以及再記錄的一個過程)該篇記錄文章,此處主要是說明下Xss建立執行緒時所佔用的記憶體為作業系統的記憶體,而並非堆的記憶體空間,所以在設定堆的記憶體大小時,也需要留存下對應的作業系統記憶體,處理分配給對應的作業系統的應用使用外,還應留存對應的執行緒記憶體,也需要設定對應的伺服器當前應用程序可建立最大執行緒的引數設定,可參考所書寫的該篇文章; (*******書寫+csdn)由多執行緒記憶體溢位產生的實戰分析 - CSDN部落格
  
  以上,基本是一個預設在不配置GC收集器前對堆記憶體空間比例的基本設定,需要了接的是關於新生代中Eden和Suivivo的比例設定,在不進行配置的情況下JVM實際也是會給一個預設的自動引數配置的,並且以上關於JVM配置的比例引數皆是設定對JDK1.6的比例設定,所以對於永久代的設定並非是佔用的整個堆的記憶體比例進行設定的,而是使用的作業系統的記憶體進行的相關永久代記憶體的設定,------
  
  ---- 設定對應的GC收集器的引數配置:
  
  ---- ParNewGC  + CMS 收集器的配置
  
  -XX:+UseParNewGC                    -XX:ParallelGCThreads=4
  
  設定開啟ParNew收集器                設定當前並行收集器開啟的執行緒數量:,參考對應的url(http://www.blogjava.net/paulwong/archive/2014/06/16/414812.html)
  
  -XX:+UseCMSCompactAtFullCollection      -XX:CMSFullGCsBeforeCompaction=0        -XX:CMSInitiatingOccupancyFraction=80                      -XX:+CMSScavengeBeforeRemark
  
  設定當前CMS後執行碎片整理                             設定多少次FULL GC後進行碎片整理                    表示老年代記憶體達到使用率的80%時,則進行CMS GC           設定每次CMS GC的Remark前執行下Minor GC
  
  (上述4個引數可參考URL:http://www.cnblogs.com/onmyway20xx/p/6605324.html)
  
  -XX:+UseConcMarkSweepGC                         -XX:ParallelCMSThreads=4                                           -XX:MaxDirectMemorySize=256M       -XX:+CMSParallelRemarkEnabled
  
  設定開啟CMS 收集器                     CMS預設開啟執行緒數設定(預設為(ParallelGCThreads+3)/4)             堆外記憶體的大小設定(如ByteBuffer位元組快取時,則佔用的為堆外記憶體:檢視連結:http://hellojava.info/?tag=maxdirectmemorysize ,https://blog.csdn.net/aesop_wubo/article/details/38406709)              -----  CMS在Remark執行前,會執行一個可中斷的併發預清理(CMS-concurrent-abortable-preclean),預設為開啟CMSParallelRemarkEnable,此處使用-XX:+CMSParallelRemarkEnabled顯示開啟,(注:CMS的Remark階段是全量堆搜尋(old+xmn)得到存活物件,詳情可檢視:美團文章的案例二:https://mp.weixin.qq.com/s/t1Cx1n6irN1RWG8HQyHU2w)
  
  此處設定-XX:CMSInitiatingOccupancyFraction=80 的原因為:1. 每次CMS GC後,都設定了清理記憶體碎片,CMSFullGCsBeforeCompaction=0,所以不存在 晉升物件沒有連續的記憶體空間儲存而引起的CMS GC併發清理失敗的問題(CMS清理失敗將會使用serial old來進行STW全域性的FULL GC),2.每次CMS執行Remark階段時已經提前執行了Minor GC,所以新生代空間滿了以後的再次晉升一般不會特別快,第二,Remark階段時的Minor GC,儘管可能會存在新生代的物件的晉升,但老年代剩餘的20%比例,應該是足足可以存放下的,所以設定80%時觸發CMS的GC一般是OK的,當然80%的該值,也可以通過每次Minor GC晉升物件的大小取其平均值得到對應的大小,然後留下相對較為充足的空間比例也是合適的,。
  
  另:關於JVM何時會觸發STW的Full GC的機制可以檢視:美團文章的第三個案例(https://mp.weixin.qq.com/s/t1Cx1n6irN1RWG8HQyHU2w)
  
  另外:Full GC 和CMS的GC是不同的,CMS 的GC是單純的老年代GC,在GC日誌中對應的標識為:CMS-inital-mak,CMS-concurrent-mark-start,等CMS的日誌標識,
  
  可以檢視該圖片(https://mmbiz.qpic.cn/mmbiz_png/hEx03cFgUsV0a8RiaD6p2fV75LI7IdZVMia9ML0LccOY9bWeBvXzMbQnWaKmyhmWXDO1eU4U5X4YrvJdZpPE1pbQ/640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1),除了CMS的GC外,其他的老年代GC在執行時好像是都同時執行的FULL GC,上面也有提到過,關於CMS執行時的四個階段的特性和執行方式,也可以檢視美團的文章的案例2,以及該文章中對GC收集器的介紹,其中包含CMS的收集器的簡單介紹(https://www.cnblogs.com/zuoxiaolong/p/jvm8.html)
  
  -XX:+PrintTenuringDistribution 開啟jvm物件晉升年齡的列印 Desired survivor size 107347968 bytes, new threshold 1 (max 30)
  
  可參考推薦GC為:
  
  -Xmn512M -Xms1024M -Xmx1024M -XX:MaxPermSize=250M -Xss256k -Xconcurrentio -XX:SurvivorRatio=4 -XX:TargetSurvivorRatio=90 -XX:MaxTenuringThreshold=15 -XX:MaxDirectMemorySize=256M -XX:+UseParNewGC -XX:ParallelGCThreads=4 -XX:+UseConcMarkSweepGC -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=0 -XX:CMSInitiatingOccupancyFraction=80 -XX:+CMSScavengeBeforeRemark -Djava.nio.channels.spi.SelectorProvider=sun.nio.ch.EPollSelectorProvider -Dsun.net.inetaddr.ttl=60 -Dorg.mortbay.jetty.Request.maxFormContentSize=-1 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/app/ftdq_kbase/ibot_core_8013/logs  -XX:+PrintGCDetails  -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -Xloggc:/app/ftdq_kbase/ibot_core_8013/logs/gc.log
  
  -Xmn512M -Xms1024M -Xmx1024M -XX:MaxPermSize=250M -Xss256k -Xconcurrentio -XX:SurvivorRatio=4 -XX:TargetSurvivorRatio=90 -XX:MaxTenuringThreshold=15 -XX:MaxDirectMemorySize=256M -XX:+UseParNewGC -XX:ParallelGCThreads=4 -XX:+UseConcMarkSweepGC -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=0 -XX:CMSInitiatingOccupancyFraction=80 -XX:+CMSScavengeBeforeRemark -Djava.nio.channels.spi.SelectorProvider=sun.nio.ch.EPollSelectorProvider -Dsun.net.inetaddr.ttl=60 -Dorg.mortbay.jetty.Request.maxFormContentSize=-1 www.dfgjpt.com-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/app/ftdq_kbase/ibot_core_8013/logs www.thd178.com/ -XX:+PrintGCDetails -XX:+PrintTenuringDistribution -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -Xloggc:/app/ftdq_kbase/ibot_core_8013/logs/gc.log  -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.port=22223
  
  JAVA_OPTIONS="-Xmn1000M -Xms3000M -Xmx3000M -XX:MaxPermSize=512M -Xss128k -Xconcurrentio -XX:SurvivorRatio=5 -XX:TargetSurvivorRatio=90 -XX:+UseCMSInitiatingOccupancyOnly -XX:+CMSParallelRemarkEnabled -XX:+CMSPermGenSweepingEnabled -XX:MaxTenuringThreshold=31 -XX:CMSInitiatingOccupancyFraction=90 -Xloggc:/opt/jetty_kbase-search-7661/logs/gc.log -XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses www.tiaotiaoylzc.com-XX:+PrintGCDetails -XX:+PrintHeapAtGC -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -XX:+PrintTenuringDistribution -XX:+UseParNewGC -XX:+CMSParallelRemarkEnabled -XX:+UseConcMarkSweepGC -Djava.nio.channels.spi.SelectorProvider=sun.nio.ch.EPollSelectorProvider www.dfgjpt.com -Dsun.net.inetaddr.ttl=60 -Dorg.mortbay.jetty.Request.maxFormContentSize=-1 -Djava.rmi.server.hostname=172.16.9.55 -Dcom.sun.management.jmxremote.port=17661 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Dsolr.solr.home=/opt/jetty_kbase-search-7661/solr_home/solr www.yongshi123.cn www.mhylpt.com -Dsolr.library.home=/opt/jetty_kbase-search-7661/solr_home"
  
  此處需要注意一點,第三個引數中為search引數,新增了 +ExplicitGCInvokesConcurrentAndUnloadsClasses 標識,(參考:https://blog.csdn.net/aesop_wubo/article/details/38406709)
  
  由於系統中使用-XX:MaxDirectMemorySize=256M(上面提到的堆外記憶體的設定,用於檔案讀取拷貝時增加效率),但是堆外記憶體的設定後,發現每隔一小時會進行一次Full GC,但此時FUll GC時,老年代根本沒有用滿,且永久代也沒有用滿(方太上便是這種情況,也是一個小時觸發一次Full GC),根據上述的參考連結可知:觸發Full GC的目的主要是想要回收 堆外記憶體的回收,即Native所使用記憶體的回收,但Young GC時,不具備回收堆外記憶體的情況,所以會主動觸發Full GC進行記憶體回收,此處使用 ExplicitGCInvokesConcurrentAndUnloadsClasses  引數,可以將回收堆外記憶體的任務交給 CMS進行處理,CMS 的回收好處相比於Fulll GC的好處,此處則不再做累贅,通過使用CMS回收堆外記憶體的情況,則可以避免頻繁的Full GC,(FUll GC 期間對系統是有影響的,且是STW的,所以對於使用CMS和Full GC進行回收堆外記憶體,此處也應該根據實際需調整,因為CMS 在併發預清理階段也是STW的,不過合理的配置CMS,則回收時間應該也是最佳的),另外,提一下上述的一個問題,上述的 -XX:+CMSParallelRemarkEnabled 表示在CMS,Remark之前,進行一個可中斷的併發預清理,(此處其實可以不開啟,因為此處已經使用CMSScavengeBeforeRemark ,表示每次CMS前進行一次年輕代的回收,那麼 此時則沒有必要等待5秒或怎樣的一箇中斷的預清理了,此處做已備註,可考慮測試去除等操作)
  
  併發收集的引數預設 -XX:UseAdaptiveSizePolicy的開啟,將會全權管理記憶體分配,此時所設定的新生代的eden和survivor的比例配置將會失效,等,。
  
  CMS的設定,可以設定FUll GC前先進行下相關的Minor GC的回收,以及可以設定是否開啟對永久代的回收,(因為如果應用中存在較多的動態類,或使用String.inten()等將資料都放置到了對應的常量池中,則對永久代Perm的回收則也是有必要的, )除此之外,可以參考美團,或者jvm引數設定中對CMS的一些配置的說明,也是較為清晰和詳細的。
  
  物件每經歷一次Minor GC,年齡加1,達到“晉升年齡閾值”後,被放到老年代,這個過程也稱為“晉升”。顯然,“晉升年齡閾值”的大小直接影響著物件在新生代中的停留時間,在Serial和ParNew GC兩種回收器中,“晉升年齡閾值”通過引數MaxTenuringThreshold設