首先解釋下內存溢出和內存泄露之間的區別,為后面的學習做些鋪墊:
1、內存溢出和內存泄露的區別和聯系
內存溢出 out of memory:是指程序申請內存時,沒有足夠的內存供申請者使用,或者說,給了你一塊存儲int類型數據的存儲空間,但是你卻存儲long類型的數據,那么結果就是內存不夠用,此時就會報錯OOM,即所謂的內存溢出
內存泄露 memory leak:是指程序在申請內存后,無法釋放已申請的內存空間,一次內存泄漏似乎不會有大的影響,但內存泄漏堆積后的后果就是內存溢出。
memory leak會最終會導致out of memory!
2、JVM中的堆棧和數據結構中堆棧的區別
在數據結構中,堆是完全二叉樹,堆中個元素是有序的。而棧是一種特殊的線性表,具有先進后出,只允許在一端(棧頂)插入、刪除的特點。
在jvm虛擬機中的堆棧對應內存的不同區域,籠統的說堆是用于存放對象的內存塊,棧是用于執行程序的內存塊。
下面切入正題
運行時數據區域
Java虛擬機在執行java程序的過程中會把它所管理的內存劃分為若干個不同的數據區域,這些區域有各自的用途,以及創建和銷毀的時間。有的區域隨著虛擬機進程的啟動而存在,有些區域則依賴用戶線程的啟動和結束而建立和銷毀。java虛擬機所管理的內存將會包含以下幾個運行時數據區域
1、線程獨有的內存區域有如下幾塊:
(1)、程序計數器:是一塊較小的內存空間,它可以看作是當前線程所執行字節碼的行號指示器。在虛擬機的概念模型里,字節碼解釋器工作時就是通過改變這個計數器的值來取下一條需要執行的字節碼的指令。由于Java虛擬機的多線程是通過線程輪流切換并分配處理器執行時間的方式實現的,在任何一個確定的時刻,一個處理器(對于多核處理器來說就是一個核)都只會執行一條線程中的指令。因此,為了線程切換后能恢復到正確的執行位置,每條線程都需要有一個獨立的程序計數器,各條線程計數器互不影響,獨立存儲,我們稱這類內存為區域為“線程私有”的內存。
此內存區域是唯一一個在java虛擬機規范中沒有規定任何OutOfMemoryError情況的區域。 (2)、java虛擬機棧:它的生命周期與線程相同。虛擬機棧描述的是java方法執行的內存模型,每個方法在執行的同時都會創建一個棧幀用于存儲局部變量表、操作數棧、動態鏈接、方法出口等信息。每一個方法從調用直至執行完成的過程,就對應著棧幀在虛擬機中入棧到出棧的過程。
(3)、本地方法棧:與虛擬機棧發揮的作用是非常相似的,他們之間的區別不過是虛擬機棧為虛擬機執行java方法(也就是字節碼)服務,而本地方法棧則為虛擬機使用到的Native方法服務。
2、線程間共享的內存區域有如下幾塊:
(1)、java堆:對于大多數java應用來說,java堆是java虛擬機所管理的內存中最大的一塊。Java堆是被所有線程共享的一塊內存區域,在虛擬機啟動時創建。此內存區域的唯一目的就是存放對象實例,幾乎所有的對象實例都是在這里分配的。Java堆是垃圾收集器管理的主要區域,因此很多時候也被稱為“GC堆”(Garbage Collected Heap)。
(2)、方法區:用于存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯后的代碼數據。雖然java虛擬機規范把方法區描述為堆的一個邏輯部分,但它卻有一個別名叫做Non-Heap(非堆),目的是與java堆區分開來。
(3)、運行時常量池:是方法區的一部分。Class文件中除了有類的版本、字段、方法、接口、等描述信息外,還有一項信息是常量池,用于存放編譯期生成的各種字面量和符號引用,這部分內容將在類加載后進入方法區的運行時常量池存放。既然運行時常量池是方法區的一部分,再染收到方法區內存的限制,當常量池無法再申請到內存時會拋出OutOfMemoryError異常。
3、直接內存
直接內存并不是虛擬機運行時數據區的一部分,也不是Java虛擬機規范中定義的內存區域。但是這部分內存也被頻繁地使用,而且也可能導致內存溢出問題。JDK1.4中新增加了NIO(參考https://www.ibm.com/developerworks/cn/education/java/j-nio/j-nio.html),引入了一種基于通道(channel)與緩沖區(buffer)的I/O方式,它可以使用Native函數庫直接分配堆外內存,然后通過一個存儲在Java堆中的DirectByteBuffer對象作為這塊內存的引用進行操作。這樣能在一些場景中顯著提高性能,因為避免了在Java堆和Native堆中來回復制數據。顯然,本機直接內存的分配不會受到Java堆大小的限制,但是,既然是內存,肯定還是會受到本機總內存(包括RAM、SWAP區)大小以及處理器尋址空間的限制。
分類: JAVA虛擬機
標簽: JVM, JAVA, JAVA虛擬機, OOM