本文參考Jvm規范文檔(https://docs.oracle.com/javase/specs/jvms/se14/html/jvms-2.html),描述的是一個抽象的JVM引擎相關內容。
一.概述
首先關於jvm內存結構,會有6個部分,這6部分根據這3點加以區分:必須實現還是選擇實現;每個線程私有還是所有線程共享;出現異常StackOverflowError可能發生部分,異常OutOfMemoryError可能發生的部分。
二.JVM內存結構
根據規定虛擬機結構並不是虛擬機規范嚴格所限制的,實現基本讀取class文件和一些操作外,不同執行引擎會不同,例如運行時數據區在內存的布局,垃圾回收器算法的使用,或者優化如何翻譯成機器碼。
對於運行時數據區域,也就是jvm內存,有6個結構,他們有的是在jvm生命周期而初始化或者銷毀的,有的則是跟隨線程的生命周期初始化或銷毀。
1.程序計數寄存器( program counter Register),虛擬機是多線程的,每個線程都有自己的計數寄存器,線程的當前方法如果是不是本地(native)方法,計數寄存器記錄的是jvm正在執行指令的地址;如果是本地方法,計數寄存器的值是未定義(undefined)
2.虛擬機棧(Java Virtual Machine Stacks),每個線程都有一個jvm棧(這是棧一段內存空間,不是數據結構的棧,和數據結構棧類似,fifo),保存在楨(frames)里;jvm棧在方法調用時創建,並保存了當前線程的本地變量和局部變量,保存jvm棧的楨可能是在堆(heap)上分配的,而且內存結構上不要去jvm棧是連續的。
jvm棧創建時可以是每個單獨設置固定大小或者統一動態伸縮的方式,動態伸縮是通過初始化參數的最大值和最小值(maximun and minimum size)控制的。
有兩個著名的錯誤就和jvm棧有關:StackOverflowError,由於線程需要的jvm棧大於能夠提供的大小;OutOfMemoryError ,由於jvm棧嘗試動態擴張的時候沒有足夠的內存空間,或者新線程要創建jvm棧但是內存空間不足;
3.堆(Heap),堆是被所有線程所共享的,它是jvm啟動時候就初始化的運行時數據區域,所有類的實例和數組都是從這里分配的內存。堆存儲的對象不會明確的釋放,但是會被自動存儲管理系統(垃圾回收gc)進行回收的,jvm規范沒有要求gc清除特定的類型,具體選擇由實現者去決定;堆可能也是固定大小或者動態擴縮容量的,動態伸縮控制也是參數堆最大最小值,內存結構上也是不要求連續性。
同樣當計算需要堆的大小超過gc能提供的大小同樣出現OutOfMemoryError
4.方法區(Method Area),方法區也是被所有線程所共享的,它是jvm啟動時就初始化,用來保存了每個類的結構,例如運行時常量池,屬性和方法的數據,特殊方法和構造器的代碼。盡管方法區邏輯上是堆的一部分,但是jvm規范並沒有要求有方法區,或者方法區如何管理編譯的代碼,所以實現方式中可能是被gc回收或壓縮,也是同堆一樣內存不要求連續,可通過參數調整,也會出現OutOfMemoryError
5.運行時常量池(Run-Time Constant Pool),運行時常量池保存了在class文件里屬於常量池表(constant_pool table)的內容(常量池表,是為了方便jvm指令引用,包含17種常量,https://docs.oracle.com/javase/specs/jvms/se14/html/jvms-4.html#jvms-4.4),運行時常量池提供的功能類似一般編程語言的符號表,但是它包含內容遠多於符號表,既能存放編譯時確定的數字字面值,也能保存運行時才確定的方法或者屬性的引用對象。運行時常量池分配在方法區內,會在jvm加載類或者接口時候就被構造好。
當創建類或者接口時如果構造需要的空間不能被方法區滿足也會出現OutOfMemoryError
6.本地方法棧(Native Method Stacks),jvm可能用到傳統的棧,用來配合本地方法(不實用java寫的方法),本地方法也可能會用到jvm的指令集。jvm的實現如果不要本地方法或者本地方法用的傳統棧,那么本地方法棧就可以不用提供,但是要用的話就得在每個用到本地方法的線程分別分配本地方法棧。本地方法棧可以時固定或者動態伸縮的,如果是固定大小,每個本地方法棧就要單獨分配大小。本地方法棧類似jvm棧,所以當計算中需要的本地方法棧大於可以提供的大小,出現StackOverflowError;本地方法棧擴張時候沒有足夠空間,或者新線程創建本地方法棧沒有足夠空間,出現OutOfMemoryError。
三.總結
按照規范理解來看:
是否存在角度,方法區,在方法區上的運行時常量池,還有本地方法棧是jvm規范沒有強制要求有的結構,剩下的程序計數寄存器,jvm棧,堆是一定會有的;
是否線程私有角度,程序計數寄存器,jvm棧,本地方法棧是線程私有每個線程一個,堆,方法區,運行時常量池是所有線程共享;
異常表現角度,StackOverflowError 發生的地方有jvm棧和本地方法棧,它們在線程中所需大小超過允許大小,OutOfMemoryError 發生在jvm棧,本地放方法棧,堆,方法區,他們擴張或創建的過程沒有足夠空間;
最后本文內容是我的淺薄理解,如有任何誤解地方感謝指教。