1. 程式人生 > 其它 >JVM-記憶體模型

JVM-記憶體模型

1. 記憶體中的棧(stack)、堆(heap)和靜態區(static area)
  • 通常我們定義一個基本資料型別的變數,一個物件的引用,還有就是函式呼叫的現場儲存都使用記憶體中的棧空間;
  • 通過new關鍵字和構造器建立的物件放在堆空間;
  • 程式中的字面量(literal)如直接書寫的100、”hello”和常量都是放在靜態區中
  • 棧空間操作起來最快但是棧很小,通常大量的物件都是放在堆空間,理論上整個記憶體沒有被其他程序使用的空間甚至硬碟上的虛擬記憶體都可以被當成堆空間來使用。
1 String str = new String("hello");
上面的語句中變數str放在棧上,用new創建出來的字串物件放在堆上,而”hello”這個字面量放在靜態區。 補充:較新版本的Java(從Java 6的某個更新開始)中使用了一項叫”逃逸分析”的技術,可以將一些區域性物件放在棧上以提升物件的操作效能。 3 JVM記憶體
  • 實體記憶體即隨機儲存器RAM
  • 連線處理器和記憶體的是地址匯流排,地址匯流排的寬度影響了實體地址的定址範圍,決定了處理器一次從記憶體或暫存器獲取多少bit
  • java記憶體結構
    • 堆Heap
      • 儲存java物件的記憶體區域
      • JVM啟動時一次申請完成,大小固定不能重新申請
      • 堆記憶體由垃圾回收器管理
      • OOM異常後有Java heap space,通過VM args:-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path儲存當前記憶體堆快照便於分析
      • fullgc前後也可以dump:-XX:+HeapDumpBeforeFullGC -XX:+HeapDumpAfterFullGC -XX:HeapDumpPath=e:\dump
      • 堆最大值-Xmx20m,-Xms20m
      • 如何判斷OOM是記憶體洩露還是記憶體溢位?
        • 先用Eclipse Memory Analyzer檢視洩露物件到GC Roots的引用鏈,便可準確定位洩露程式碼的位置
    • 方法區
      • 類被載入到虛擬機器時,類資訊、常量?、靜態變數、JIT編譯後的程式碼等
      • 屬於堆堆一部分,但是幾乎不會被垃圾回收器回收,屬於永久代?
      • 1.6及之前版本可以通過-XX:PermSize和-XX:MaxPermSize設定永久代(方法區)上限,方法區無法滿足記憶體分配需求,也會丟擲OutOfMemoryError異常
        • 通過String的intern()方法或者CGLib動態生成大量的類可以復現OOM:PermGen space
      • 1.7開始去除永久代
    • 執行時常量池
      • 1.6及以前,常量池分配在永久代中;1.7後放在堆中
      • 編譯期產生常量;執行時也產生新的常量,例如String的intern()方法
      • 常量包含兩大類:字面量(如文字字串、final常量)、符號引用(類全限定名、欄位、方法的名稱和描述符)
//1.6中intern方法會把首次遇到的字串複製到永久代,且返回永久代字串例項的引用 //1.7及以後,intern方法不復制,只在常量池記錄首次出現的例項引用,返回原string物件 String s = new StringBuilder("hello").append("world").toString(); //s.intern() == s 1.6結果false,1.7結果true String s = new StringBuilder("ja").append("va").toString(); //s.intern() == s 1.6結果false,1.7結果false
    • 虛擬機器棧-VM stack
      • 建立新執行緒時,為這個執行緒分配一個java棧
      • 每執行一個方法,建立一個棧幀,包含區域性變量表、運算元棧、動態連結、方法返回值
      • 區域性變量表的大小編譯期已確定,存放了編譯期可知的各種基本資料型別、物件引用和returnAddress型別
        • 當我們說 JVM 執行引擎是基於棧的時候,其中的“棧”指的就是運算元棧
      • -Xss2m設定棧大小。方法引數和區域性變數過多,棧幀越大,棧深度越小。一般情況達到1000~2000沒問題
      • 異常情況:
        • 1.執行緒請求的棧深度大於虛擬機器允許的最大深度,則丟擲StackOverflowError
        • 2. 如果虛擬機器棧可以動態擴充套件,擴充套件時無法申請到足夠的記憶體,則丟擲OutOfMemoryError異常。例如建立過多的執行緒。
    • 本地方法棧
      • HotSpot不區分虛擬機器棧和本地方法棧
    • 程式計數器-Program Count Register
      • 佔用記憶體較小,當前執行緒所執行位元組碼的行號指示器,為了多執行緒切換後能恢復到正確的位置來執行
      • 執行緒私有記憶體:每個執行緒擁有一個獨立的程式計數器,各個執行緒的計數器互不影響
      • native方法執行時計數器值為空undefined
    • 直接記憶體--堆外記憶體 Direct Memory
      • NIO分配的是堆外記憶體,然後通過一個儲存在java堆中的DirectByteBuffer物件作為這塊記憶體的引用進行操作
      • 可顯著提高效能,因為避免了在堆記憶體和Native堆中來回複製資料
      • 直接記憶體的分配不受堆大小的限制。注意堆記憶體+直接記憶體不要大於實體記憶體限制
      • -XX:MaxDirectMemorySize指定,預設與堆最大值-Xmx一樣
      • Unsafe.allocateMemory(long)申請分配記憶體,來模擬堆外記憶體溢位問題。如果OOM的Dump檔案很小且程式用到了NIO可以考慮直接記憶體問題。
  • 垃圾回收
    • 回收條件:一個物件不再被其他活動物件引用,活動物件指能夠被根物件集合到達的物件
    • 根物件集合:
      • 方法區類靜態屬性的物件引用
      • 方法區常量引用的物件
      • 虛擬機器棧(棧幀中的本地變量表)引用的物件
      • 本地方法棧引用的物件
    • 基於分代的垃圾回收
      • 把物件按照壽命長短進行分組,新建立的是年輕代,經過幾輪迴收後仍然存活的,劃分到年老代
      • 年老代的回收頻率低於年輕代,提高回收效率
      • 永久區主要是Class物件
5. JVM 選擇32位還是64位
  • https://blog.csdn.net/XXJ19950917/article/details/73527313
  • 官網:效能相差不大,32位的應用遷移到64位效能出現下降。
  • 記憶體區別
    • 32位堆最大4G,64位堆大小受限於實體記憶體和虛擬記憶體;
    • 32位堆疊最大320k,64位是1024k?
  • 選擇32位
    • 目前來看64位會比32位jdk效能有所下降
  • 選擇64位
    • 64位計算有點:一次讀取64位,可以進行更大範圍的整數運算,浮點計算沒有影響;
    • 可以支援更大的記憶體,超過2GB的Java Heap效能更好
    • 相容性問題:32位出現過不相容的情況