Java內存區域與內存溢出異常

Java內存區域與內存溢出異常
Java的內存管理是一個老生常談的問題,雖然Java號稱可以自動管理自己的內存,使程序員從內存管理的圍牆解放出來,但是一連串的內存泄漏和溢出方面的問題,使得我們不得不去深入瞭解Java的內存管理機制。本篇文章將從Java的內存區域開始剖析Jvm的內存機制,闡述內存溢出異常產生的原因。

運行時數據區域
衆說周知,Java程序是運行在Java虛擬機中的,虛擬機顧名思義,就是一個虛擬的計算機。所以Java虛擬機也擁有一些與真實計算機相近的概念,比如棧,堆,程序計數器等,通常我們在這些概念面前加上虛擬機,以表明特指Java虛擬機的棧。

Java程序運行時,Java虛擬機會對內存進行管理,劃分爲若干個不同的數據區域,每個數據區域都有其不同的功能。根據《Java虛擬機規範(Java SE 7版)》的規定,Java虛擬機所管理的區域會包括以下幾個運行時區域,如下圖所示。

下面我們一一介紹每個區域的不同功能。

程序計數器
程序計數器,即PC。學過計算機組成原理的同學一定對這個概念不陌生,在計算機組成原理中PC指的是PC寄存器,用來存放計算機執行的指令的所在內存區域的地址。而在Java虛擬機中,PC也有類似的作用,它的作用是存儲當前線程所執行的字節碼的行號指示器,通過改變這個計數器的值來選取下一條需要執行的字節碼指令。與計算機PC不同的是,在Java虛擬機中,PC只是一塊較小的內存空間,而不是寄存器。

由於Java虛擬機是多線程的,爲了在線程之間進行隔離,每一個線程都會擁有一個獨立的程序計數器。因此,在進行線程調度的時候,每個線程的執行互不影響。我們稱這類內存區域爲“線程私有”的內存。

程序計數器記錄的只是正在執行的虛擬機字節碼指令的地址,如果執行的是Native方法,那麼計數器的值則爲空。該內存區域是唯一一個在Java虛擬機規範中沒有規定任何OutOfMemoryError情況的區域。

Java虛擬機棧
Java虛擬機棧與系統棧也有些類似,都用來存儲程序運行過程中創建的棧幀,不過Java虛擬機棧存儲的是方法的棧幀而已,它與程序計數器一樣,都是線程私有的。

在Java方法執行時創建的棧幀是用來存儲局部變量表、操作數棧、動態鏈接,方法出口等信息,我們平常所說的方法的入棧和出棧就是一個方法從執行到結束的過程。虛擬機棧的特性與一般的棧一樣,同樣是後進先出,遞歸調用的原理就是基於此。

一般來說,對於Java虛擬機棧,我們主要關心的部分是它的局部變量表的存儲。在我們定義一個變量的時候,變量到底被存放在哪裏是我們經常遇到的問題。對於基本數據類型,如boolean、byte等以及對象的引用(reference類型,一個指向對象的指針或者是一個句柄,不是對象本身)和returnAddress類型(指向了一條字節碼指令的地址)。

局部變量表的大小是在編譯期就已經完全確定下來的,在方法運行期間不會改變局部變量表的大小。同時,對於64位長度的long和double類型的數據會佔用兩個局部變量空間,其餘的只佔用一個。

在Java虛擬機規範中,對這個區域規定了兩種異常情況:StackOverflowError異常和OutOfMemoryError異常。

本地方法棧
本地方法棧與虛擬機棧類似,唯一的區別是本地方法棧是用來執行Native方法的。

Java堆
Java堆是我們在編寫Java程序中所能使用的最大的一塊的內存區域了,也是我們經常需要調整的區域。這個區域的唯一目的就是存放對象實例,幾乎所有的對象實例都在這裏分配內存,包括數組(因爲數組也是引用數據類型)。Java堆與虛擬機棧不同,它是被所有線程共享的區域,在虛擬機啓動的時候創建。

Java堆還可以進一步分爲:新生代和老年代;再細緻一點的有Eden空間、From Survivor空間、To Survivor空間等,這些更細緻的分區是在Java堆垃圾收集器進行垃圾管理的時候需要考慮的。

Java堆的大小可以是固定的,也可以是不固定的,可以通過-Xmx和-Xms控制,前者是最大值,後者是最小值,在兩者相同時,堆的大小就是固定的。在內存中如果沒有足夠的空間來分配,將會拋出OutOfMemoryError異常。

方法區
方法區和Java堆一樣,是各個線程共享的內存區域,用來存儲已經被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據。

這個區域也是屬於需要進行垃圾回收的區域,主要是回收常量池和對類型的卸載,一般來說,回收的效果不會太理想,但是卻是必須的。

根據Java虛擬機的規範規定,當方法區無法滿足內存分配需求時,將拋出OutOfMemoryError異常。

運行時常量池
該區域是方法區的一部分,用於存放編譯期生成的各種字面量和符號引用,有時候直接引用也會放入,這部分內容將在類加載後進入方法區的運行時常量池中存放。

運行時常量池有一定的動態性,對String類有所瞭解的同學應該明白,在運行期間通過String類的intern()方法可以動態往常量池裏動態添加常量。

直接內存
直接內存不屬於Java虛擬機運行時數據區的一部分,而是屬於操作系統管理的區域。這部分的使用很頻繁,利用的好,可以大大提升程序的運行效率,比較優秀的使用例如基於NIO的Netty框架等。

爲什麼使用直接內存可以提升性能呢,因爲可以避免在Java堆和Native堆中來回複製數據的開銷。

這部分的內存使用不會收到Java堆大小的限制,但會收到本機的內存大小限制。因此,在操作這部分內存時需要謹慎,一旦出問題,可能會影響到本機的其它服務。 當各個內存區域總和大於物理內存限制,拋出OutOfMemoryError異常。
原文地址https://www.cnblogs.com/qianpangzi/p/10555612.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章