1、虚拟机内存管理、运行时数据区、线程共享区、Java堆、新生代、老年代、Eden区域分配、方法区、线程独占区、虚拟机栈
1.Java虛擬機內存管理
1.1.運行時數據區[Runtime Data Area]
1.1.1.線程共享區
1.1.1.1.Java堆[heap]
1.1.1.1.1.新生代、老年代、Eden區域分配
1.1.1.1.2.年輕代(Young Generation)
1.1.1.1.3.老年代(Old Generation)
1.1.1.2.方法區[Method Area]
1.1.2.線程獨占區
1.1.2.1.虛擬機棧[VM Stack]
1.1.2.2.本地方法棧[Native Method stack]
1.1.2.3.程序計數器[Program Counter Register]
1.1.3.執行引擎
1.Java虛擬機內存管理
JVM = 類加載器(classloader)+執行引擎(execution engine)+運行時數據區域(runtime data area)
再如網上的一個圖:
方法區和堆是所有線程共享的:
1.1.運行時數據區[Runtime Data Area]
Java虛擬機在執行Java程序的過程中會把它管理的內存劃分為若干個不同的數據區域。這些區域都有各自的用途,以及創建和銷毀的時間,有的區域隨著虛擬機進程的啟動而存在,有些區域則是依賴用戶線程的啟動和結束而建立和銷毀。
1.1.1.線程共享區
1.1.1.1.Java堆[heap]
?存放對象實例。
?垃圾收集器管理的主要區域。
?新生代,老年代,Eden空間。
為java內存管理的最大一塊兒區域:
堆不夠出現:OutOfMemory
主要的參數:-Xmx -Xms
被所有線程共享,在虛擬機啟動時創建,用來存放對象實例,幾乎所有的對象實例都在這里分配內存。對于大多數應用來說,Java堆(Java Heap)是Java虛擬機所管理的內存中最大的一塊。Java堆是垃圾收集器管理的主要區域,因此很多時候也被稱做**”GC堆”**。如果從內存回收的角度看,由于現在收集器基本都是采用的分代收集算法,所以Java堆中還可以細分為:新生代和老年代;新生代又有Eden空間、From Survivor空間、To Survivor空間三部分。Java 堆不需要連續內存,并且可以通過動態增加其內存,增加失敗會拋出 OutOfMemoryError異常。
對于堆區大小,可以通過參數-Xms和-Xmx來控制,-Xms為JVM啟動時申請的最小heap內存,默認為物理內存的1/64但小于1GB;-Xmx為JVM可申請的最大Heap內存,默認為物理內存的1/4但小于1GB,默認當剩余堆空間小于40%時,JVM會增大Heap到-Xmx大小,可通過-XX:MinHeapFreeRadio參數來控制這個比例;當空余堆內存大于70%時,JVM會減少Heap大小到-Xms指定大小,可通過-XX:MaxHeapFreeRatio來指定這個比例。對于系統而言,為了避免在運行期間頻繁的調整Heap大小,我們通常將-Xms和-Xmx設置成一樣。
1.1.1.1.1.新生代、老年代、Eden區域分配
Java中的堆是JVM所管理的最大的一塊內存空間,主要用于存放各種類的實例對象。
在java中,堆被劃分成兩個不同的區域:新生代(Young)、老年代(Old)。新生代(Young)又被劃分為三個區域:Eden、From Survivor、To Survivor。
這樣劃分的目的是為了使JVM能夠更好的管理堆內存中的對象,包括內存的分配以及回收。
堆的內存模型大致為:
從圖中可以看出:堆大小=新生代+老年代。其中,堆的大小可以通過參數-Xms、-Xmx來指定。
以JDK1.6為例:
默認的,新生代(Young)與老年代(Old)的比例的值為1:2(該值可以通過-XX:NewRatio來指定),即:新生代(Young)=1/3的堆空間大小。老年代(Old)=2/3的堆空間大小。其中,新生代(Young)被細分為Eden和兩個Survivor區域,這兩個Survivor區域分別被命名為from和to,以示區分。Eden空間不足的時候,會把存活的對象轉移到Survivor中。
新生代大小可以由-Xmn來控制,默認的Eden: from: to = 8:1:1(可以通過參數-XX:SurvivorRatio來設定),即:Eden = 8/10的新生代空間大小,from = to = 1/10的新生代空間大小。
JVM每次只會使用Eden和其中的一塊Survivor區域來為對象服務,所以無論什么時候,總是有一塊Survivor區域是空閑著的。
因此,新生代實際可用的內存空間為9/10 (即90%)的新生代空間。
1.1.1.1.2.年輕代(Young Generation)
對象在被創建時,內存首先是在年輕代進行分配(注意,大對象可以直接在老年代分配)。當年輕代需要回收時會觸發Minor GC(也稱作Young GC)。
年輕代由Eden Space和兩塊相同大小的Survivor Space(又稱S0和S1)構成,可通過-Xmn參數來調整新生代大小,也可通過-XX:SurvivorRatio來調整Eden Space和Survivor Space大小。不同的GC方式會按不同的方式來按比值劃分Eden Space和Survivor Space,有些GC方式還會根據運行狀況來動態調整Eden、S0、S1的大小。
年輕代的Eden區內存是連續的,所以其分配會非???#xff1b;同樣Eden區的回收也非???#xff08;因為大部分情況下Eden區對象存活時間非常短,而Eden區采用的復制回收算法,此算法在存活比例很少的情況下非常高效,后面會詳細介紹)。
如果在執行垃圾回收之后,仍沒有足夠的內存分配,也不能再擴展,將會拋出OutOfMemoryError:java Heap Space異常。
1.1.1.1.3.老年代(Old Generation)
老年代用于存放在年輕代中經多次垃圾回收仍然存活的對象,可以理解為比較老一點的對象,例如緩存對象;新建的對象也有可能在老年代上直接分配內存,這主要有兩種情況:一種為大對象,可以通過啟動參數設置-XX:PretenureSizeThreshold=1024,表示超過多大時就不在年輕代分配,而是直接在老年代分配。此參數在年輕代采用Parallel Scavenge GC時無效,因為其會根據運行情況自己決定什么對象直接在老年代上分配內存;另一種為大的數組對象,且數組對象中無引用外部對象。
當老年代滿了的時候就需要對老年代進行垃圾回收,老年代的垃圾回收稱作Major GC(也稱作Full GC)。
老年代所占用的內存大小為-Xmx對應的值減去-Xmn對應的值。
1.1.1.2.方法區[Method Area]
方法區存放了要加載的類的信息(如類名,修飾符)、運行時常量池、已被虛擬機加載的類信息、final定義的常量、屬性(類中的field)和方法信息、靜態變量、即時編譯后的代碼等數據。
當開發人員調用類對象中的getName、isInterface等方法來獲取信息時,這些數據都來源于方法區。方法區是全局共享的,在一定條件下它也會被GC。當方法區使用的內存超過它允許的大小時,就會拋出OutOfMemory:PermGen Space異常。
在Hotspot虛擬機中,這塊區域對應的是Permanent Generation(持久代),一般的,方法區上執行的垃圾收集是很少的,因此方法區又被稱為持久代的原因之一,但這也不代表著在方法區上完全沒有垃圾收集,其上的垃圾收集主要是針對常量池的內存回收和對已加載類的卸載。在方法區上進行垃圾收集,條件苛刻而且相當困難,關于其回后面再介紹。
**運行時常量池(Runtime Constant Pool)**是方法區的一部分,用于存儲編譯期就生成的字面常量、符號引用、翻譯出來的直接引用(符號引用就是編碼是用字符串表示某個變量、接口的位置,直接引用就是根據符號引用翻譯出來的地址,將在類鏈接階段完成翻譯);運行時常量池除了存儲編譯期常量外,也可以存儲在運行時間產生的常量,比如String類的intern()方法,作用是String維護了一個常量池,如果調用的字符“abc”已經在常量池中,則返回池中的字符串地址,否則,新建一個常量加入池中,并返回地址。
JVM方法區的相關參數,最小值:–XX:PermSize;最大值–XX:MaxPermSize。
JVM用永久代(Permanet Generation)來存放方法區??梢酝ㄟ^-XX:PermSize和-XX:MaxPermSize來指定最小值和最大值。
和Java 堆一樣不需要連續的內存,并且可以動態擴展,動態擴展失敗一樣會拋出 OutOfMemoryError異常。對這塊區域進行垃圾回收的主要目標是對常量池的回收和對類的卸載,但是一般比較難實現,HotSpot虛擬機把它當成永久代(Permanent Generation)來進行垃圾回收。方法區邏輯上屬于堆的一部分,但是為了與堆進行區分,通常又叫”非堆”。運行時常量池(Runtime Constant Pool)運行時常量池是方法區的一部分。Class文件中的常量池(編譯器生成的各種字面量和符號引用)會在類加載后被放入這個區域。除了在編譯期生成的常量,還允許動態生成,例如 String 類的intern()。這部分常量也會被放入運行時常量池。
注:在JDK1.7之前,HotSpot使用永久代實現方法區;HotSpot使用 GC分代實現方法區帶來了很大便利;從JDK1.7開始HotSpot開始移除永久代。其中符號引用(Symbols)被移動到Native Heap中,字符串常量和類引用被移動到Java Heap中。在 JDK1.8 中,永久代已完全被元空間(Meatspace)所取代。元空間的本質和永久代類似,都是對JVM規范中方法區的實現。不過元空間與永久代之間最大的區別在于:元空間并不在虛擬機中,而是使用本地內存。因此,默認情況下,元空間的大小僅受本地內存限制。直接內存(Direct Memory)并不是虛擬機運行時數據區的一部分,也不是Java虛擬機規范中定義的內存區域,但是這部分內存也被頻繁地使用,而且也可能導致OutOfMemoryError異常出現。在JDK 1.4中新加入了NIO類,引入了一種基于通道(Channel)與緩沖區(Buffer)的I/O方式,它可以使用Native函數庫直接分配堆外內存,然后通過一個存儲在Java堆里的DirectByteBuffer對象作為這塊內存的引用進行操作。這樣能在一些場景中顯著提高性能,因為避免了在Java 堆和Native堆中來回復制數據。
1.1.2.線程獨占區
1.1.2.1.虛擬機棧[VM Stack]
線程私有的,它的生命周期與線程相同。虛擬機棧描述的是Java方法執行的內存模型:每個方法被執行的時候都會同時創建一個棧幀(Stack Frame)用于存儲局部變量表、操作棧、動態鏈接、方法出口等信息。每一個方法被調用直至執行完成的過程,就對應著一個棧幀在虛擬機棧中從入棧到出棧的過程。局部變量表存放了編譯期可知的各種基本數據類型(boolean、byte、char、short、int、float、long、double)、對象引用(reference類型),它不等同于對象本身,根據不同的虛擬機實現,它可能是一個指向對象起始地址的引用指針,也可能指向一個代表對象的句柄或者其他與此對象相關的位置)和return Address類型(指向了一條字節碼指令的地址)。局部變量表所需的內存空間在編譯期間完成分配,當進入一個方法時,這個方法需要在幀中分配多大的局部變量空間是完全確定的,在方法運行期間不會改變局部變量表的大小。該區域可能拋出以下異常:
當線程請求的棧深度超過最大值,會拋出StackOverflowError異常;棧進行動態擴展時如果無法申請到足夠內存,會拋出OutOfMemoryError異常。
存放方法運行時所需的數據,成為棧幀。
- 虛擬機棧描述的是Java方法執行的動態內存模型。
- 棧幀
?每個方法執行,都會創建一個棧幀,伴隨著方法從創建到執行完成。用于存儲局部變量表,操作數棧,動態鏈接,方法出口等。
方法執行的時候入棧,方法執行完成時出棧:
?局部變量表
- 存放編譯器可知的各種基本數據類型,引用類型,return Address類型。
- 局部變量表的內存空間在編譯期完成分配,當進入一個方法時,這個方法需要在幀分配多少內存是固定的,在方法運行期間是不會改變局部變量表的大小。
?大小
- StackOverflowError
- OutOfMemory
案例:
虛擬機棧占用的是操作系統內存,每個線程都對應著一個虛擬機棧,它是線程私有的,而且分配非常高效。一個線程的每個方法在執行的同時,都會創建一個棧幀(Statck Frame),棧幀中存儲的有局部變量表、操作站、動態鏈接、方法出口等,當方法被調用時,棧幀在JVM棧中入棧,當方法執行完成時,棧幀出棧。
局部變量表中存儲著方法的相關局部變量,包括各種基本數據類型,對象的引用,返回地址等。在局部變量表中,只有long和double類型會占用2個局部變量空間(Slot,對于32位機器,一個Slot就是32個bit),其它都是1個Slot。需要注意的是,局部變量表是在編譯時就已經確定好的,方法運行所需要分配的空間在棧幀中是完全確定的,在方法的生命周期內都不會改變。
虛擬機棧中定義了兩種異常,如果線程調用的棧深度大于虛擬機允許的最大深度,則拋出StatckOverFlowError(棧溢出);不過多數Java虛擬機都允許動態擴展虛擬機棧的大小(有少部分是固定長度的),所以線程可以一直申請棧,直到內存不足,此時,會拋出OutOfMemoryError(內存溢出)。
1.1.2.2.本地方法棧[Native Method stack]
HotSport虛擬機不區分,虛擬機棧和本地方法棧。
?虛擬機棧為虛擬機執行Java方法服務。
?本地方法棧為虛擬機執行native方法服務。
為JVM所調用到的Native即本地方法服務。
與虛擬機棧非常相似,其區別不過是虛擬機棧執行Java方法(也就是字節碼)服務,而本地方法棧則是為了虛擬機使用到的Native方法服務。虛擬機規范中對本地方法棧中的方法使用的語言、使用方式與數據結構并沒有強制規定,因此具體的虛擬機可以自由實現它。甚至有的虛擬機(譬如Sun HotSpot虛擬機)直接就把本地方法棧和虛擬機棧合二為一。與虛擬機棧一樣,本地方法棧區域也會拋出StackOverflowError和OutOfMemoryError異常。
本地方法棧用于支持native方法的執行,存儲了每個native方法調用的狀態。本地方法棧和虛擬機方法棧運行機制一致,它們唯一的區別就是,虛擬機棧是執行Java方法的,而本地方法棧是用來執行native方法的,在很多虛擬機中(如Sun的JDK默認的HotSpot虛擬機),會將本地方法棧與虛擬機棧放在一起使用。
1.1.2.3.程序計數器[Program Counter Register]
?程序計數器是一塊較小的內存空間,它可以看作是當前線程所執行的字節碼的行號指示器。
?程序計數器處于線程獨占區。
?如果線程執行的是Java方法,這個計數器記錄的是正在執行的虛擬機字節碼指令的地址。如果正在執行的是native方法,這個計數器的值為undefined。
?此區域是唯一一個在Java虛擬機規范中沒有規定任何OutOfMemoryError情況的區域。
行號計數器如下:
記錄當前線程所執行到的字節碼的行號。
線程私有,它的生命周期與線程相同??梢钥醋鍪钱斍熬€程所執行的字節碼的行號指示器。在虛擬機的概念模型里(僅是概念模型,各種虛擬機可能會通過一些更高效的方式去實現),字節碼解釋器工作時就是通過改變這個計數器的值來選取下一條需要執行的字節碼指令,如:分支、循環、跳轉、異常處理、線程恢復(多線程切換)等基礎功能。如果線程正在執行的是一個Java方法,這個計數器記錄的是正在執行的虛擬機字節碼指令的地址;如果正在執行的是Natvie方法,這個計數器值則為空(undefined)。程序計數器中存儲的數據所占空間的大小不會隨程序的執行而發生改變,所以此區域不會出現OutOfMemoryError的情況。
程序計數器是一個比較小的內存區域,可能是CPU寄存器或者操作系統內存,其主要用于指示當前線程所執行的字節碼執行到了第幾行,可以理解為是當前線程的行號指示器。字節碼解釋器在工作時,會通過改變這個計數器的值來取下一條語句指令。 每個程序計數器只用來記錄一個線程的行號,所以它是線程私有(一個線程就有一個程序計數器)的。
如果程序執行的是一個Java方法,則計數器記錄的是正在執行的虛擬機字節碼指令地址;如果正在執行的是一個本地(native,由C語言編寫完成)方法,則計數器的值為Undefined,由于程序計數器只是記錄當前指令地址,所以不存在內存溢出的情況,因此,程序計數器也是所有JVM內存區域中唯一一個沒有定義OutOfMemoryError的區域。
1.1.3.執行引擎
?即時編譯器[JITCompiler]
?垃圾收集[Garbage Collection]
1.1.4.本地庫接口[java Native Interface]
1.1.5.本地方法庫
1.1.6.類加載器子系統[classloader subsystem]
總結
以上是生活随笔為你收集整理的1、虚拟机内存管理、运行时数据区、线程共享区、Java堆、新生代、老年代、Eden区域分配、方法区、线程独占区、虚拟机栈的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 普兰金融是干什么的
- 下一篇: 2、垃圾回收算法(标记清除算法、复制算法