詳細介紹Java記憶體區域與記憶體溢出異常
這篇文章主要介紹了Java記憶體區域與記憶體溢出異常詳解的相關資料,需要的朋友可以參考下
#Java記憶體區域與記憶體溢出異常
#概述
對於C 和C++程式開發的開發人員來說,在記憶體管理領域,程式設計師對記憶體擁有絕對的使用權,但是也要主要到正確的使用和清理內存,這就要求程式設計師有較高的水平。
而對於Java 程式設計師來說,在虛擬機器的自動記憶體管理機制的幫助下,不再需要為每個new 操作去寫配對的delete/free 程式碼,而且不容易出現記憶體洩漏和記憶體溢位問題,看起來由虛擬機器管理記憶體一切都很美好。不過,也正是因為Java 程式設計師把記憶體控制的權力交給了Java 虛擬機,一旦出現記憶體洩漏和溢出方面的問題,如果不了解虛擬機是怎樣使用記憶體的,那排查錯誤將會成為一項異常艱難的工作。
Java執行階段資料區
我們一般在開發中認為JVM不過有堆疊和堆疊兩部分組成,但是實際的Java 虛擬機器在執行Java 程式的過程中會把它所管理的記憶體劃分為若干個不同的資料區。這些區域都有各自的用途,以及創建和銷毀的時間,有的區域隨著虛擬機器進程的啟動而存在,有些區域則是依賴用戶執行緒的啟動和結束而建立和銷毀。如下圖:
程式計數器
#如果學習過電腦組成原理的應該很清楚,程式計數器就相當於身分證一樣,由於JVM也有自己的CPU,在執行多執行緒程式的時候,透過時間片輪轉的方式,根據程式計數器來調度執行緒的執行。
程式計數器( Program Counter Register)是一塊較小的記憶體空間,它的作用可以看做是目前執行緒所執行的字節碼的行號指示器。在虛擬機器的概念模型裡(僅是概念模型,各種虛擬機器可能會透過一些更有效率的方式去實現),字節碼解釋器工作時就是透過改變這個計數器的值來選取下一條需要執行的字節碼指令,分支、循環、跳轉、異常處理、執行緒復原等基礎功能都需要依賴這個計數器來完成。
由於Java 虛擬機器的多執行緒是透過執行緒輪流切換並分配處理器執行時間的方式來實現的,在任何一個確定的時刻,一個處理器(對於多核心處理器來說是一個核心)只會執行一條線程中的指令。因此,為了線程切換後能恢復到正確的執行位置,每條線程都需要有一個獨立的程序計數器,各條線程之間的計數器互不影響,獨立存儲,我們稱這類內存區域為“線程私有”的內存。
如果執行緒正在執行的是一個Java 方法,這個計數器記錄的是正在執行的虛擬機器字節碼指令的位址;如果正在執行的是Natvie 方法,這個計數器值則是空( Undefined) 。此記憶體區域是唯一在Java 虛擬機器規格中沒有規定任何 OutOfMemoryError 情況的區域。
Java 虛擬機器堆疊
與程式計數器一樣, Java 虛擬機器堆疊( Java Virtual Machine Stacks)也是執行緒私有的,它的生命週期與線程相同。
虛擬機器堆疊描述的是Java 方法執行的記憶體模型:每個方法執行的時候都會同時建立一個堆疊幀( Stack Frame)用於儲存局部變數表、操作棧、動態連結、方法出口等資訊。每一個方法被呼叫直到執行完成的過程,就對應一個堆疊幀在虛擬機器棧中從入棧到出棧的過程。
經常有人把 Java 記憶體區分為堆疊記憶體( Heap)和堆疊記憶體( Stack),這種分法比較粗糙, Java 記憶體區域的劃分其實遠比這複雜。這種劃分方式的流行只能說明大多數程式設計師最關注的、與物件記憶體分配關係最密切的記憶體區域是這兩塊。其中所指的「堆」在後面會專門講述,而所指的「棧」就是現在講的虛擬機棧,或者說是虛擬機棧中的局部變數表部分。
局部變數表存放了編譯期可知的各種基本資料型別( boolean、 byte、 char、 short、 int、 float、long、 double)、物件參考( reference 類型,它不等同於物件本身,根據不同的虛擬機器實現,它可能是指向物件起始位址的引用指針,也可能指向一個代表物件的句柄或者其他與此物件相關的位置)和returnAddress 類型(指向了一條字節碼指令的位址)。
其中 64 位元長度的 long 和 double 類型的資料會佔用 2 個局部變數空間(Slot),其餘的資料型別只佔 1 個。局部變數表所需的記憶體空間在編譯期間完成分配,當進入一個方法時,這個方法需要在幀中分配多大的局部變數空間是完全確定的,在方法運行期間不會改變局部變數表的大小。
在Java 虛擬機器規格中,對這個區域規定了兩種例外狀況:如果執行緒請求的堆疊深度大於虛擬機器所允許的深度,將會拋出StackOverflowError 例外;如果虛擬機器堆疊可以動態擴展(目前大部分的Java 虛擬機器都可動態擴展,只不過Java 虛擬機規格中也允許固定長度的虛擬機堆疊),當擴展時無法申請到足夠的記憶體時會拋出OutOfMemoryError 例外。
本機方法堆疊
本機方法堆疊( Native Method Stacks)與虛擬機器堆疊所扮演的角色是非常相似的,其差異不過是虛擬機器堆疊為虛擬機器執行Java 方法(也就是字節碼)服務,而本機方法堆疊則是為虛擬機器使用到的Native方法服務。虛擬機器規範中對本機方法堆疊中的方法所使用的語言、使用方式與資料結構並沒有強制規定,因此具體的虛擬機器可以自由實作它。甚至有的虛擬機器(譬如 Sun HotSpot 虛擬機器)直接就把本地方法堆疊和虛擬機器棧合而為一。與虛擬機器堆疊一樣,本地方法堆疊區域也會拋出StackOverflowError 和OutOfMemoryError 異常。
Java 堆疊
對大多數應用程式來說, Java 堆( Java Heap)是 Java 虛擬機器所管理的記憶體中最大的一塊。 Java堆是被所有執行緒共享的一塊記憶體區域,在虛擬機器啟動時創建。此記憶體區域的唯一目的就是存放物件實例,幾乎所有的物件實例都在這裡分配記憶體。這一點在Java 虛擬機規範中的描述是:所有的物件實例以及數組都要在堆上分配,但是隨著JIT 編譯器的發展與逃逸分析技術的逐漸成熟,棧上分配、標量替換優化技術將會導致一些微妙的變化發生,所有的物件都分配在堆上也漸漸變得不是那麼「絕對」了。
ava 堆是垃圾收集器管理的主要區域,因此很多時候也被稱為「GC 堆( 」 Garbage Collected Heap,幸好國內沒翻譯成「垃圾堆」)。如果從記憶體回收的角度來看,由於現在收集器基本上都是採用的分代收集演算法,所以Java 堆中還可以細分為:新生代和老年代;再細緻一點的有Eden 空間、 From Survivor空間、 To Survivor 空間等。如果從記憶體分配的角度來看,執行緒共享的 Java 堆中可能會分割出多個執行緒私有的分配緩衝區( Thread Local Allocation Buffer, TLAB)。不過,無論如何劃分,都與存放內容無關,無論哪個區域,存儲的都仍然是對象實例,進一步劃分的目的是為了更好地回收內存,或者更快地分配內存。在本章中,我們僅僅針對記憶體區域的角色進行討論, Java 堆中的上述各個區域的分配和回收等細節將會是下一章的主題。
根據 Java 虛擬機器規範的規定, Java 堆可以處於物理上不連續的記憶體空間中,只要邏輯上是連續的即可,就像我們的磁碟空間一樣。在實現時,既可以實現成固定大小的,也可以是可擴展的,不過當前主流的虛擬機都是按照可擴展來實現的(透過-Xmx 和-Xms 控制)。如果在堆中沒有記憶體完成實例分配,且堆也無法再擴展時,將會拋出 OutOfMemoryError 異常。
方法區
方法區( Method Area)與Java 堆一樣,是各個執行緒共享的記憶體區域,它用於儲存已被虛擬機器載入的類資訊、常數、靜態變數、即時編譯器編譯後的程式碼等資料。雖然 Java 虛擬機器規範把方法區描述為堆的一個邏輯部分,但它卻有一個名叫做 Non-Heap(非堆),目的應該是與 Java 堆區分開來。
Java 虛擬機器規格對這個區域的限制非常寬鬆,除了和 Java 堆一樣不需要連續的記憶體和可以選擇固定大小或可擴充外,還可以選擇不實作垃圾收集。相對而言,垃圾收集行為在這個區域是比較少出現的,但並非資料進入了方法區就如永久代的名字一樣「永久」存在了。這個區域的記憶體回收目標主要是針對常量池的回收和對類型的卸載,一般來說這個區域的回收「成績」比較難以令人滿意,尤其是類型的卸載,條件相當苛刻,但是這部分區域的回收確實是有必要的。
根據 Java 虛擬機器規格的規定,當方法區無法滿足記憶體分配需求時,就會拋出OutOfMemoryError 例外。
執行時間常數池
執行時間常數池( Runtime Constant Pool)是方法區的一部份。 Class 檔案中除了有類別的版本、欄位、方法、介面等描述等資訊外,還有一項資訊是常數池( Constant Pool Table),用於存放編譯期產生的各種字面量和符號引用,這部分內容將在類別載入後存放到方法區的運行時常數池中。
Java 虛擬機器對Class 檔案的每一部分(自然也包括常數池)的格式都有嚴格的規定,每個位元組用於儲存哪種資料都必須符合規範上的要求,這樣才會被虛擬機器認可、裝載和執行。但對於執行時間常數池, Java 虛擬機器規格沒有做任何細節的要求,不同的提供者實現的虛擬機器可以按照自己的需求來實現這個記憶體區域。不過,一般來說,除了保存 Class 檔案中描述的符號引用外,還會將翻譯出來的直接引用也儲存在運行時常數池中。
執行階段常數池相對於Class 檔案常數池的另一個重要特徵是具備動態性, Java 語言並不要求常數一定只能在編譯期產生,也就是並非預置入Class 檔案中常數池的內容才能進入方法區運行時常數池,運行期間也可能將新的常數放入池中,這種特性被開發人員利用得比較多的便是String 類別的intern()方法。
既然執行時間常數池是方法區的一部分,自然會受到方法區記憶體的限制,當常數池無法再申請到記憶體時會拋出 OutOfMemoryError 例外。
直接記憶體
直接記憶體( Direct Memory)並不是虛擬機器運行時資料區的一部分,也不是Java 虛擬機規格中定義的記憶體區域,但是這部分記憶體也被頻繁地使用,也可能導致OutOfMemoryError 異常出現。
在JDK 1.4 中新加入了NIO ( New Input/Output)類,引入了一種基於通道( Channel)與緩衝區( Buffer)的I/O 方式,它可以使用Native 函數庫直接分配堆外內存,然後透過一個儲存在Java 堆裡面的DirectByteBuffer 物件作為這塊記憶體的參考進行操作。這樣能在一些場景中顯著提高效能,因為避免了在 Java 堆和 Native 堆中來回複製資料。
顯然,本機直接記憶體的分配不會受到Java 堆大小的限制,但是,既然是內存,則肯定還是會受到本機總內存(包括RAM 及SWAP 區或者分頁文件)的大小及處理器尋址空間的限制。伺服器管理員配置虛擬機參數時,一般會根據實際內存設定-Xmx 等參數信息,但經常會忽略掉直接內存,使得各個內存區域的總和大於物理內存限制(包括物理上的和操作系統級的限制),從而導致動態擴充時出現OutOfMemoryError 異常。
以上是詳細介紹Java記憶體區域與記憶體溢出異常的詳細內容。更多資訊請關注PHP中文網其他相關文章!

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

Java 8引入了Stream API,提供了一種強大且表達力豐富的處理數據集合的方式。然而,使用Stream時,一個常見問題是:如何從forEach操作中中斷或返回? 傳統循環允許提前中斷或返回,但Stream的forEach方法並不直接支持這種方式。本文將解釋原因,並探討在Stream處理系統中實現提前終止的替代方法。 延伸閱讀: Java Stream API改進 理解Stream forEach forEach方法是一個終端操作,它對Stream中的每個元素執行一個操作。它的設計意圖是處

PHP是一種廣泛應用於服務器端的腳本語言,特別適合web開發。 1.PHP可以嵌入HTML,處理HTTP請求和響應,支持多種數據庫。 2.PHP用於生成動態網頁內容,處理表單數據,訪問數據庫等,具有強大的社區支持和開源資源。 3.PHP是解釋型語言,執行過程包括詞法分析、語法分析、編譯和執行。 4.PHP可以與MySQL結合用於用戶註冊系統等高級應用。 5.調試PHP時,可使用error_reporting()和var_dump()等函數。 6.優化PHP代碼可通過緩存機制、優化數據庫查詢和使用內置函數。 7

PHP和Python各有優勢,選擇應基於項目需求。 1.PHP適合web開發,語法簡單,執行效率高。 2.Python適用於數據科學和機器學習,語法簡潔,庫豐富。

膠囊是一種三維幾何圖形,由一個圓柱體和兩端各一個半球體組成。膠囊的體積可以通過將圓柱體的體積和兩端半球體的體積相加來計算。本教程將討論如何使用不同的方法在Java中計算給定膠囊的體積。 膠囊體積公式 膠囊體積的公式如下: 膠囊體積 = 圓柱體體積 兩個半球體體積 其中, r: 半球體的半徑。 h: 圓柱體的高度(不包括半球體)。 例子 1 輸入 半徑 = 5 單位 高度 = 10 單位 輸出 體積 = 1570.8 立方單位 解釋 使用公式計算體積: 體積 = π × r2 × h (4

PHP和Python各有優勢,適合不同場景。 1.PHP適用於web開發,提供內置web服務器和豐富函數庫。 2.Python適合數據科學和機器學習,語法簡潔且有強大標準庫。選擇時應根據項目需求決定。

PHP適合web開發,特別是在快速開發和處理動態內容方面表現出色,但不擅長數據科學和企業級應用。與Python相比,PHP在web開發中更具優勢,但在數據科學領域不如Python;與Java相比,PHP在企業級應用中表現較差,但在web開發中更靈活;與JavaScript相比,PHP在後端開發中更簡潔,但在前端開發中不如JavaScript。

Java是熱門程式語言,適合初學者和經驗豐富的開發者學習。本教學從基礎概念出發,逐步深入解說進階主題。安裝Java開發工具包後,可透過建立簡單的「Hello,World!」程式來實踐程式設計。理解程式碼後,使用命令提示字元編譯並執行程序,控制台上將輸出「Hello,World!」。學習Java開啟了程式設計之旅,隨著掌握程度加深,可創建更複雜的應用程式。
