主頁 > 後端開發 > Java記憶體區域與記憶體溢位例外(1)

Java記憶體區域與記憶體溢位例外(1)

2022-08-18 07:51:07 後端開發

1. 運行時資料區域

??Java虛擬機在執行Java程式的程序中會把它所管理的記憶體劃分為若干個不同的資料 區域,這些區域都有各自的用途,以及創建和銷毀的時間,有的區域隨著虛擬機行程的 啟動而存在,有些區域則是依賴用戶執行緒的啟動和結束而建立和銷毀,根據《Java虛擬 機規范(第2版)》的規定,Java虛擬機所管理的記憶體將會包括以下幾個運行時資料區 域,如圖2-1所示,

??1.1 程式計數器

??程式計數器(Program Counter Register)是一塊較小的記憶體空間,它的作用可以看 做是當前執行緒所執行的位元組碼行號指示器,在虛擬機的概念模型里(僅是概念模型, 各種虛擬機可能會通過一些更高效的方式去實作),位元組碼解釋器作業時就是通過改變 這個計數器的值來選取下一條需要執行的位元組碼指令,分支、回圈、跳轉、例外處理、 執行緒恢復等基礎功能都需要依賴這個計數器來完成,

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

??如果執行緒正在執行的是一個Java方法,這個計數器記錄的是正在執行的虛擬機位元組 碼指令的地址;如果正在執行的是Natvie方法,這個計數器值則為空Undefined)此 記憶體區域是唯一一個在Java虛擬機規范中沒有規定任何OutOfMemoryError情況的區域,

??1.2 Java虛擬機堆疊

??與程式計數器一樣,Java虛擬機堆疊(Java Virtual Machine Stacks)也是執行緒私有的, 它的生命周期與執行緒相同,虛擬機堆疊描述的是Java方法執行的記憶體模型:每個方法被執行的時候都會同時創建一個堆疊幀Stack Frame?)用于存盤區域變數表、操作堆疊、動態鏈接、方法出口等資訊,每一個方法被呼叫直至執行完成的程序,就對應著一個堆疊幀在 虛擬機堆疊中從入堆疊到出堆疊的程序,

??經常有人把Java記憶體區分為堆記憶體(Heap)和堆疊記憶體(Stack),這種分法比較粗 糙,Java記憶體區域的劃分實際上遠比這復雜,這種劃分方式的流行只能說明大多數程式 員最關注的、與物件記憶體分配關系最密切的記憶體區域是這兩塊,其中所指的"堆”在后 面會專門講述,而所指的“堆疊”就是現在講的虛擬機堆疊,或者說是虛擬機堆疊中的區域變 量表部分

??區域變數表存放了編譯期可知的各種基本資料型別(boolean、byte、char、short、int、 float、long、double),物件參考(reference型別,它不等同于物件本身,根據不同的虛擬 機實作,它可能是一個指向物件起始地址的參考指標,也可能指向一個代表物件的句柄或 者其他與此物件相關的位置)和retumAddress型別(指向了一條位元組碼指令的地址),

??其中64位長度的long和double型別的資料會占用2個區域變數空間(Slot),其余 的資料型別只占用1個,區域變數表所需的記憶體空間在編譯期間完成分配,當進入一個 方法時,這個方法需要在幀中分配多大的區域變數空間是完全確定的,在方法運行期間 不會改變區域變數表的大小,

??在Java虛擬機規范中,對這個區域規定了兩種例外狀況:如果執行緒請求的堆疊深度大 于虛擬機所允許的深度,將拋出StackOverflowError例外;如果虛擬機堆疊可以動態擴展 (當前大部分的Java虛擬機都可動態擴展,只不過Java虛擬機規范中也允許固定長度的 虛擬機堆疊),當擴展時無法申請到足夠的記憶體時會拋出OutOfMemoryError例外,

??1.3 本地方法堆疊

??本地方法堆疊(Native Method Stacks)與虛擬機堆疊所發揮的作用是非常相似的,其 區別不過是虛擬機堆疊為虛擬機執行Java方法(也就是位元組碼)服務,而本地方法堆疊則 是為虛擬機使用到的Native方法服務,虛擬機規范中對本地方法堆疊中的方法使用的語 言、使用方式與資料結構并沒有強制規定,因此具體的虛擬機可以自由實作它,甚至 有的虛擬機(譬如Sun HotSpot虛擬機)直接就把本地方法堆疊和虛擬機堆疊合二為一, 與虛擬機堆疊一樣,本地方法堆疊區域也會拋出StackOverflowError和OutOfMemoryError 例外,

??1.4 Java 堆

??對于大多數應用來說,Java堆(Java Heap)是Java虛擬機所管理的記憶體中最大的 一塊,Java堆是被所有執行緒共享的一塊記憶體區域,在虛擬機啟動時創建,此記憶體區域的 唯一目的就是存放物件實體,幾乎所有的物件實體都在這里分配記憶體,這一點在Java虛 擬機規范中的描述是:所有的物件實體以及陣列都要在堆上分配,但是隨著JIT編譯器 的發展與逃逸分析技術的逐漸成熟,堆疊上分配、標量替換?優化技術將會導致一些微妙 的變化發生,所有的物件都分配在堆上也漸漸變得不是那么“絕對"了,

??Java堆是垃圾收集器管理的主要區域,因此很多時候也被稱做“GC堆”(Garbage Collected Heap,幸好國內沒翻譯成“垃圾堆”),如果從記憶體回收的角度看,由于現在 收集器基本都是采用的分代收集演算法,所以Java堆中還可以細分為:新生代和老年代; 再細致-點的有Eden空間、From Survivor空間、To Survivor空間等,如果從記憶體分配 的角度看,執行緒共享的Java堆中可能劃分出多個執行緒私有的分配緩沖區(Thread Local Allocation Buffer, TLAB).不過,無論如何劃分,都與存放內容無關,無論哪個區域, 存盤的都仍然是物件實體,進一步劃分的目的是為了更好地回收記憶體,或者更快地分配 記憶體,

??根據Java虛擬機規范的規定,Java堆可以處于物理上不連續的記憶體空間中,只要 邏輯上是連續的即可,就像我們的磁盤空間一樣,在實作時,既可以實作成固定大小 的,也可以是可擴展的,不過當前主流的虛擬機都是按照可擴展來實作的(通過-Xmx 和-Xms控制),如果在堆中沒有記憶體完成實體分配,并且堆也無法再擴展時,將會拋出 OutOfMemoryError 例外,

??1.5 方法區

??方法區(Method Area)與Java堆一樣,是各個執行緒共享的記憶體區域,它用于存盤已被虛擬機加載的類資訊常量、靜態變數即時編譯器編譯后的代碼等資料,雖 然Java虛擬機規范把方法區描述為堆的一個邏輯部分,但是它卻有一個別名叫做Non-Heap (非堆),目的應該是與Java堆區分開來,

??對于習慣在HotSpot虛擬機上開發和部署程式的開發者來說,很多人愿意把方法區 稱為“永久代”(Permanent Generation),本質上兩者并不等價,僅僅是因為HotSpot虛 擬機的設計團隊選擇把GC分代收集擴展至方法區,或者說使用永久代來實作方法區而 已,對于其他虛擬機(如BEA JRockit, IBM J9等)來說是不存在永久代的概念的,即 使是HotSpot虛擬機本身,根據官方發布的路線圖資訊,現在也有放棄永久代并“搬家" 至Native Memory來實作方法區的規劃了,

??Java虛擬機規范對這個區域的限制非常寬松,除了和Java堆一樣不需要連續的內 存和可以選擇固定大小或者可擴展外,還可以選擇不實作垃圾收集,相對而言,垃圾 收集行為在這個區域是比較少出現的,但并非資料進入了方法區就如永久代的名字一 樣“永久”存在了,這個區域的記憶體回收目標主要是針對常量池的回收和對型別的卸 載,一般來說這個區域的回收"成績”比較難以令人滿意,尤其是型別的卸載,條件 相當苛刻,但是這部磁區域的回收確實是有必要的,在Sun公司的BUG串列中,曾出 現過的若干個嚴重的BUG就是由于低版本的HotSpot虛擬機對此區域未完全冋收而導 致記憶體泄漏,

??根據Java虛擬機規范的規定,當方法區無法滿足記憶體分配需求時,將拋出 OutOfMemoryError 例外,

??1.6 運行時常量池

??運行時常量池(Runtime Constant Pool)是方法區的一部分,Class檔案中除了有 類的版本、欄位、方法、介面等描述等資訊外,還有一項資訊是常量池(Constant Pool Table),用于存放編譯期生成的各種字面量和符號參考,這部分內容將在類加載后存放 到方法區的運行時常量池中,

??Java虛擬機對Class檔案的每一部分(自然也包括常量池)的格式都有嚴格的規 定,每一個位元組用于存盤哪種資料都必須符合規范上的要求,這樣才會被虛擬機認可、 裝載和執行,但對于運行時常量池,Java虛擬機規范沒有做任何細節的要求,不同的 提供商實作的虛擬機可以按照自己的需要來實作這個記憶體區域,不過,一般來說,除 了保存Class檔案中描述的符號參考外,還會把翻譯出來的直接參考也存盤在運行時常 量池中七

??運行時常量池相對于Class檔案常量池的另外一個重要特征是具備動態性,Java語 言并不要求常量一定只能在編譯期產生,也就是并非預置入Class檔案中常量池的內容 才能進入方法區運行時常量池,運行期間也可能將新的常量放入池中,這種特性被開發 人員利用得比較多的便是String類的intern,方法

??既然運行時常量池是方法區的一部分,自然會受到方法區記憶體的限制,當常量池無 法再申請到記憶體時會拋出OutOfMemoryError例外,

??1.7 直接記憶體

??直接記憶體(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 例外,

2. 物件訪問

??介紹完Java虛擬機的運行時資料區之后,我們就可以來探討一個問題:在Java語 言中,物件訪問是如何進行的?物件訪問在Java語言中無處不在,是最普通的程式行 為,但即使是最簡單的訪問,也會卻涉及Java堆疊、Java堆、方法區這三個最重要記憶體區 域之間的關聯關系,如下面的這句代碼:

Object obj = new Object();

??假設這句代碼出現在方法體中,那“Object obj”這部分的語意將會反映到Java堆疊 的本地變數表中,作為一個reference型別資料出現,而“new Object()”這部分的語意 將會反映到Java堆中,形成一塊存盤了 Object型別所有實體資料值(Instance Data,對 象中各個實體欄位的資料)的結構化記憶體,根據具體型別以及虛擬機實作的物件記憶體布 局(Object Memory Layout)的不同,這塊記憶體的長度是不固定的,另外,在Java堆中 還必須包含能査找到此物件型別資料(如物件型別、父類、實作的介面、方法等)的地 址資訊,這些型別資料則存盤在方法區中,

??由于reference型別在Java虛擬機規范里面只規定了一個指向物件的參考,并沒有 定義這個參考應該通過哪種方式去定位,以及訪問到Java堆中的物件的具體位置,因此 不同虛擬機實作的物件訪問方式會有所不同,主流的訪問方式有兩種:使用句柄和直接 指標

  • 如果使用句柄訪問方式,Java堆中將會劃分出一塊記憶體來作為句柄池,reference 中存盤的就是物件的句柄地址,而句柄中包含了物件實體資料和型別資料各自的具體地址資訊,如圖2-2所示,

??

  • 如果使用直接指標訪問方式,Java堆物件的布局中就必須考慮如何放置訪問型別資料的相關資訊,reference中直接存盤的就是物件地址,如圖2-3所示,

??

??這兩種物件的訪問方式各有優勢,使用句柄訪問方式的最大好處就是reference中存 儲的是穩定的句柄地址,在物件被移動(垃圾收集時移動物件是非常普遍的行為)時只 會改變句柄中的實體資料指標reference本身不需要被修改

??使用直接指標訪問方式的最大好處就是速度更快,它節省了一次指標定位的時間開 銷,由于物件的訪問在Java中非常頻繁,因此這類開銷積少成多后也是一項非常可觀的執行成本,就虛擬機Sun HotSpot而言,它是使用第二種方式進行物件 訪問的,但從整個軟體開發的范圍來看,各種語言和框架使用句柄來訪問的情況也十分 常見,

3. 實戰:OutOfMemoryError 例外

??在Java虛擬機規范的描述中,除了程式計數器外,虛擬機記憶體的其他幾個運行時區 域都有發生OutOfMemoryError (下文稱OOM)例外的可能,本節將通過若干實體來驗 證例外發生的場景(代碼清單2-1至代碼清單2-6的幾段簡單代碼),并且會初步介紹幾 個與記憶體相關的最基本的虛擬機引數,

??本節內容的目的有兩個:第一,通過代碼驗證Java虛擬機規范中描述的各個運行 時區域儲存的內容;第二,希望讀者在作業中遇到實際的記憶體溢位例外時,能根據例外 的資訊快速判斷是哪個區域的記憶體溢位,知道怎樣的代碼可能會導致這些區域的記憶體溢 出,以及出現這些例外后該如何處理,

??下面代碼的開頭都注釋了執行時所需要設定的虛擬機啟動引數(注釋中“VMArgs” 后面跟著的引數),這些引數對實驗的結果有直接影響,請讀者除錯代碼的時候不要忽 略掉,如果讀者使用控制臺命令來執行程式,那直接跟在Java命令之后書寫就可以,如果使用Eclipse IDE,則可以參考圖2-4在Debug/Run頁簽中的設定,

??下文的代碼都是基于Sun HotSpot 17.1-b03 (JDK 1.6 Update 22中帶的虛擬機)運 行的,對于不同公司的不同版本的虛擬機,引數和程式運行的結果可能會有所差別,

??3.1 Java堆溢位

??Java堆用于儲存物件實體,我們只要不斷地創建物件,并且保證GC Roots到物件 之間有可達路徑來避免垃圾回識訓制清除這些物件,就會在物件數量到達最大堆的容量限制后產生記憶體溢位例外,

??代碼清單2-1中限制Java堆的大小為20MB,不可擴展(將堆的最小值-Xms參 數與最大值-Xmx引數設定為一樣即可避免堆自動擴展),通過引數-XX:+HeapDump OnOutOfMemoryError可以讓虛擬機在出現記憶體溢位例外時Dump出當前的記憶體堆轉儲 快照以便事后進行分析,

package test.one;

import java.util.ArrayList;
import java.util.List;

public class test {
	static class OOMObject{
		
	}
	public static void main(String[] args) {
		List<OOMObject> list = new ArrayList<>();
		while (true) {
			list.add(new OOMObject());
		}
		
	}

}

??

運行引數:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8

??Java堆記憶體的OOM例外是實際應用中最常見的記憶體溢位例外情況,出現Java堆記憶體溢位時,例外堆疊資訊"java.lang.OutOfMemoryError”會跟著進一步提示“Java heap space

??要解決這個區域的例外,一般的手段是首先通過記憶體映像分析工具(如Eclipse Memory Analyzer)對dump出來的堆轉儲快照進行分析,重點是確認記憶體中的物件是 否是必要的,也就是要先分清楚到底是出現了記憶體泄漏(Memory Leak)還是記憶體溢 (Memory Overflow)o 圖 2-5 顯示了使用 Eclipse Memory Analyzer 打開的堆轉儲快照檔案,

??如果是記憶體泄漏,可進一步通過工具査看泄漏物件到GC Roots的參考鏈,于是就 能找到泄漏物件是通過怎樣的路徑與GC Roots相關聯并導致垃圾收集器無法自動回收 它們的,掌握了泄漏物件的型別資訊,以及GC Roots參考鏈的資訊,就可以比較準確 地定位出泄漏代碼的位置,

??如果不存在泄漏,換句話說就是記憶體中的物件確實都還必須存活著,那就應當檢査 虛擬機的堆引數(-Xmx與-Xms),與機器物理記憶體對比看是否還可以調大,從代碼上 檢査是否存在某些物件生命周期過長、持有狀態時間過長的情況,嘗試減少程式運行期 的記憶體消耗,

??3.2 虛擬機堆疊和本地方法堆疊溢位

??由于在HotSpot虛擬機中并不區分虛擬機堆疊和本地方法堆疊,因此對于HotSpot來 說,-Xoss引數(設定本地方法堆疊大小)雖然存在,但實際上是無效的,堆疊容量只由-Xss 引數設定,關于虛擬機堆疊和本地方法堆疊,在Java虛擬機規范中描述了兩種例外:

  • 如果執行緒請求的堆疊深度大于虛擬機所允許的最大深度,將拋出StackOverflowError 例外,
  • 如果虛擬機在擴展堆疊時無法申請到足夠的記憶體空間,則拋出OutOfMemoryError 例外,

??這里把例外分成兩種情況看似更加嚴謹,但卻存在著一些互相重疊的地方:當堆疊空 間無法繼續分配時,到底是記憶體太小,還是已使用的堆疊空間太大,其本質上只是對同一 件事情的兩種描述而已,

??在筆者的實驗中,如果將實驗范圍限制于單執行緒中的操作,嘗試了下面兩種方法均無法讓虛擬機產生OutOfMemoryError例外,嘗試的結果都是獲得StackOverflowError 例外,測驗代碼如清單2-2所示,

  • 使用-Xss引數減少堆疊記憶體容量,結果:拋出StackOverflowError例外,例外出現 時輸出的堆疊深度相應縮小,
  • 定義了大量的本地變數,增加此方法幀中本地變數表的長度,結果:拋出 StackOverflowError例外時輸出的堆疊深度相應縮小,
package test.one;
//別忘了設定運行引數 -Xss128k
public class JavaVMStackSOF {
	private int stackLength = 1;
	public void stackLeak() {
		stackLength++;
		stackLeak();
	}
	public static void main(String[] args) {
		JavaVMStackSOF oom = new JavaVMStackSOF();
		try {
			oom.stackLeak();
		} catch (Exception e) {
			System.out.println("stack length:"+oom.stackLength);
			throw e;
		}
	}

}

??實驗結果表明:在單個執行緒下,無論是由于堆疊幀太大,還是虛擬機堆疊容量太小,當 記憶體無法分配的時候,虛擬機拋出的都是StackOverflowEnor例外,

??如果測驗時不限于單執行緒,通過不斷地建立執行緒的方式倒是可以產生記憶體溢位異 常,如代碼清單2-3所示,但是,這樣產生的記憶體溢位例外與堆疊空間是否足夠大并不存 在任何聯系,或者準確地說,在這種情況下,給每個執行緒的堆疊分配的記憶體越大,反而越容易產生記憶體溢位例外,

??原因其實不難理解,作業系統分配給每個行程的記憶體是有限制的,譬如32位的 Windows限制為2GB,虛擬機提供了引數來控制Java堆和方法區的這兩部分記憶體的最大值,剩余的記憶體為2GB (作業系統限制)減去Xmx (最大堆容量),再減去MaxPermSize (最大方法區容量),程式計數器消耗記憶體很小,可以忽略掉,如果虛擬機行程本身耗費的記憶體不計算在內,剩下的記憶體就由虛擬機堆疊和本地方法堆疊"瓜分” 了,每個執行緒分配到的堆疊容量越大,可以建立的執行緒數量自然就越少,建立執行緒時就越容易把剩下的記憶體耗盡,

??這一點需要在開發多執行緒應用的時候特別注意,出現StackOverflowError例外時有錯誤堆疊可以閱讀,相對來說,比較容易找到問題的所在,而且,如果使用虛擬機 默認引數,堆疊深度在大多數情況下(因為每個方法壓入堆疊的幀大小并不是一樣的,所以 只能說大多數情況下)達到1000 - 2000完全沒有問題,對于正常的方法呼叫(包括遞 歸),這個深度應該完全夠用了,但是,如果是建立過多執行緒導致的記憶體溢位,在不能 減少執行緒數或者更換64位虛擬機的情況下,就只能通過減少最大堆和減少堆疊容量來換 取更多的執行緒,如果沒有這方面的經驗,這種通過“減少記憶體"的手段來解決記憶體溢位 的方式會比較難以想到,

??下面代碼還是不要跑了,cpu直接跑滿了,系統直接卡死

package test.one;
//別忘了設定運行引數 -Xss2M
public class JavaVMStackOOM {
	private void dontStop() {
		while(true) {
		}
	}
	public void stackLeakByThread() {
		while(true) {
			Thread thread = new Thread(new Runnable() {
				
				@Override
				public void run() {
					dontStop();
				}
			});
			thread.start();
		}
	}
	public static void main(String[] args) {
		JavaVMStackOOM oom = new JavaVMStackOOM();
		oom.stackLeakByThread();
	}

}

??注意 特別提示一下,如果要嘗試運行上面這段代碼,記得要先保存當前的作業, 由于在Windows平臺的虛擬機中,Java的執行緒是映射到作業系統的內核執行緒上的,所 以上述代碼執行時有較大的風險,可能會導致作業系統假死,

??3.3 運行時常量池溢位

??如果要向運行時常量池中添加內容,最簡單的做法就是使用String.intem()這個 Native方法,該方法的作用是:如果池中已經包含一個等于此String物件的字串,則回傳代表池中這個字串的String物件;否則,將此String物件包含的字串添加到常量池中,并且回傳此String物件的參考,由于常量池分配在方法區內,我們可以通 過-XX:PermSize和-XX:MaxPermSize限制方法區的大小,從而間接限制其中常量池的容量,如代碼清單2-4所示,

package test.one;
//別忘了設定運行引數 -XX:PermSize=10M -XX:MaxPermSize=10M
import java.util.ArrayList;
import java.util.List;

public class RuntimeConstantPoolOOM {
	public static void main(String[] args) {
		List<String> list = new ArrayList<String>();
		int i = 0;
		while(true) {
			list.add(String.valueOf(i++).intern());
		}
	}
}

 ??

??3.4 方法區溢位

??方法區用于存放Class的相關資訊,如類名、訪問修飾符、常量池、欄位描述、方法描述等,對于這個區域的測驗,基本的思路是運行時產生大量的類去填 滿方法區,直到溢位,雖然直接使用Java SE API也可以動態產生類(如反射時的 GeneratedConstructorAccessor和動態代理等),但在本次實驗中操作起來比較麻 煩,在代碼清單2-5中,筆者借助CGLib?直接操作位元組碼運行時,生成了大量的動態類,

??值得特別注意的是,我們在這個例子中模擬的場景并非純粹是一個實驗,這樣的應 用經常會出現在實際應用中:當前的很多主流框架,如Spring和Hibernate對類進行增強時,都會使用到CGLib這類位元組碼技術,增強的類越多,就需要越大的方法區來保證 動態生成的Class可以加載入記憶體

??下面代碼還是不要跑了,記憶體跑滿才行

package com.intehel.demo.domain;

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

public class JavaMethodAreaOOM {
    public static void main(String[] args) {
        while (true) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(OOMObject.class);
            enhancer.setUseCache(false);
            enhancer.setCallback(new MethodInterceptor() {
                @Override
                public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                    return methodProxy.invokeSuper(o,objects);
                }
            });
            enhancer.create();
        }
    }
    static class OOMObject{

    }
}

??方法區溢位也是一種常見的記憶體溢位例外,一個類如果要被垃圾收集器回收掉,判定條件是非常苛刻的,在經常動態生成大量Class的應用中,需要特別注意類的回收狀況,這類場景除了上面提到的程式使用了 GCLib位元組碼增強外,常見的還有:大量JSP 或動態產生JSP檔案的應用(JSP第一次運行時需要編譯為Java類)、基于OSGi的應用 (即使是同一個類檔案,被不同的加載器加載也會視為不同的類)等,

??3.5 本機直接記憶體溢位

??DirectMemory容量可通過-XX:MaxDirectMemorySize指定,如果不指定,則默認與Java堆的最大值(-Xmx指定)一樣,代碼清單2-6越過了 DirectByteBuffer 類,直接通過反射獲取Unsafe實體并進行記憶體分配(Unsafe類的getUnsafe()方 法限制了只有引導類加載器才會回傳實體,也就是設計者希望只有rt.jar中的類 才能使用Unsafe的功能),因為,雖然使用DirectByteBuffer分配記憶體也會拋出 記憶體溢位例外,但它拋出例外時并沒有真正向作業系統申請分配記憶體,而是通過 計算得知記憶體無法分配,于是手動拋出例外,真正申請分配記憶體的方法是unsafe. allocateMemory(),

4. 小結

??通過本章的學習,我們明白了虛擬機里面的記憶體是如何劃分的, 樣的代碼和操作可能導致記憶體溢位例外,雖然Java有垃圾收集機制, 我們并不遙遠,本章只是講解了各個區域出現記憶體溢位例外的原因, Java垃圾收集機制為了避免記憶體溢位例外的出現都做了哪些努力,

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/502120.html

標籤:其他

上一篇:day22--Java集合05

下一篇:框架設計之魂——反射

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more