jvm 堆、棧、方法區、程序計數器

最近在讀周志明的《深入理解Java虛擬機:JVM高級特性與最佳實踐》,從中學到了很多,有些人可能會問爲什麼我們要學習JVM,他有什麼用?在這裏我想說一下,並不是這本書是大家都推薦的說有用處,我們纔去讀,不要人云亦云,別人說什麼我們纔去做什麼,我們要有自己的選擇,知其然還要知其所以然,不然你很是生澀的一頭扎進去學習JVM,這樣效果並不好,首先要了解一下jvm可以帶給我們什麼:

如果你這輩子只甘心做一個平庸的Java碼農,那麼你完全沒有必要去學習JVM相關的知識,學習JVM對於一個Java程序員的好處大概可以概括爲下幾點:

你可以知道java程序是怎麼運行的;
瞭解底層的東西,而不是停留在代碼層面的,可以瞭解一下堆,棧,方法區,本地方法棧,程序計數器,垃圾回收,類加載機制,字節碼指令等等;
瞭解底層,可以對Java有更深的理解;
....

好了,下邊先來說一下jvm內存模型:
堆區(heap):堆中存儲的是對象,JVM只有一個堆,堆區是JVM內存管理中最大的一塊,也是GC主要工作區域,是線程共享的。堆區的主要作用是存儲對象實例,一般來說,所有的對象都在堆上分配內存。

堆的大小可以通過-Xms(最小值)和-Xmx(最大值)參數設置,-Xms爲JVM啓動時申請的最小內存,默認爲操作系統物理內存的1/64但小於1G,-Xmx爲JVM可申請的最大內存,默認爲物理內存的1/4但小於1G,默認當空餘堆內存小於40%時,JVM會增大Heap到-Xmx指定的大小,可通過-XX:MinHeapFreeRation=來指定這個比列;當空餘堆內存大於70%時,JVM會減小heap的大小到-Xms指定的大小,可通過XX:MaxHeapFreeRation=來指定這個比列,對於運行系統,爲避免在運行時頻繁調整Heap的大小,通常-Xms與-Xmx的值設成一樣。
棧(stack):

Java棧是與每一個線程關聯的,JVM在創建每一個線程的時候,會分配一定的棧空間給線程。存儲局部變量、引用、方法、返回值等。
StackOverflowError:如果在線程執行的過程中,棧空間不夠用,那麼JVM就會拋出此異常,這種情況一般是死循環,不合理的遞歸,該釋放的資源沒有釋放造成的。
方法區:

JVM方法區又稱靜態區,存放所有的class和靜態變量、final常量。方法區是系統分配的一個內存邏輯區域,是用來存儲類型信息的(類型信息可理解爲類的描述信息)。方法區主要有以下幾個特點:
一.方法區是線程安全的。由於所有的線程都共享方法區,所以,方法區裏的數據訪問必須被設計成線程安全的。例如,假如同時有兩個線程都企圖訪問方法區中的同一個類,而這個類還沒有被裝入JVM,那麼只允許一個線程去裝載它,而其它線程必須等待
二.方法區的大小不必是固定的,JVM可根據應用需要動態調整。同時,方法區也不一定是連續的,方法區可以在一個堆(甚至是JVM自己的堆)中自由分配。
三.方法區也可被垃圾收集,當某個類不在被使用(不可觸及)時,JVM將卸載這個類,進行垃圾收集
本地方法棧:

本地方法棧(Native MethodStacks)與虛擬機棧所發揮的作用是非常相似的,其區別不過是虛擬機棧爲虛擬機執行Java 方法(也就是字節碼)服務,而本地方法棧則是爲虛擬機使用到的Native 方法服務。虛擬機規範中對本地方法棧中的方法使用的語言、使用方式與數據結構並沒有強制規定,因此具體的虛擬機可以自由實現它。甚至有的虛擬機(譬如Sun HotSpot 虛擬機)直接就把本地方法棧和虛擬機棧合二爲一。

與虛擬機棧一樣,本地方法棧區域也會拋出StackOverflowError和OutOfMemoryError異常。
程序計數器:

程序計數器是一塊較小的內存空間,可以看作是當前線程所執行的字節碼的行號指示器。分支、循環、跳轉、異常處理、線程恢復等基礎功能都需要依賴這個計數器來完成。

由於Java 虛擬機的多線程是通過線程輪流切換並分配處理器執行時間的方式來實現的,在任何一個確定的時刻,一個處理器(對於多核處理器來說是一個內核)只會執行一條線程中的指令。因此,爲了線程切換後能恢復到正確的執行位置,每條線程都需要有一個獨立的程序計數器,各條線程之間的計數器互不影響,獨立存儲,我們稱這類內存區域爲“線程私有”的內存。

如果線程正在執行的是一個Java 方法,這個計數器記錄的是正在執行的虛擬機字節碼指令的地址;如果正在執行的是Natvie 方法,這個計數器值則爲空(Undefined)。

ok,jvm內存大致分爲這五個區域,下邊來對一些其他知識進行一些補充:
堆棧對比:
經常有人把Java 內存區分爲堆內存(Heap)和棧內存(Stack),這種分法比較粗糙,Java內存區域的劃分實際上遠比這複雜。這種劃分方式的流行只能說明大多數程序員最關注的、與對象內存分配關係最密切的內存區域是這兩塊。

堆很靈活,但是不安全。對於對象,我們要動態地創建、銷燬,不能說後創建的對象沒有銷燬,先前創建的對象就不能銷燬,那樣的話我們的程序就寸步難行,所以Java中用堆來存儲對象。而一旦堆中的對象被銷燬,我們繼續引用這個對象的話,就會出現著名的 NullPointerException,這就是堆的缺點——錯誤的引用邏輯只有在運行時纔會被發現。

棧不靈活,但是很嚴格,是安全的,易於管理。因爲只要上面的引用沒有銷燬,下面引用就一定還在,在大部分程序中,都是先定義的變量、引用先進棧,後定義的後進棧,同時,區塊內部的變量、引用在進入區塊時壓棧,區塊結束時出棧,理解了這種機制,我們就可以很方便地理解各種編程語言的作用域的概念了,同時這也是棧的優點——錯誤的引用邏輯在編譯時就可以被發現。

堆–用來存放 new 出來的對象實例,所有線程共享。

棧–主要存放引用和基本數據類型,私有線程空間。
內存泄漏和內存溢出:

內存泄露 memory leak:是指程序在申請內存後,無法釋放已申請的內存空間,一次內存泄露危害可以忽略,但內存泄露堆積的多了,不斷的佔用內存會導致內存溢出,這是我們都不想看到的。

內存溢出 out of memory:內存溢出就是你要求分配的內存超出了系統能給你的,系統不能滿足需求,於是產生溢出。

Java 堆內存的OutOfMemoryError異常是實際應用中最常見的內存溢出異常情況。出現Java 堆內存溢出時,異常堆棧信息“java.lang.OutOfMemoryError”會跟着進一步提示“Javaheapspace”。

要解決這個區域的異常,一般的手段是首先通過內存映像分析工具(如Eclipse Memory Analyzer)對dump 出來的堆轉儲快照進行分析,重點是確認內存中的對象是否是必要的,也就是要先分清楚到底是出現了內存泄漏(Memory Leak)還是內存溢出(Memory Overflow)。

如果是內存泄漏,可進一步通過工具查看泄漏對象到GC Roots 的引用鏈。於是就能找到泄漏對象是通過怎樣的路徑與GC Roots 相關聯並導致垃圾收集器無法自動回收它們的。掌握了泄漏對象的類型信息,以及GC Roots 引用鏈的信息,就可以比較準確地定位出泄漏代碼的位置。

如果不存在泄漏,換句話說就是內存中的對象確實都還必須存活着,那就應當檢查虛擬機的堆參數(-Xmx 與-Xms),與機器物理內存對比看是否還可以調大,從代碼上檢查是否存在某些對象生命週期過長、持有狀態時間過長的情況,嘗試減少程序運行期的內存消耗。
JAVA GC 算法

 Java GC(垃圾回收)機制是一種和垃圾回收的自動內存管理機制。GC機制對JVM中的內存進行標記,並確定哪些內存需要回收,根據一定的回收策略,自動的回收內存,保證JVM中的內存空間,防止出現內存泄露和內存溢出問題。

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