前言算法
本篇博客將結合博主在實際工做中對JVM的認識,以及前一段時間春節大流量下對Java後臺服務的一些參數設置的一個總結。若是你對JVM還不熟悉,能夠參考博主之前的博客:《對Java內存結構的一點思考和實踐》緩存
JVM體系結構
網絡
上圖,包含了組成JVM的各個要素,下面咱們簡單先來介紹下它們。多線程
類加載子系統:負責從文件系統或者網絡上加載CLASS信息,加載的信息存放在內存空間中的方法區。併發
方法區:就是存放類信息、常量信息、常量池信息等。ide
Java堆:在JVM啓動的時候會創建Java堆,Java堆是Java程序最主要的內存區域,也是JVM進行垃圾回收的核心區域,幾乎全部的對象都放置在Java堆中,而且堆空間是全部線程共享的。工具
Java棧:每一個虛擬機線程都會有一個私有的棧空間,即Java棧。在Java棧中保存着局部變量、方法參數、方法調用、返回值等信息。總之,這個空間和多線程有關係,和方法的執行有關係。性能
本地方法棧:要知道一些Java類的實現是依賴於native方法的,也就是一般用C編寫的本地方法,本地方法棧和Java棧相似。spa
垃圾收集系統:Java進行垃圾清理的機制,後文在詳細介紹。線程
PC寄存器:寄存器也是每一個線程私有的空間,JVM會爲每一個線程建立PC寄存器,在任意時刻,一個JAVA線程老是在執行一個方法,即當前線程的當前方法。實際上,若是當前方法不是本地方法,那麼會將當前方法的信息存入PC寄存器。在PC寄存器中,實際上,就是一個指針的概念,表明了當前線程的一個執行環境。在多線程進行上下文切換的時候,PC寄存器就發揮做用了。
執行引擎:負責執行虛擬機的字節碼。
堆、棧、方法區
能夠說,在內存當中,咱們最爲關心的就是堆、棧、方法區。下面咱們重點剖析下,它們三者之間的關係。
好比,有一個User類,有2個實例對象u1/u2,那麼存儲結構信息就如上圖所示。
堆,解決的是數據存儲的問題,即數據怎麼放,放在哪裏。即User類的2個實例對象都存放在堆中。
棧,解決的是程序的運行問題,即程序如何執行。說白了,u1/u2這2個局部變量,對真實對象的引用就存放在棧中。
方法區,是堆、棧的一個輔助區域,或者說是先決條件,沒有類信息等,如何建立對象呢。
Java堆能夠細分爲新生代、老年代。其中新生代存放新生的對象或者年齡不大的對象;老年代則存放老年對象。新生代分爲Eden、S0、S1這三個區域,SO/S1也稱爲from/to區域。S0/S1這兩個區域是大小相等而且能夠互換角色的空間,在後文的複製回收算法中在詳細描述它們。
在絕大多數狀況下,對象首先分配在Eden區域,在一次新生代回收後,若是對象還存活,則會進入S0/S1區域,以後每通過一次新生代回收,若是對象存活,它的年齡就加一,當年齡達到閥值後,就會進入老年代。
Java棧的結構
Java方法區,也稱之爲永久區(Perm)。方法區,它保存系統的類信息,好比類的字段、方法、常量池等信息。方法區的大小決定了系統能夠保存多少個類,若是系統定義了太多的類,而方法區空間不足,就有可能拋出內存溢出錯誤。
JVM參數設置
-XX:+PrintGC 使用這個參數,虛擬機啓動後,只要遇到GC,就會打印日誌
-XX:+PrintGCDetails 能夠查看詳細信息,包括各個區的狀況
-XX:+PrintCommandLineFlags 將給虛擬機設置的參數所有打印出來
-Xms200m 設置JAVA程序啓動時初始堆大小
-Xmx500m 設置JAVA程序能夠得到的最大堆大小
注意:在實際開發中,咱們通常狀況下,都會將堆的初始值和最大值設置成同樣的。這樣的好處在於減小程序運行時的垃圾回收次數,從而提升性能。
-Xmn70m 設置新生代的大小
注意:若是設置一個比較大的新生代,那麼勢必減小老年代的大小,因此這個參數對系統性能以及GC行爲有很大影響。在實際中,通常將新生代大小設置爲整個堆大小的1/3到1/4左右。也就是說新生代:老年代=1/2 到 1/3。總而言之,咱們應該儘量將對象預留在新生代,減小老年代的GC次數。
-XX:SurvivorRatio 用於新生代中Eden/from=Eden/to的比例
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/log/xxx.dump
開啓堆內存溢出OOM錯誤導出堆信息功能,並打印到指定路徑下
注意:實際上這個堆內存導出文件,能夠用相關內存分析工具進行分析。
-Xss1m 線程的最大棧空間
注意:這個參數直接決定了方法能夠調用的最大深度,特別是遞歸調用。
-XX:PermSize=64M
-XX:MaxPermSize=64M 方法區的初始、最大大小設置
-XX:MaxDirectMemorySize
注意:這個參數用於配置直接內存。什麼是直接內存,想想NIO,你就會明白。若是不設置,默認就是最大堆大小。所以若是直接內存若是沒有有效釋放空間,或者達到堆空間的最大大小,就會OOM。
-client/-server 事實上,在JDK1.7 64之後,就已經沒有這方面的事情了。
-XX:MaxTenuringThreshold=15 設置新生代對象進入老年代的對象的年齡閥值,默認就是15次
-XX:PretenureSizeThreshold
若是對象的大小比較大,沒法在Eden直接分配呢?會直接進入老年代!可是須要注意TLAB的優先分配。
-XX:+UseTLAB
-XX:+PrintTLAB
-XX:+TLABSize
使用、打印、設置TLAB大小。後文會介紹TLAB。
-XX:UseSerialGC 設置新生代、老年代使用串行回收器
-XX:UseParNewGC
-XX:ParallelGCThreads
設置新生代ParNew回收器,以及回收線程的個數
-XX:UseParallelOldGC
CMS相關參數後文介紹。
GC
垃圾收集算法
引用計數法
一句話,若是對象存在被引用,則計數器加一,失效則減一;若是計數器爲0,就能夠回收。
沒法解決循環引用的問題,並且計數器常常加減,操做頻繁,性能不佳。實際工做中並無使用。
標記清除法
分爲標記、清除階段。那麼怎麼標記呢?並非採用上面的引用計數法,而是基於ROOT樹尋找路徑來肯定是否應該被標記。被標記將被清除,這會致使空間碎片的問題。垃圾回收後的空間不是連續的,顯然不連續的內存空間的效率要低於連續內存空間的效率。
複製算法
核心思想就是將內存空間分爲2塊,每次只是用其中一塊。在垃圾回收時,將其中一塊A的沒法回收的留存對象所有COPY到另外一塊B內存中,而後清除塊A內存中的全部對象。垃圾回收時,反覆去交換這2塊空間的角色,完成垃圾收集工做。這裏,顯然避免了內存碎片的問題,可是須要注意的是複製的成本。若是有不少對象須要複製呢?咱們知道新生代的不少對象很不穩定,是會被頻繁回收的,所以對於新生代而言,這種算法的複製成本比較小,因此被普遍應用。相比老年代而言,大量的對象是穩定的,甚至是不會被回收的,所以複製成本太大了,不適合。
標記壓縮法
在標記清除算法的基礎上作了些改進,就是把存活的對象壓縮到內存的另外一端,然後進行垃圾清理,從而避免了內存碎片的問題。事實上,老年代採用的就是這種算法。
其實,爲何分爲新生代、老年代?說白了,就是想根據對象的特色進行分類,不一樣的特色就可使用不一樣的算法,這就是分代的好處。對於新生代、老年代而言,新生代回收頻率很高,可是每次回收耗時很短;而老年代回收頻率很低,耗時會相對較長,因此應該儘可能減小老年代的GC。
停頓現象
垃圾回收的任務就是識別和回收垃圾對象,完成內存清理動做。爲了讓GC高效的執行,大部分狀況下,會要求系統進入一個停頓的狀態,也就是暫停了全部的應用線程。由於只有這樣纔不會有新的垃圾產生,同時停頓也保證了系統在某一瞬間的一致性,有益於垃圾的標記。所以在進行GC的時候,會產生應用程序的停頓。
TLAB
Thread Local Allocate Buffer,線程本地分配緩存,一個線程專用的內存分配區域。在實際中,每一個線程勢必會用到內存,爲何不提早就爲每一個線程建立一個較小的專屬的內存區域呢?沒必要等到線程使用到內存的時候在去申請。這樣的話,會加速線程的運行速度,並且也避免了多線程的問題。固然,TLAB區域並不會太大。
垃圾收集器
串行回收器
所謂串行,就是單線程進行垃圾回收工做。新生代、老年代均可以使用。若是機器的並行性能比較差,能夠考慮這種,由於它的專一性、獨佔性會有不錯的表現。
並行回收器:ParNew
在串行回收器的基礎上進行功能加強,就是使用多線程。適用於新生代。顯然,對於並行能力較強的計算機而言,會有效縮短垃圾回收的實際時間。
並行回收器:ParallelGC
多線程獨佔+複製算法,新生代回收,很是關注系統的吞吐量,由於它提供參數來進行吞吐量的控制,好比設置最大垃圾收集停頓時間-XX:MaxGCPauseMillis。
並行回收器:ParallelOldGC
多線程獨佔+標記壓縮算法,老年代回收。
目前最主流的回收器:CMS
CMS,即Concurrent Mark Sweep,併發標記清除,採用標記算法,適用老年代,關注系統停頓時間。
須要注意的是,CMS並非獨佔的回收器,也就是說CMS回收過程當中,應用程序仍然在不斷工做,這是CMS回收器的一大優勢。也正由於如此,CMS回收,應用程序不停頓運行,垃圾繼續會產生,須要足夠的內存空間。CMS並不會等到應用程序飽和纔開始回收,而是根據指定的閥值開始回收。好比-XX:CMSInitiatingOccupancyFration設置爲68,就是說當老年代空間使用達到68%的時候,CMS開始回收。另外,CMS爲了不標記算法產生的內存碎片問題,提供了功能,好比-XX:+UseCMSCompactAtFullCollection可讓CMS回收一次後進行內存碎片整理,-XX:CMSFullGCBeforeCompaction參數能夠設置多少次CMS回收後,對內存進行一次壓縮整理。
G1回收器
目前應用並不普遍。其主要的思想在於分區算法,把內存區域劃分爲N多個小的獨立的空間,其實是想細粒度的對內存進行劃分,這樣就能夠細粒度的回收,而不是對整個空間進行垃圾回收,從而提高性能,並減小了GC停頓時間。
到這裏,JVM總結就結束了,但願對你有用吧~