人羣當中,一位叫java的小夥子正向周圍一衆人羣細數着本身取得的榮耀與輝煌。就在此時,c老頭和c++老頭緩步走來,看着被衆人圍住的java,c老頭感嘆地對着身旁的c++說道:「原覺得你就能夠挑起個人樑子一直走下去的。」 c++笑着迴應道:「江山代有才人出,這世界之後總會是90後甚至00後的天下!」 察覺到c和c++的java連忙走出人羣,說道:「兩位前輩謙虛了,這世界可還離不開兩位前輩,我只不過是站在了兩位前輩的肩上罷了。」 「你這小子但是解決了咱們很多的問題啊,像指針、多繼承、內存管理......那時,但是有不少程序員對咱們抱怨頗深!」c++誇讚道。 「還有Java Virtual Machine,真的是一個不錯的想法!」一旁的c補充道。 ...... Java虛擬機,一直都是都是咱們在學習Java的過程當中反覆說起的一個東西,那麼JVM具體是怎樣的呢?請看下圖:java
簡單說來,JVM的工做就是經過類加載系統將字節碼文件加載到內存當中去,加載到內存當中的數據,就從邏輯上造成了咱們看到的圖中的運行時數據區(內存模型),c++
隨後執行引擎操做/調度內存模型中數據執行程序。程序員
如今看到內存模型裏面的東西,你們是否有些眼熟呢?如今回想起本身面試時,遇到的JVM面試題是否是全是關於內存模型裏面的東西。好比:棧、堆、Eden、Survivor、GC等等。面試
public class Example{
public int add(){
int a = 3;
int b = 4;
int c = a + b;
return c;
}
public static void main(String[] args){
Example e1 = new Example();
e1.add();
//.......
}
//......
}複製代碼
這個栗子是在幹啥,不用多說吧!今天咱們就要來慢慢地剝開它,以往剝開吃得太快就沒有什麼感受了。算法
棧,全稱爲Java 虛擬機棧,線程私有,生命週期和線程一致。描述的是 Java 方法執行的內存模型:每一個方法在執行時都會牀建立一個棧幀(Stack Frame)用於存儲局部變量表
、操做數棧
、動態連接
、方法出口
等信息。每個方法從調用直至執行結束,就對應着一個棧幀從虛擬機棧中入棧到出棧的過程。數組
怎樣解釋上面的話呢?那就須要開始剝栗子了!刀來(大喝一聲)!架構
當栗子開始執行時,因爲只有一個main線程,於是JVM只須要爲main線程分配好棧區的內存(話句話說若是有多個線程,天然就會有多個棧區,而且爲各自線程私有)。OK! main繼續執行,就會遇到main()方法,遇到以後呢!JVM又會在棧區當中再劃出一個小塊來存放main()方法執行過程的數據,這一小塊區域也就是棧幀。main()方法執行過程當中又有一個add()方法出現了,一樣地,JVM又會再爲add()分配一個棧幀,同時壓入到棧區,之後再遇到其餘方法也是如此。固然,方法在執行完成以後,便會彈出並釋放內存,當線程中棧區的全部方法都返回以後,程序也就算是執行完畢了。併發
那麼棧幀又是何許物業?咦,我刀呢?算了,手撕吧。jvm
當咱們扯開棧區,撕開棧幀,一不當心,局部變量表、操做數棧、動態連接、方法出口......嘩啦啦地散落一地。性能
撿起add()棧幀的局部變量表和操做數棧就能夠看到這樣一個畫面,在執行栗子中add()方法中的三行代碼時,局部變量表和操做數棧的一個變化過程:首先,執行int a = 3;
局部變量表中會分配出一個int區域,表示爲a;同時iconst命令使得操做數棧中壓入了常量3,而後再由istore命令將3彈出,賦值給局部變量表中a。一樣,int b = 4;
這一行代碼也是如此。而後,int c = a + b;
從右往左開始,先執行a + b
,也就是iload命令從局部變量中取出a、b對應的值,再將iadd後的值push進操做數棧中,剩下的即是int c = 7
的操做了。
經過上面的栗子,就很容易明白;局部變量表,顧名思義就是存放每一個方法中的局部變量(即編譯器可知的各類基本類型(boolean、byte、char、short、int、float、long、double)、對象引用(reference 類型)和 returnAddress 類型(指向了一條字節碼指令的地址))所在處,如圖中的a、b。操做數棧,也就是存放的就是方法當中的各類操做數的臨時空間,又如栗子中的三、4。
動態連接:Class文件的常量池中存在有大量的符號引用,字節碼中的方法調用指令就以指向常量池的引用做爲參數,而將部分符號引用在運行期間轉化爲直接引用,這種轉化即爲動態連接。這個解釋當中會涉及到許多概念,好比常量池、符號引用等,要想理解這些概念,就須要去了解class文件的結構,內容太多就不在這裏詳細描述了。
方法出口:簡單來講,就是用於標記當前方法執行完成以後,應該返回到下一條指令執行位置。好比就上面的栗子而言,add()在執行完畢以後,就應該返回到e1.add()以後繼續執行main()後面的代碼。
對於絕大多數應用來講,這塊區域是 JVM 所管理的內存中最大的一塊。線程共享,主要是用於存放對象實例和數組。除此以外,堆區還涉及到JVM中一個很是重要的工做--GC(Garbage Collection)。
從圖中就能夠看出棧和堆之間的關係,對於new的對象,棧中局部變量表只會存放在堆中的地址引用,具體實例變量的空間分配都在堆中。
而堆中的內存區域又會劃分爲年輕代和老年代兩部分,其中通常狀況下年輕代佔1/3內存,老年代佔2/3內存;年輕代又被劃分爲Eden區(伊甸園區)和兩個Survivor區(倖存區),各自分別佔年輕代空間的8/十、/1/十、1/10。也就是說若是堆內存區域有600M,那麼年輕代200M、老年代400M、Eden區160M、S0區20M、S1區20M。
這樣劃分區域的目的是什麼呢?這個回答就關係到JVM的GC機制了。
首先,程序一開始,全部的實例對象都會生成在Eden區中,當Eden區滿了的時候,這時就會觸發minor gc,jvm使用gc roots的查找方式將非垃圾對象移動(複製算法)到S0區域中去,而且將Eden區中的其餘對象視爲垃圾對象,清空Eden區。
當實例對象再次充滿Eden區時,又會觸發minor gc;可是此次是將Eden區和S0區中的全部非垃圾對象移動到S1中,並清空Eden區和S0區;一樣下次minor gc時,就是將Eden區和S1區中的非垃圾對象轉移到S0中......固然,這個左手倒右手過程並非無休止的。在反覆minor gc的過程當中,每一個對象身上還有一個叫作分代年齡的屬性,每次minor gc對象的分代年齡就會加1,當達到15(默認狀況)時,這個對象就會被放到老年代中去,成爲長期存在的對象。除此以外,還有一種狀況,便是當從Eden區複製內容到Survivor區時,複製內容大小超過S0或S1任一區域一半大小,也會直接被放入到老年代中,因此老年代纔會須要那麼大的區域,否則怎麼抗得住這些年輕人這樣搞~~。
雖然老年代空間很大,但總會有滿了的時候,這時麻煩的事情就出現了——full gc。在full gc時,jvm會先觸發STW(Stop-The-World),也就是暫停其餘全部的java進程,回收整個內存模型當中的內存資源,從而形成用戶響應超時或者是系統無響應,這對於併發程序比較高的系統(好比秒殺活動)影響程度是極其之大的。
經過gc機制,咱們就能夠得出一個簡單有效的JVM優化辦法,那就是減小full gc的次數,如何減小呢?只須要調整老年代和年輕代的內存空間分配使得在minor gc的過程當中儘量的消除大部分的垃圾對象。好比這種java -Xmx3072 -Xms3072M -Xmn2048M -Xss1M
-Xmx3072M:設置JVM最大可用內存爲3072M。 -Xms3072M:設置JVM初始內存爲3072M。此值能夠設置與-Xmx相同,以免每次垃圾回收完成後JVM從新分配內存。 -Xmn2048M:設置年輕代大小爲2G。增大年輕代後,將會減少年老代大小。不過此值對系統性能影響較大,Sun官方推薦配置爲整個堆的3/8。 -Xss1M:設置每一個線程的堆棧大小。JDK5.0之後每一個線程堆棧大小爲1M,之前每一個線程堆棧大小爲256K。更具應用的線程所需內存大小進行調整。在相同物理內存下,減少這個值能生成更多的線程。
GC Roots:在上面的gc過程當中,咱們還提到了JVM是如何判斷垃圾對象的。簡單地來講,就是從gc roots的根出發(即局部變量表中的引用對象),一路沿着引用關係找,凡是可以被找到的對象都是非垃圾對象,而且會被移動到下一個它應該去的區域中。剩下的對象,會在區域清空時,一同被清理掉而無須關心。
除了棧、堆以外,還有程序計數器、方法區(元空間)、本地方法棧,這些相對比較容易理解。
程序計數器:用來記錄當前指令執行完成後下一條指令的位置,由執行引擎來完成相應的修改操做。
方法區(元空間):存放常量、靜態變量、類信息等。
本地方法棧:與java虛擬機棧相似,不過存放的是native方法執行時的局部變量等數據存放位置。由於native方法通常不是由java語言編寫的,常見的就是.dll文件當中的方法(由C/C++編寫),好比Thread
類中start()
方法在運行時就會調用到一個start0()
方法,查看源碼時就會看到private native void start0();
這個方法就是一個本地方法。本地方法的做用就至關因而一個「接口」,用來鏈接java和其餘語言的接口。
另外,對於new出來的對象,不管是在棧的局部變量表仍是在方法區中的空間中,存放的都只是對象在堆中的地址(引用),具體的空間分配是在堆中。
最後,最近不少小夥伴找我要Linux學習路線圖,因而我根據本身的經驗,利用業餘時間熬夜肝了一個月,整理了一份電子書。不管你是面試仍是自我提高,相信都會對你有幫助!目錄以下:
免費送給你們,只求你們金指給我點個贊!
也但願有小夥伴能加入我,把這份電子書作得更完美!
推薦閱讀: