天天看點

深入了解JVM-自動記憶體管理前言

自動記憶體管理

  • 前言
    • Java記憶體區域與記憶體溢出異常
      • 運作時資料區域
        • 程式計數器
        • Java虛拟機棧
        • 本地方法棧
        • Java堆
        • 方法區
        • 運作時常量池
        • 直接記憶體
      • OutOfMemoryError異常

前言

從Java的第一個版本誕生到現在已經有二十餘年的時間,白駒過隙,滄海桑田,

轉眼已過了四分之一個世紀,在圖1-3所示的時間線裡,我們看到JDK的版本已經發展

JDK13。這二十多年裡誕生過無數與Java相關的産品、技術與标準。現在讓我們走入道,從孕育Java語言的時代開始,再來回顧一下Java的發展軌迹和曆史變遷。19年4月,由 James Gosling博士上司的綠色計劃( Green Project)開始啟動,此計

劃最初的目标是開發一種能夠在各種消費性電子産品(如機頂盒、冰箱、收音機等)上運作的程式架構。這個計劃的産品就是Java語言的前身:Oak(得名于James Gosling辦公室外的一顆橡樹)。Oak當時在消費市場上并不算成功,但随着1995年網際網路潮流的興起,Oak迅速找到了最适合自己發展的市場定位并蛻變為Java語言。

深入了解JVM-自動記憶體管理前言
1-3
           

Java記憶體區域與記憶體溢出異常

運作時資料區域

Java虛拟機在執行Java程式的過程中會把它所管理的記憶體劃分成若幹個不同的數量區域。根據《Java虛拟機規範》的規定,Java虛拟機所管理的記憶體将會包括以下幾個運作時資料區域。

深入了解JVM-自動記憶體管理前言

程式計數器

程式計數器占用較小的記憶體空間,可以看做是目前線程所執行的位元組碼的行号訓示器,由于Java虛拟機的多線程是通過線程輪流切換并配置設定處理器執行時間的方式來實作的,在任何一個确定的時刻,一個處理器(對于多核處理器來說就是一個核心)都隻會執行一條線程中的指令。是以,為了線程切換後能夠恢複到正确的執行位置,每條線程都需要有一個獨立的程式計數器。

如果線程正在執行Java方法,則計數器記錄的是正在執行的虛拟機位元組碼指令的位址;如果正在執行的是Native方法,則這個計數器則為空。此記憶體區域是唯一一個在java虛拟機規範中沒有規定任何OutOfMemoryError情況的區域。

Java虛拟機棧

同樣是線程私有,描述Java方法執行的記憶體模型:每個方法在執行的同時都會建立一個棧幀(Stack Frame)用于存儲局部變量表、操作數棧、動态連結、方法出口等資訊。一個方法對應一個棧幀。

局部變量表:存放了編譯器可知的各種基本資料類型(boolean、byte等)、對象引用(reference類型,它不等同于對象本身,可能是一個指向對象起始位址的引用指針,也可能是指向另一個代表對象的句柄或其他次對象相關的位置)和returnAddress類型(指向了一條位元組碼指令的位址)

  規定的異常情況有兩種:1.線程請求的棧的深度大于虛拟機所允許的深度,将抛出StackOverflowError異常;2.如果虛拟機可以動态擴充,如果擴充時無法申請到足夠的記憶體,就抛出OutOfMemoryError異常。
           

本地方法棧

和Java虛拟機棧很類似,不同的是本地方法棧為Native方法服務

Java堆

對大多數應用來說,Java堆(Heap)是Java虛拟機所管理的記憶體中最大的一塊,Java堆是被所有線程共享的一塊記憶體區域,在虛拟機啟動時建立。該記憶體區域唯一的目的就是存放對象執行個體,Java對象執行個體以及數組都在堆上配置設定(随着JIT編譯器發展等技術成熟,所有對象配置設定在堆上也漸漸不是那麼“絕對”了)。

Java堆是垃圾收集器管理的主要區域,是以Java堆也常被稱為“GC堆”,由于現在收集器基于分代收集算法,Java堆還可以細分為:新生代和老年代。

根據Java虛拟機規範的規定,Java堆可以處于實體上不連續的記憶體空間中,隻要邏輯上是連續的即可,就像我們的磁盤空間一樣(或者說,像連結清單一樣雖然記憶體上不一定連續,但邏輯上是連續)。如果在堆中沒有記憶體完成執行個體配置設定,而且堆也沒辦法再擴充時,将會抛出OutOfMemoryError異常。

方法區

所有線程共享,存儲已被虛拟機加載的類資訊、常量、靜态變量、即時編譯器編譯後的代碼等資料。

當方法區無法滿足記憶體配置設定需求時,抛出OutOfMemoryError
           

運作時常量池

運作時常量池是方法區的一部分,Class檔案中除了有關類的版本、字段、方法、接口等描述資訊外,還有一項資訊是常量池,用于存放編譯期生成的各種字面量和符号引用,這部分内容将在類加載後進入方法區的運作時常量池中存放。

字面量如:文班字元串,Java中聲明為final的常量等

符号引用:字段或方法的名稱呵描述符等資訊

運作時常量池相對于Class檔案常量池的另一個重要特征是具備動态性,Java語言并非不要求常量一定隻有編譯期才能産生,也就是并非預置入Class檔案中常量池的内容才能進入方法區運作時常量池,運作期間也可以将新的常量池放入池中。

直接記憶體

并不是虛拟機運作時資料區的一部分,也不是Java虛拟機規範中定義的記憶體區域。

JDK1.4加入了NIO,引入一種基于通道與緩沖區的I/O方式,它可以使用Native函數庫直接配置設定堆外記憶體,然後通過一個存儲在Java堆中的DirectByteBuffer對象作為這塊記憶體的引用進行操作。因為避免了在Java堆和Native堆中來回複制資料,提高了性能。
  一般我們使用Unsafe和NIO包下ByteBuffer來建立堆外記憶體。如果從堆内向磁盤寫資料時,資料會被先複制到堆外記憶體,即核心緩沖區,然後再由OS寫入磁盤,使用堆外記憶體避免了這個操作。(提升複制速度(io效率))
           

本機直接記憶體的配置設定不會受到Java 堆大小的限制,受到本機總記憶體大小限制

直接記憶體也可以由 -XX:MaxDirectMemorySize 指定

直接記憶體申請空間耗費更高的性能

直接記憶體IO讀寫的性能要優于普通的堆記憶體

當各個記憶體區域總和大于實體記憶體限制,抛出OutOfMemoryError異常。

OutOfMemoryError異常