JVM的基本結構如下:
主要結構如圖: 接下來分別介紹6個數據區的主要用途:
程序計數器占用的大小比較小,每個線程都會創建自己的程序計數器,當然它的生存周期也就跟線程的生存周期保持一直了,它的主要用途是跟蹤程序方法執行的路徑,它記錄了當前方法執行的操作和位置,在進行多線程調度的時候是需要進行CPU分片的,有了程序計數器就可以保護線程的執行情況,當獲得CPU使用權的時候能夠進行線程恢復.它一般存放的指令為:分支、循環、跳轉、異常處理等指令.如果是不是本地方法,這里存放就是字節碼指令的地址,如果是本地方法,它的值是undefined.這個區域通常來說比較小,也是JVM唯一沒有規定OutOfMemoryError的區域.
這個區域也是線程私有的區域,當每個方法進行執行的時候都會在棧區創建棧幀(stack frame),棧幀一般存放的局部變量表,操作數棧,動態連接與方法出口等信息.局部變量表存放的方法入口還有在編譯器就確定的基本數據類型(long與double類型占用兩個slot,其余占用一個)和引用類型和returnAddress類型(eturnAddress類型會被Java虛擬機的jsr、ret和jsr_w指令所使用。returnAddress類型的值指向一條虛擬機指令的操作碼。與前面介紹的那些數值類的原生類型不同,returnAddress類型在Java語言之中并不存在相應的類型,也無法在程序運行期間更改returnAddress類型的值。)這個區域確定了兩種異常情況,當方法調用深度超過JVM的允許值會拋出StackOverflow,如果動態的擴展無法申請到足夠多的內存也會拋出OutOfMemoryError,虛擬機規范并沒要求這個區域是否可擴展,但大部分實現的虛擬都是可擴展的.
本地方法棧與虛擬機棧基本一直,主要區別一個管理的目標是字節碼定義的方法棧,本地方法棧管理的是本地方法.這個區域的規范并不強制語言與數據結構,給跨平臺提供靈活的選擇性.
這是虛擬機中最大的一塊區域,也是開發者所指的JVM內存的主要含義,不僅僅是因為它所占比例一般最大,更重要的是垃圾回收的目標也主要在這個區域,這個區按照規范的定義是存放實例的唯一區域,但是隨著最新的技術發展(JIT技術與逃逸)發展,對象也不一定在這個區域存放.這個區域也是多個線程共享的,因此也會帶來多線程并發的復雜性.保持對象的一致性也是控制堆上的對象能夠對多個線程保持數據的一致.堆上主要存放對象實例和數組,當JVM啟動的時候就存在了堆區域,這與棧區不太一樣,棧是在方法開始調用的時候才開始創建.堆中的垃圾回收根據對象生存周期的不同來進行分代收集,主要的收集算法也有標記-清除,復制-整理,復制-壓縮等算法,根據這些算法也有一些串行,并行,甚至并發的垃圾收集器.在主流hotspot也分為年輕代(可以分為Eden和Survivor)和老年代.這個區域可以是連續的內存也可以只是邏輯連續的.
這個區域主要存放被JVM加載的類的信息,包括常量,靜態變量,即時編譯后的代碼,這個區域垃圾回收效果收益不大但是是必要的.主要的收集對象為常量和需要卸載的類,類的回收機制有特定的要求.,并且十分的嚴苛.這個區域別名為Non-heap.在hotspot中使用堆中的永久區(其他JVM實現一般沒所謂的永久區)來實現方法區,這樣將垃圾回收器直接覆蓋到了堆中的方法區(hotspot中的永久區),省去了專門為永久區進行垃圾收集的工作,但是這樣增加了堆溢出的風險,因此現在jdk逐漸講方法區移除到堆外.
屬于方法區的一個部分,存放Class類編譯器各種能夠確定的字面量和符號引用(和直接引用).字節碼格式中一般對其他區域要求嚴格,這個區域是比較寬松的.受到方法區大小限制,申請不到足夠的內存也會拋出OutOfMemoryError異常.
有些時候我們可以直接使用堆外內存,例如jdk1.4引入的java nio就用channel與buffer調用本地方法在堆外分配內存,使用堆上的DirectByteBuffer作為引用進行操作,這樣減少了堆內外數據交換,提升性能.當然這個區域的大小受到物理內存大小限制,申請不夠拋出OutOfMemoryError異常.
新聞熱點
疑難解答