2019-09-19

?????????????????????????????????????????????????????????????? Java JVM內(nèi)存組成





JVM的內(nèi)存被劃分5個區(qū)域: 堆區(qū)、方法區(qū)——這兩個區(qū)域的數(shù)據(jù)共享 虛擬機棧、本地方法棧、程序計數(shù)器——這三個區(qū)域的數(shù)據(jù)私有隔離,不可共享 接下來詳細敘述一下各個區(qū)域的作用: 1、堆區(qū) 堆區(qū)是JVM中最大一塊內(nèi)存區(qū)域,存儲著各類生成的對象、數(shù)組等,JVM8中把運行時常量池、靜態(tài)變量也移到堆區(qū)進行存儲。堆區(qū)被細化可以分為年輕代、老年代,而年輕代又可分為Eden區(qū)、From Survivor、To Survivor三個區(qū)域,比例是8:1:1。一個對象從生成到結(jié)束將會有機會經(jīng)歷堆區(qū)的不同區(qū)域完成“使命”。根據(jù)Java虛擬機規(guī)范的規(guī)定,Java堆可以處于物理上不連續(xù)的內(nèi)存空間中,只要邏輯上是連續(xù)的即可,就像我們的磁盤空間一樣。在實現(xiàn)時,既可以實現(xiàn)成固定大小的,也可以是可擴展的,不過當前主流的虛擬機都是按照可擴展來實現(xiàn)的(通過-Xmx和-Xms控制)。如果在堆中沒有內(nèi)存完成實例分配,并且堆也無法再擴展時,將會拋出OutOfMemoryError異常 問題:什么是運行時常量池? 這個問題其實我在講解String數(shù)據(jù)類型的時候已經(jīng)詳細敘述過,簡單的來class文件在編譯后除了存儲一些類的版本、字段、方法、接口等元數(shù)據(jù)信息外,還有一部分信息是常量池,這個常量池我們稱之為“靜態(tài)常量池”,只是作為一種持久化數(shù)據(jù)存儲在硬盤上,代表編譯期生成的各種字面量和符號引用(最常見的就是字符串常量),那么這類信息被加載到內(nèi)存中就會以運行時常量池的形式存在內(nèi)存中,JDK7以前這類信息被存儲在方法區(qū),但是JDK7/JDK8都已經(jīng)移到了堆區(qū)。這類數(shù)據(jù)變量的好處簡單來說就是如果堆區(qū)中已經(jīng)存在一個數(shù)據(jù)變量,即使再創(chuàng)建一個這樣的變量,那么JVM將會直接指向已經(jīng)創(chuàng)建好的數(shù)據(jù),而不會再分配內(nèi)存區(qū)域,這樣一方面加快數(shù)據(jù)的創(chuàng)建,另一方面節(jié)省內(nèi)存空間!但是實際上的機制要復雜一些,可以參考我之前講述的String類去理解! 2、方法區(qū) 方法區(qū)主要是存儲類的元數(shù)據(jù)的,如虛擬機加載的類信息、編譯后的代碼等。JDK8之前方法區(qū)的實現(xiàn)是被稱為一種“永久代”的區(qū)域,這部分區(qū)域使用JVM內(nèi)存,但是JDK8的時候便移除了“永久代(Per Gen)”,轉(zhuǎn)而使用“元空間(MetaSpace)”的實現(xiàn),而且很大的不同就是元空間不在共用JVM內(nèi)存,而是使用的系統(tǒng)內(nèi)存,有個測試可以很容易的證明這一點——我們現(xiàn)在通過動態(tài)生成類來模擬 “PermGen space”的內(nèi)存溢出: 假設我們有這段程序:package com.paddx.test.memory;import java.io.File;import java.net.URL;import java.net.URLClassLoader;import java.util.ArrayList;import java.util.List;public class PermGenOomMock{ public static void main(String[] args) { URL url = null; ListclassLoaderList = new ArrayList(); try { url = new File("/tmp").toURI().toURL(); URL[] urls = {url}; while (true){ ClassLoader loader = new URLClassLoader(urls); classLoaderList.add(loader); loader.loadClass("com.paddx.test.memory.Test"); } } catch (Exception e) { e.printStackTrace(); } } } 以下是測試結(jié)果: 即通過類加載的機制,不斷加載新的類到內(nèi)存區(qū)域中,在JDK7和JDK8中,我們分別設置永久代和元空間的大小,我們發(fā)現(xiàn)此時內(nèi)存溢出的分別是永久代和元空間,也就說在JDK8中,方法區(qū)的實現(xiàn)已經(jīng)由永久代轉(zhuǎn)變成了元空間 3、虛擬機棧 我們通常所說的“方法入棧”、“棧區(qū)”其實指代的就是虛擬機棧。但是實際上這個并不準確,我們所說的“棧區(qū)”等稱呼確切的說指代的是虛擬機棧中局部變量表的部分。 Java虛擬機棧(Java Virtual Machine Stacks)也是線程私有的,它的生命周期與線程相同。虛擬機棧描述的是Java方法執(zhí)行的內(nèi)存模型:每個方法被執(zhí)行的時候都會同時創(chuàng)建一個棧幀(Stack Frame)用于存儲局部變量表、操作棧、動態(tài)鏈接、方法出口等信息。每一個方法被調(diào)用直至執(zhí)行完成的過程,就對應著一個棧幀在虛擬機棧中從入棧到出棧的過程。 局部變量表存放了編譯期可知的各種基本數(shù)據(jù)類型(boolean、byte、char、short、int、float、long、double)、對象引用(reference類型,它不等同于對象本身,根據(jù)不同的虛擬機實現(xiàn),它可能是一個指向?qū)ο笃鹗嫉刂返囊弥羔槪部赡苤赶蛞粋€代表對象的句柄或者其他與此對象相關的位置)和returnAddress類型(指向了一條字節(jié)碼指令的地址)。 其中64位長度的long和double類型的數(shù)據(jù)會占用2個局部變量空間(Slot),其余的數(shù)據(jù)類型只占用1個。局部變量表所需的內(nèi)存空間在編譯期間完成分配,當進入一個方法時,這個方法需要在幀中分配多大的局部變量空間是完全確定的,在方法運行期間不會改變局部變量表的大小。 在Java虛擬機規(guī)范中,對這個區(qū)域規(guī)定了兩種異常狀況:如果線程請求的棧深度大于虛擬機所允許的深度,將拋出StackOverflowError異常;如果虛擬機??梢詣討B(tài)擴展(當前大部分的Java虛擬機都可動態(tài)擴展,只不過Java虛擬機規(guī)范中也允許固定長度的虛擬機棧),當擴展時無法申請到足夠的內(nèi)存時會拋出OutOfMemoryError異常。 4、本地方法棧 從英文中我們可以很容易的看出,其實這部分區(qū)域是專門為Native方法來實現(xiàn)的!由于java需要與一些底層系統(tǒng)如操作系統(tǒng)或某些硬件交換信息時的情況。本地方法正是這樣一種交流機制:它為我們提供了一個非常簡潔的接口,而且我們無需去了解java應用之外的繁瑣的細節(jié) ,一個Native Method就是一個java調(diào)用非java代碼的接口。方法對應的實現(xiàn)不是在當前文件,而是在用其他語言(如C和C++)實現(xiàn)的文件中。Java語言本身不能對操作系統(tǒng)底層進行訪問和操作,但是可以通過JNI接口調(diào)用其他語言來實現(xiàn)對底層的訪問。 5、程序計數(shù)器 程序計數(shù)器(Program Counter Register)是一塊較小的內(nèi)存空間,它的作用可以看做是當前線程所執(zhí)行的字節(jié)碼的行號指示器。在虛擬機的概念模型里(僅是概念模型,各種虛擬機可能會通過一些更高效的方式去實現(xiàn)),字節(jié)碼解釋器工作時就是通過改變這個計數(shù)器的值來選取下一條需要執(zhí)行的字節(jié)碼指令,分支、循環(huán)、跳轉(zhuǎn)、異常處理、線程恢復等基礎功能都需要依賴這個計數(shù)器來完成。 由于Java虛擬機的多線程是通過線程輪流切換并分配處理器執(zhí)行時間的方式來實現(xiàn)的,在任何一個確定的時刻,一個處理器(對于多核處理器來說是一個內(nèi)核)只會執(zhí)行一條線程中的指令。因此,為了線程切換后能恢復到正確的執(zhí)行位置,每條線程都需要有一個獨立的程序計數(shù)器,各條線程之間的計數(shù)器互不影響,獨立存儲,我們稱這類內(nèi)存區(qū)域為“線程私有”的內(nèi)存。 如果線程正在執(zhí)行的是一個Java方法,這個計數(shù)器記錄的是正在執(zhí)行的虛擬機字節(jié)碼指令的地址;如果正在執(zhí)行的是Natvie方法,這個計數(shù)器值則為空(Undefined)。

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • 第二部分 自動內(nèi)存管理機制 第二章 java內(nèi)存異常與內(nèi)存溢出異常 運行數(shù)據(jù)區(qū)域 程序計數(shù)器:當前線程所執(zhí)行的字節(jié)...
    小明oh閱讀 1,298評論 0 2
  • 《深入理解Java虛擬機》筆記_第一遍 先取看完這本書(JVM)后必須掌握的部分。 第一部分 走近 Java 從傳...
    xiaogmail閱讀 5,482評論 1 34
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴謹 對...
    cosWriter閱讀 11,683評論 1 32
  • 一、運行時數(shù)據(jù)區(qū)域 Java虛擬機管理的內(nèi)存包括幾個運行時數(shù)據(jù)內(nèi)存:方法區(qū)、虛擬機棧、本地方法棧、堆、程序計數(shù)器,...
    luhanlin閱讀 614評論 0 0
  • 一、運行時數(shù)據(jù)區(qū)域 Java虛擬機管理的內(nèi)存包括幾個運行時數(shù)據(jù)內(nèi)存:方法區(qū)、虛擬機棧、本地方法棧、堆、程序計數(shù)器,...
    加油小杜閱讀 1,588評論 1 15

友情鏈接更多精彩內(nèi)容