手上的這本《深刻理解Java虛擬機》這本書買來已接近2年,期間也是看看停停,現現在也才只看到前10章(來回倒騰的看)。寫這個專題的目的:一、做一個專題複習,老話說的好:好記性不如爛筆頭,正好也能夠把本身的一些理解記錄;二、我買的這本書大部分是基於1.六、1.7的,而如今都java11了,一些內容作了改變,但我仍是以JDK8做爲講解(畢竟高版本我也不太熟)。
做爲本系列的第一章:就從內存模型開始提及。java
我想你們剛畢業找工做面試的時候都被問過這種問題:Java的內存區域是如何劃分的?因而可知這塊仍是挺重要都。總的來講,Java虛擬機內存區域共分爲:程序計數器、虛擬機棧、本地方法棧、堆、方法區、直接內存、運行時常量池七6塊區域。下面將會一一講解。c++
其實從名字就能夠看出來,它是計數用的,咱們在程序中在執行if、while、try/catch的時候都是依賴於這個計數器。要知道Java是多線程編程語言,爲了在切換線程的時候程序計數器能恢復到正確的位置,每一個線程都會維護一個程序計數器,也就是說:程序計數器是線程私有的,同時它仍是內存區域惟一一個在Java虛擬機規範中沒有規範任何OOM狀況的區域。git
特色:github
這裏之因此稱虛擬機棧是由於後面還有一個本地方法棧。這裏的虛擬機棧指的就是咱們平時說的堆棧中的棧,在數據結構中咱們知道棧的特色是先進後出的,虛擬機棧描述的Java方法的執行模型。這裏我舉一個例子(爲了簡單,這裏就用js舉例):面試
function a(){ b(); } function b(){ c(); } function c(){ } a();
從上面能夠看出:a調用b,b調用c。執行開始的順序是:a>b>c。執行結束的順序是:c>b>a。正好符合棧的特性。
在咱們調用一個Java方法的時候:每一個方法都會建立一個棧幀(Stack Frame)。這裏的棧幀你就把它理解成C語言的結構體,只是一個數據結構而已。它存放的是:*局部變量表、操做數棧、動態連接等。算法
這裏又多了4個名詞,下面分別對這三個名詞做解釋。
a、局部變量表
由名字能夠知道它存放的是變量:局部變量和方法參數,它存放於方法的Code屬性的max_locals數據項。至於這個Code屬性是什麼,後續會有專門的文章介紹。咱們須要知道的是:系統不會爲局部變量賦予初始值(實例變量和類變量都會被賦予初始值)
b、操做數棧
Java虛擬機的解釋執行引擎被稱爲"基於棧的執行引擎",其中所指的棧就是指-操做數棧。
操做數棧是一個基於字節的數組,可是它不是基於數組的角標來索引,而是經過壓棧和出棧來訪問,這裏舉一個小例子:編程
// int a = 1 ; b = 2; c = a + b ; iload_0 // 將局部變量表中索引爲0的操做數壓入棧 iload_1 // 將局部變量表中索引爲1的操做數壓入棧 iadd // 將相加結果壓入棧 istore_2 // 從操做數棧中彈出結果真後放入局部變量表中索引爲2的位置
動態連接
這個我會有一個後續將會有一篇文章來介紹。數組
這這塊內存區域有可能發生兩種異常:StackOverflowError、OOM。這兩種異常都很好演示:數據結構
// StackOverflowError異常 function a(){ a(); } a(); // 演示OOM的話,則最好設置下堆內存 //-Xms=10M -Xmx=10M function a(){ int[] = new int[1024*10]; a(); }
特色:多線程
本地方法棧和虛擬機方法棧很相似,區別就是虛擬機爲的是Java方法服務,而本地方法棧則爲虛擬機使用的Native服務。這裏就涉及了一個概念:本地方法。那什麼是本地方法呢?
簡單地講,一個Native Method就是一個java調用非java代碼的接口
這個非Java代碼的接口多是c,也有多是c++。更多關於本地方法的內容就不過多展開。
對於大部分應用來講,Java堆是虛擬機管理內存中最大的一塊,它存放的內容是對象實例。根據Java虛擬機規範:絕大多數對象實例以及數組都都在堆上分配。(Class對象除外,它是存放在方法區)堆是垃圾回收器管理的主要區域,咱們知道現代收集器是基於分代收集算法,所以咱們能夠對Java對進行劃分:新生代、老年代。而後對新生代能夠再劃分:Eden空間、From Survivor、To Survivor空間。下面對這幾塊內存空間做介紹。
Eden
新生代的一塊內存空間,它是新小對象「出生」的地方,當Eden沒有足夠的空間進行分配的時候,發生一次Minor GC。
From、To
Survivor之因此會劃分兩塊區域,是因爲新生代的回收算法決定的。From、To這個不是固定的,並且To區域永遠是空的,Eden:Survior它們的默認比例是8:1,也就是說新生代的可用內存大小是8+1=9。當對象從Eden進入到From以後它的年齡設置爲1,每熬過一次Minor GC,那它的年齡就+1,當年齡到達必定的值(默認15)就會進入到老年代。
注意: 虛擬機並並非永遠要求對象的年齡達到咱們設置的值或者默認值15才能進入到老年代,若是Survivor中相同年齡全部對象大小總和大於Survivor空間的一半,那麼年齡大於或者等於該年齡對象的就能夠直接進入到老年代。
老年代
老年代存放的是長期存活的對象和大對象,這裏的大對象多是大字符串和大數組。
MinorGC
Minor GC:發生在新生代的垃圾回收動做,由於大多數Java對象都是朝生夕死,所以此次回收會很頻繁,速度也會很快。
Major GC(Full GC):老年代的垃圾回收動做,Full GC的速度通常比Minor GC慢10倍以上。
永久代
其實一開始我一直理不清方法區和永久代之間的概念,最近才整明白。方法區是Java虛擬機規範的叫法,而永久代是Hotspot的叫法,咱們能夠將永久代當成對方法區的一種實現,並且Java8已經去永久代。永久代是一片連續的堆空間,能夠經過-XX:MaxPermSize來設置。永久代的垃圾回收是和老年代捆綁在一塊兒的,所以不論那個滿了都會觸發二者的垃圾回收。
JDK1.7中,存儲在永久代的部分數據就已經轉移到了Java Heap或者是 Native Heap,譬如符號引用(Symbols)轉移到了native heap;字面量(interned strings)轉移到了java heap;類的靜態變量(class statics)轉移到了java heap。
元數據
元數據是jdk8出來的,它和永久代相似,最大的區別是元空間並不在虛擬機中,而是使用本地內存。
-XX:MetaspaceSize // 初始空間大小 -XX:MaxMetaspaceSize // 最大空間
至於爲何要做永久代到元數據之間的轉換,我想主要有兩個緣由:
運行時常量池,屬於方法區的一部分,用於存放編譯期生成的各類字面量和符號引用。對於運行時常量池,不一樣產商有不一樣實現。還有兩個個相似的名詞叫:字符串常量池、class文件常量池,下面來分別介紹這三者。
字符串常量池
符串池裏的內容是在類加載完成,通過驗證,準備階段以後在堆中生成字符串對象實例,而後將該字符串對象實例的引用值存到string pool中(記住:string pool中存的是引用值而不是具體的實例對象,具體的實例對象是在堆中開闢的一塊空間存放的)
在HotSpot VM裏實現的string pool功能的是一個HashTable類,它是一個哈希表,裏面存的是駐留字符串(也就是咱們常說的用雙引號括起來的)的引用(而不是駐留字符串實例自己)
,也就是說在堆中的某些字符串實例被這個StringTable引用以後就等同被賦予了」駐留字符串」的身份。這個StringTable在每一個HotSpot VM的實例只有一份,被全部的類共享。
class文件常量池
咱們知道class類結構最前面除了魔數、主次版本號以後就是常量池了,這個常量池就是咱們說的class文件常量池,它存放的是咱們編譯生成的各類字面量和符號引用。
符號引用:符號引用是一組符號來描述所引用的目標,符號能夠是任何形式的字面量,只要使用時能無歧義地定位到目標便可(它與直接引用區分一下,直接引用通常是指向方法區的本地指針,相對偏移量或是一個能間接定位到目標的句柄)。通常包括下面三類常量:類和接口的全限定名、字段的名稱和描述符、方法的名稱和描述符。
運行時常量池
當java文件被編譯成class文件以後,也就是會生成我上面所說的class常量池。當jvm在執行某個類的時候,必須通過加載、鏈接、初始化,而鏈接又包括驗證、準備、解析三個階段。而當類加載到內存中後,jvm就會將class常量池中的內容存放到運行時常量池中
,由此可知,運行時常量池也是每一個類都有一個。在上面我也說了,class常量池中存的是字面量和符號引用,也就是說他們存的並非對象的實例,而是對象的符號引用值。而通過解析(resolve)以後,也就是把符號引用替換爲直接引用,解析的過程會去查詢全局字符串池,也就是咱們上面所說的HashTable,以保證運行時常量池所引用的字符串與全局字符串池中所引用的是一致的。
小結
1.全局常量池在每一個VM中只有一份,存放的是字符串常量的引用值
2.class常量池是在編譯的時候每一個class都有的,在編譯階段,存放的是常量的符號引用
3.運行時常量池是在類加載完成以後,將每一個class常量池中的符號引用值轉存到運行時常量池中,也就是說,每一個class都有一個運行時常量池,類在解析以後,將符號引用替換成直接引用,與全局常量池中的引用值保持一致
直接內存不屬於虛擬機運行時數據區的一部分,它不是Java虛擬機規範定義的區域,在JDK1.4加入了NIO咱們能夠直接操做堆外內存,在RPC通訊中咱們常常會使用到NIO,著名框架Netty就是基於此。直接內存不受Java堆的限制,可是收到本機的內存限制。
本篇主要就JVM的內存模型做了介紹,主要介紹了虛擬機棧、堆、常量池,這三個也是咱們平時用的比較多的,固然也不表明其它不重要。
一些嘮叨:剛畢業那會也常常好奇爲何如今公司都喜歡面試造飛機、工做擰螺絲,如今也逐漸慢慢有體會,由於螺絲絕大部分人都會擰啊。花通樣的薪資確定人家不肯意就招個擰螺絲的。並且,一些原理性東西對長遠的工做確實有益,反正學到手總不是壞事。
歡迎關注公衆號:碼農有道
參考《深刻理解Java虛擬機》,Java中幾種常量池的區分