認(ren)識JVM的內存分配
當我們在JVM中運行一段程序代碼,JVM初始運行的時候都會分配好Method Area(方法區)和Heap(堆),而(er)JVM每遇(yu)到一(yi)個線程(cheng)(cheng),就為其分配一(yi)個Program Counter Register(程(cheng)(cheng)序計數器), VM Stack(虛擬機棧(zhan))和Native Method Stack (本地方法棧(zhan)),當線程(cheng)(cheng)終止時,三者(虛擬機棧(zhan),本地方法棧(zhan)和程(cheng)(cheng)序計數器)所占用的內存空間也(ye)會被釋放(fang)掉。
這也是為什么我把內存(cun)區域分為線(xian)(xian)程(cheng)(cheng)共(gong)享(xiang)(xiang)和非線(xian)(xian)程(cheng)(cheng)共(gong)享(xiang)(xiang)的原因(yin),非線(xian)(xian)程(cheng)(cheng)共(gong)享(xiang)(xiang)的那三(san)個區域的生命(ming)周(zhou)期與所屬線(xian)(xian)程(cheng)(cheng)相(xiang)同,而線(xian)(xian)程(cheng)(cheng)共(gong)享(xiang)(xiang)的區域與JAVA程(cheng)(cheng)序(xu)運行的生命(ming)周(zhou)期相(xiang)同,所以這也是系統垃圾回收的場所只發生在線(xian)(xian)程(cheng)(cheng)共(gong)享(xiang)(xiang)的區域的原因(yin)。
No.1 程序計數器
程序計數器(qi)是(shi)一塊較小的(de)內存區域,作用可以(yi)看做(zuo)是(shi)當前線程執行的(de)字節碼的(de)位置指示器(qi)。分(fen)支、循(xun)環、跳轉、異常處理和線程恢復等(deng)基礎功(gong)能都需(xu)要依賴這個計算器(qi)來完成。
No.2 虛擬機棧
虛擬機棧也叫棧內存,是在線程創建時創建,它的生命期是跟隨線程的生命期,線程結束棧內存也就釋放,對于棧來說不存在垃圾回收問題,只要線程一結束,該棧就釋放了,所以不存在垃圾回收。
它所描述的(de)是java方(fang)法執行(xing)的(de)內存模型(xing),每個(ge)方(fang)法執行(xing)的(de)同時創(chuang)建幀棧(Strack Frame)用于(yu)存儲局(ju)部(bu)變量表(包含了對(dui)應(ying)的(de)方(fang)法參數(shu)和(he)(he)局(ju)部(bu)變量),操(cao)作棧(Operand Stack,記(ji)錄出(chu)棧、入棧的(de)操(cao)作),動態鏈接(jie)、方(fang)法出(chu)口等(deng)信息,每個(ge)方(fang)法被(bei)調用直到(dao)執行(xing)完畢(bi)的(de)過(guo)程,對(dui)應(ying)這幀棧在虛(xu)擬機棧的(de)入棧和(he)(he)出(chu)棧的(de)過(guo)程。
2.1、局部變量表
局部變量表存放了編譯期可知的各種:
基本數據類型(boolean、byte、char、short、int、float、long、double)
對象的引用(reference類型,不等同于對象本身,根據不同的虛擬機實現,可能是一個指向對象起始地址的引用指針,也可能是一個代表對象的句柄或者其他與對象相關的位置)
returnAdress類型(指向下一條字節碼指令的地址)
局部(bu)變量表所需(xu)的內存空(kong)間(jian):在編譯(yi)期間(jian)分配,運行(xing)期間(jian)不改(gai)變(大小(xiao)固定)。
2.2、棧幀
棧幀是一個內存區塊,是一個數據集,是一個有關方法(Method)和運行期數據的數據集,當一個方法 A 被調用時就產生了一個棧幀 F1,并被壓入到棧中,A 方法又調用了 B 方法,于是產生棧幀 F2 也被壓入棧,執行完畢后,先彈出 F2棧幀,再彈出 F1 棧幀,遵循“先進后出”原則。
No.3 堆
Heap(堆)是JVM的內存數據區。該區域是被所有線程共享的內存區域,在JVM啟動時候創建,專門用來保存對象的實例。
在Heap中分配一定的內存來保存對象實例,實際上也只是保存對象實例的屬性值,屬性的類型和對象本身的類型標記等,并不保存對象的方法(以幀棧的形式保存在Stack中)。而對象實例在Heap中分配好以后,需要在Stack中保存一個4字節的Heap內存地址,用來定位該對象實例在Heap中的位置,便于找到該對象實例。
堆是線程共享的內存區域,在JVM啟動時候創建,專門用來保存對象的實例。
java堆處于物理不連續的內存空間中,只要邏輯上連續即可。
垃圾(ji)回收的主(zhu)要場所。
No.4 方法區
方法區存放了:
- 加載類的類定義數據
- 常量和靜態變量
- JIT(即時編譯器)編譯后的代碼
方法區也可以是內存不連續的區域組成的,并且可設置為固定大小,也可以設置為可擴展的
垃圾回(hui)收(shou)在這(zhe)(zhe)個(ge)區域會比較(jiao)少出現(xian),這(zhe)(zhe)個(ge)區域內存回(hui)收(shou)的(de)(de)目的(de)(de)主要針(zhen)對(dui)常量(liang)池的(de)(de)回(hui)收(shou)和類的(de)(de)卸載。
No.5 運行時常量池(Runtime Constant Pool)
方法區內部有一個非常重要的區域,叫做運行時常量池(Runtime Constant Pool,簡稱 RCP)。
在字節碼文件(Class文件)中,除了有類的版本、字段、方法、接口等相關信息描述外,還有常量池(Constant Pool Table)信息,用于存儲編譯器產生的字面量和符號引用。這部分內容在類被加載后,都會存儲到方法區中的RCP。值得注意的是,運行時產生的新常量也可以被放入常量池中,比如 String 類中的 intern() 方法產生的常量。
常量(liang)池(chi)就(jiu)是(shi)這(zhe)個類(lei)型用(yong)到的常量(liang)的一個有序集合(he)。包(bao)括直(zhi)接常量(liang)(基本類(lei)型,String)和(he)對其他類(lei)型、方法、字段的符號(hao)引(yin)用(yong).例如(ru):
- 類和接口的全限定名;
- 字段的名稱和描述符;
- 方法和名稱和描述符。
池(chi)中的(de)數據(ju)和數組一樣通過索引(yin)訪(fang)問。由于常量(liang)池(chi)包含(han)了一個類(lei)型(xing)所有(you)的(de)對其(qi)他類(lei)型(xing)、方法(fa)、字段(duan)的(de)符號引(yin)用,所以常量(liang)池(chi)在Java的(de)動態(tai)鏈接中起了核(he)心作用.
No.6 本地方法棧
與VM Strack相似,VM Strack為JVM提供(gong)執行JAVA方法(fa)的服務,Native Method Stack則為JVM提供(gong)使(shi)用native 方法(fa)的服務。
No.7 直接內存區
直接(jie)內(nei)存(cun)區(qu)并不是JVM 管(guan)理(li)的(de)內(nei)存(cun)區(qu)域的(de)一部分,而(er)是其之外的(de)。該區(qu)域也會在 Java 開發中使(shi)用(yong)到,并且存(cun)在導致內(nei)存(cun)溢出的(de)隱患(huan)。如果你對 NIO 有所了解,可(ke)能(neng)會知道 NIO 是可(ke)以(yi)使(shi)用(yong) Native Methods 來(lai)使(shi)用(yong)直接(jie)內(nei)存(cun)區(qu)的(de)。
JVM內存區域可以分為線程共享和非線程共享兩部分,
線程共享的有堆和方法區,非線程共享的有虛擬機棧,本地方法棧和程序計數器。