目錄
- JVM初識
- 1、JVM的位置
- 2、JVM的體系結構
- 3、類加載器
- 示例:
- 4、雙親委派機制
- 5、沙箱安全機制
- 組成沙箱的基本元件:
- 6、Native
- 7、PC寄存器
- 8、方法區
- 9、棧
- 10、三種JVM
- 11、堆[重點]
- 12、新生區、老年區、永久區
- 新生區
- 永久區
- 示例:vm options
- 13、堆記憶體調優
- 問:
- 示例:jprofiler用法
- 14、GC:垃圾回收(自動進行)
- GC的作用區隻有堆--綠色部分
- GC兩種類型:
- 題目:
- 引用計數法:
- 複制算法:
- 标記清除法:
- 标記清除壓縮:
- 總結:
- 15、JMM Java記憶體模型
- 補充:
- 讀深入了解JAVA虛拟機
- 運作時是資料區域
- 程式計數器
- Java虛拟機棧
- 本地方法棧
- Java堆
- 方法區
- 運作時常量池
- 直接記憶體
- HotSpot虛拟機對象探秘
- 運作時是資料區域
JVM初識
- 請你談談對JVM的了解?java8虛拟機和之前的變化更新?
- 什麼是OOM,什麼是棧溢出 StackOverFlowError?怎麼分析?
- JVM的常用調優參數有哪些?
- 記憶體快照如何抓取,怎麼分析Dump檔案?
- 談談JVM中,類加載器你的認識?
1、JVM的位置
2、JVM的體系結構
3、類加載器
- 作用:加載class檔案 ---》new Student();
1、虛拟機自帶的加載器
2、啟動類(根)加載器
3、擴充類加載器
4、應用程式(系統類)加載器
示例:
package lesson04;
public class Car {
public int age;
public static void main(String[] args) {
Car car1 = new Car();
Car car2 = new Car();
Car car3 = new Car();
System.out.println(car1.hashCode());
System.out.println(car2.hashCode());
System.out.println(car3.hashCode());
Class c1 = car1.getClass();
Class c2 = car2.getClass();
Class c3 = car3.getClass();
System.out.println(c1.hashCode());
System.out.println(c2.hashCode());
System.out.println(c3.hashCode());
ClassLoader classLoader = c1.getClassLoader();
System.out.println(classLoader);//AppClassLoader
ClassLoader parent = classLoader.getParent();
System.out.println(parent);//ExtClassLoader jre/lib/ext
ClassLoader parent1 = parent.getParent();
System.out.println(parent1);//null rt.jar
}
}
結果:
460141958
1163157884
1956725890
685325104
685325104
685325104
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@1540e19d
null
4、雙親委派機制
示例:
package java.lang;
public class String {
/**
*雙親委派機制:安全
* 1、APP ---->EXT---->BOOT(最終執行)
*/
public String toString(){
return "hello";
}
public static void main(String[] args) {
String s = new String();
System.out.println(s.getClass().getClassLoader());
s.toString();
}
/**
* 1、類加載器收到類加載的請求
* 2、将這個請求向上委托給父類加載器完成,一直向上委托,直到跟加載器BOOT
* 3、啟動類加載器是否能夠加載目前這個類,能加載就結束,使用目前的加載器,否則,抛出異常,通知子加載器進行加載
* 4、重複步驟 3
*
* null :Java調用不到——————c\c++
* Java = C++; 去掉指針和記憶體管理 --》C++--
*/
}
結果:
錯誤: 在類 java.lang.String 中找不到 main 方法, 請将 main 方法定義為:
public static void main(String[] args)
否則 JavaFX 應用程式類必須擴充javafx.application.Application
Process finished with exit code 1
5、沙箱安全機制
組成沙箱的基本元件:
- 位元組碼校驗器(bytecode verifier):確定java類檔案遵循java語言規範,這樣可以幫助java程式實作記憶體保護,但并不是所有的類檔案都會經過位元組碼校驗,比如核心類。
- 類裝載器(class loader):其中類裝載器在3個方面對Java沙箱起作用
- 它防止惡意代碼區幹涉善意的代碼;//雙親委派機制
- 它守護了被信任的類庫邊界
- 它将代碼歸入保護域,确定了代碼可以進行那些操作
類裝載器采用的機制是雙親委派機制
1、從最内層的JVM自帶類加載器開始加載,外層惡意同名類得不到加載進而無法使用;
2、由于嚴格通過包來區分了通路域,外層惡意的類通過内置代碼也無法擷取權限通路到内層類,破壞代碼就自然無法生效;
- 存取控制器(access controller ):存取控制器可以控制核心API對作業系統的存取權限,而這個控制的政策設定,可以由使用者指定。
- 安全管理器(security manager):是核心API和作業系統之間的主要接口。實作權限控制,比存取控制器優先級高。
- 安全軟體包(security package):java.security下的類和擴充包下的類,允許使用者為自己的應用增加新的安全特性 包括:
- 安全提供者
- 消息摘要
- 數字簽名 keytools
- 加密
- 鑒别
6、Native
package test;
public class Demo {
public static void main(String[] args) {
new Thread(()->{},"my thread name").start();
}
/**
* native:凡是帶了native 關鍵字的,說明Java的作用範圍達不到了,會去掉底層C語言的庫
* 會進入本地方法棧
* 調用本地方法本地接口 JNI java native interface
* JNI作用:擴充java的使用,融合不同的程式設計語言為JAVA所用! 最初:c c++
* Java誕生的時候 C C++ 橫行,想要立足 就必須要有調用 C C++ 的程式
* 它在記憶體區域中專門開辟了一塊标記區域:Native Method Stack【本地方法棧】 登記了 native 方法
* 在最終執行的時候,加載本地方法庫中的方法通過JNI
*
* java程式驅動列印機,管理系統,掌握即可,在企業級應用中較為少見!
*/
public native void hello();
/**
* 調用其他接口 : Socket; WebService; http;
*/
}
7、PC寄存器
程式計數器:program counter register
每個線程都有一個程式計數器,是線程私有的,就是一個指針,指向方法區中的方法位元組碼(用來存儲指向像一條指令的位址,也即将要執行的指令代碼),在執行引擎讀取下一條指令,是一個非常小的記憶體空間,幾乎可以忽略不計
8、方法區
Method Area 方法區
方法區是被所有線程共享的,所有字段和方法位元組碼,以及一些特殊方法,如構造函數、接口代碼也在此定義,簡單說,所有定義的方法的資訊都儲存在該區域,此區域屬于共享區域
[static]靜态變量、[final]常量、[Class]類資訊(構造方法、接口定義)、運作時的常量池存放方法去中,但是 執行個體變量存在堆記憶體中,和方法無關
9、棧
-
棧是資料結構
程式 = 資料結構 + 算法
-
棧===》先進後出 後進先出
隊列==》先進先出 後進後出(FIFO : first input first output)
- 棧:棧記憶體,主管程式的運作,生命周期和線程同步,線程結束,棧記憶體也就釋放了,對于棧來說不存在垃圾回收,一旦線程結束,棧就Over了
- 棧存儲:八大基本類型 + 對象的引用(位址) + 執行個體方法
- 棧運作原理:棧幀
- 棧滿了:StackOverflowError 【error虛拟機就停止了】
- 棧 + 堆 + 方法區 :互動關系 【一個對象在記憶體中執行個體化的過程】
10、三種JVM
-
Sun公司
Java -version可以檢視
HotSpot Java HotSpot(TM) 64-Bit Server VM (build 25.271-b09, mixed mode)
- BEA公司 JRockit
- IBM公司 J9VM
11、堆[重點]
- Heap:一個JVM隻有一個堆記憶體,堆記憶體的大小是可以調節的。
- 類加載器讀取了類檔案後,一般會把什麼東西放到堆中呢? 類、方法、常量、變量--儲存我們所有引用的真實對象
- 堆記憶體中還要細分為三個區域:
- 新生區 (伊甸園)Young/New
- 養老去old
- 永久區
12、新生區、老年區、永久區
新生區
- 一個類誕生、成長的地方,甚至死亡
- 伊甸園區:所有的對象都是在伊甸園區new出來的
- 幸存者區 0區 1區
真理:經過研究,99%的對象都是臨時對象!
永久區
這個區域常駐記憶體的,用來存放JDK自身攜帶的Class對象,Interface中繼資料,存儲的是java運作時的一些環境或類資訊--------這個區域不存在垃圾回收!關閉虛拟機就會釋放永久區的記憶體。
假設:一個啟動類,加載了大量的第三方jar包。tomcat部署了太多的應用。大量動态生成的反射類。 不斷的被加載,知道記憶體滿了,就會出現OOM。
- JDK1.6之前:永久代,常量池在方法區
- JDK1.7:永久代,但是慢慢的退化了【去永久代,常量池在堆中】
- JDK1.8之後:無永久代,常量池在元空間
元空間:邏輯上存在,實體上不存在 新生區 + 老年代 = JVM maxMemory
示例:vm options
vm options: -Xms1024m -Xmx1024m -XX:+PrintGCDetails
package test;
public class Demo02 {
public static void main(String[] args) {
//傳回虛拟機試圖使用的最大記憶體
long maxMemory = Runtime.getRuntime().maxMemory();//位元組 1024 * 1024
//傳回JVM的初始化總記憶體
long totalMemory = Runtime.getRuntime().totalMemory();
System.out.println("maxMemory:"+maxMemory+"位元組\t"+maxMemory/(double)1024/1024+"MB");
System.out.println("totalMemory:"+totalMemory+"位元組\t"+totalMemory/(double)1024/1024+"MB");
/**
* 預設情況下:配置設定的總記憶體 是電腦記憶體的1/4,而初始化的記憶體 是1/64
*/
/**
* 遇到OOM:
* 解決方式:
* 1、嘗試将堆記憶體擴大 看結果
* 2、分析記憶體,看一下那個地方出現問題(專業工具)
*
* 初識記憶體和總記憶體都設定為1024M 并且列印GC消息
* -Xms1024m -Xmx1024m -XX:+PrintGCDetails
*/
/**
* 305664K + 699392K = 1005056K (除1024)---》 981.5M
*/
}
}
結果:
maxMemory:1029177344位元組 981.5MB
totalMemory:1029177344位元組 981.5MB
Heap
PSYoungGen total 305664K, used 20971K [0x00000000eab00000, 0x0000000100000000, 0x0000000100000000)
eden space 262144K, 8% used [0x00000000eab00000,0x00000000ebf7afb8,0x00000000fab00000)
from space 43520K, 0% used [0x00000000fd580000,0x00000000fd580000,0x0000000100000000)
to space 43520K, 0% used [0x00000000fab00000,0x00000000fab00000,0x00000000fd580000)
ParOldGen total 699392K, used 0K [0x00000000c0000000, 0x00000000eab00000, 0x00000000eab00000)
object space 699392K, 0% used [0x00000000c0000000,0x00000000c0000000,0x00000000eab00000)
Metaspace used 3235K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 354K, capacity 388K, committed 512K, reserved 1048576K
Process finished with exit code 0
13、堆記憶體調優
問:
在一個項目中,突然出現了OOM故障,那麼該如何排除?研究為什麼出錯?
- 能夠看到代碼第幾行出錯:記憶體快照分析工具 MAT Jprofiler
- Debug:一行行分析代碼
MAT Jprofiler作用
- 分析Dump記憶體檔案,快速定位記憶體洩露;
- 獲得堆中的資料
- 獲得大的對象
安裝插件
- File ----》settings ----》 Plugins ----》 搜Jprofiler ---》search in market ----> install
- 安裝完之後重新開機IDEA
- 下載下傳安裝Jprofiler 自定義安裝路徑要沒有中文和空格 建議下載下傳9.2.1的可用下面注冊碼
-
彈出的license information
選enter license key ---》name company 随便選 ----》 注冊碼如下選一個
[email protected]#23874-hrwpdp1sh1wrn#0620
[email protected]#36573-fdkscp15axjj6#25257
[email protected]#5481-ucjn4a16rvd98#6038
[email protected]#99016-hli5ay1ylizjj#27215
[email protected]#40775-3wle0g1uin5c1#0674
-
重新打開IDEA File ---> settings --->Tools ---> JProfiler ---> JProfiler executable
找到下載下傳路徑的bin目錄下的.exe 之後點選Apply OK
示例:jprofiler用法
可以根據不同的錯 dump出檔案
- -Xms :設定初始化記憶體大小 1/64
- -Xmx :設定最大配置設定記憶體 1/4
- -XX:+PrintGCDetails :列印GC垃圾回收資訊
- -XX:+HeapDumpOnOutOfMemoryError :OOM dump
VM options: -Xms1m -Xmx4m -XX:+HeapDumpOnOutOfMemoryError
package test;
import java.util.ArrayList;
//Dump
public class Demo03 {
byte[] array = new byte[1024*1024];
public static void main(String[] args) {
ArrayList<Object> list = new ArrayList<>();
int count = 0;
//OOM
try {
while (true){
list.add(new Demo03());
count++;
}
} catch (Exception e) {
System.out.println("count+"+count);
}
/**
* Throwable
* Exception
* Error
*/
}
}
結果:
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid9696.hprof ...
Heap dump file created [3514346 bytes in 0.045 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at test.Demo03.<init>(Demo03.java:8)
at test.Demo03.main(Demo03.java:16)
Process finished with exit code 1
先看大對象-----》看線程 能定位到第幾行!!!
14、GC:垃圾回收(自動進行)
GC的作用區隻有堆--綠色部分
JVM在進行GC時:并不是對這三個區域統一回收,大部分的時候,回收的都是新生代~
- 新生代
- 幸存區 (from to兩個區 會交換的區域~~ from變to to變from)
- 老年區
GC兩種類型:
- 輕GC [普通的GC]: 新生代和幸存區(偶爾幸存區滿的話)
- 重GC [全局GC]:全部清
題目:
- JVM的記憶體模型和分區!詳細到每個區放什麼?
- 堆裡面的分區有哪些? Eden from to 老年區,說說他們的特點?
- GC的算法有那些?标記清除法、标記整理(壓縮)法、複制算法、引用計數法
- 輕GC 和 重GC 分别在什麼時候發生?
引用計數法:
複制算法:
- 好處:沒有記憶體碎片!
- 壞處:浪費了記憶體空間(多了一半空間永遠是空的to,假設對象100%存活(極端情況:from區中好多對象都複制到to中))
複制算法最佳使用場景:對象存活度較低的時候:新生區
标記清除法:
- 優點:不需要額外的空間!
- 缺點:兩次掃描嚴重浪費時間,會産生記憶體碎片。
标記清除壓縮:
對标記清除的優化~~~
再次優化
- 先标記清除幾次-------》再進行壓縮
總結:
- 記憶體效率:複制算法 > 标記清除算法 > 标記壓縮算法 (時間複雜度)
- 記憶體整齊度:複制算法 = 标記壓縮算法 > 标記清除算法
- 記憶體使用率:标記壓縮算法 = 标記清除算法 > 複制算法
-
思考?沒有最優的JVM算法嗎? ----木有~~ 沒有最好的算法~·隻有最合适的(看場景)
是以---------GC : 分代收集算法
- 年輕代:【存活率低】 複制算法
- 老年代:【區域大、存活率高】标記清除+标記壓縮 混合實作(JVM調優,清多少次在壓等等)
15、JMM Java記憶體模型
Java Memory Model
-
JMM是幹嘛的? 作用:緩存一緻性,用于定義資料讀寫的規則(遵守這個規則)
JMM定義了線程工作記憶體和主記憶體之間的抽象關系:線程之間共享變量存儲在主記憶體(Main Memory)中,每個線程都有一個私有的本地記憶體(Local Memory)
解決共享對象可見性這個問題:volilate [當線程更改了共享變量 會馬上重新整理到主記憶體,保證其他線程取時候是正确的]
- 如何學習? 學volilate
- JMM制定了一些規則:
- 不允許read和load、store和write操作之一單獨出現。即 使用了read必須load,使用了store必須write
- 不允許線程丢棄它最近的assign操作,即 工作變量的資料改變了之後,必須告知主記憶體
- 不允許一個線程将沒有assign的資料從工作記憶體同步回主記憶體
- 一個新的變量必須在主記憶體中誕生,不允許工作記憶體直接使用一個未被初始化的變量。就是對變量實施use、store操作之前,必須經過assign和load操作
- 一個變量同一時間隻有一個線程對其進行lock操作。多次lock後,必須執行相同次數的unlock才能解鎖
- 如果對一個變量進行lock操作,會清空所有工作記憶體中此變量的值,在執行引擎使用這個變量前,必須重新assgin或load操作初始化變量的值
- 如果一個變量沒有被lock,就不能對其進行unlock操作,也不能unlock一個被其他線程鎖住的變量
- 對一個變量進行unlock之前,必須把此變量同步回主記憶體
補充:
多看部落格,多百度~~·加油!
思維導圖網站:https://www.processon.com/
讀深入了解JAVA虛拟機
運作時是資料區域
程式計數器
- 程式計數器是一塊較小的記憶體空間,它可以看作是目前線程所執行的位元組碼的行号訓示器。
- 在虛拟機的概念模型裡(僅是概念模型,各種虛拟機可能會通過一些更高效的方式去實作),位元組碼解釋器工作時就是通過改變這個計數器的值來選取下一條需要執行的位元組碼指令,分支、循環、跳轉、異常處理、線程恢複等基礎功能都需要依賴這個計數器來完成。
- 由于java虛拟機的多線程是通過線程輪流切換并配置設定處理器執行時間的方式來實作的,在任何一個确定的時刻,一個處理器(對于多核處理器來說是一個核心)都隻會執行一條線程中的指令。是以,為了線程切換後能恢複到正确的執行位置,每條線程都需要有一個獨立的程式計數器,各條線程之間計數器互不影響,獨立存儲,我們稱這類記憶體區域為“線程私有“的記憶體。
- 如果線程正在執行的是一個java方法,這個計數器記錄的是正在執行的虛拟機位元組碼指令的位址;如果正在執行的是native方法,這個計數器值則為空(undefined)。
- 此記憶體區域是唯一一個在java虛拟機規範中沒有規定任何OutOfMemoryError情況的區域。
Java虛拟機棧
- 與程式計數器一樣,java虛拟機棧也是線程私有的,它的生命周期與線程相同。
- 虛拟機棧描述的是java方法執行的記憶體模型:每個方法在執行的同時都會建立一個棧幀用于存儲局部變量表、操作數棧、動态連結、方法出口等資訊。每一個方法從調用直至執行完成的過程,就對應這個一個棧幀在虛拟機棧中入棧到出棧的過程。
- 局部變量表存放了編譯器可知的各種基本資料類型(八大基礎類型)、對象引用(reference類型,它不等同于對象本身,可能是一個指向起始位址的引用指針,也可能是指向一個代表對象的句柄或其他與此對象相關的位置)和returnAddress類型(指向了一條位元組碼指令的位址)。
- 其中64位長度的long和double類型的資料會占用2個局部變量空間(slot),其餘的資料類型隻占用1個。局部變量表所需的記憶體空間在編譯期間完成配置設定,當進入一個方法時,這個方法需要在幀中配置設定多大的局部變量空間時完全确定的,在方法運作期間不會改變局部變量表的大小。
-
如果線程請求的棧深度大于虛拟機所允許的深度,将抛出StackOverflowError異常;
如果虛拟機棧可以擴充(目前大部分的java虛拟機都可以動态擴充,隻不過java虛拟機規範中也允許固定長度的虛拟機棧),如果擴充時無法申請到足夠的記憶體,就會抛出OutOfMemoryError異常。
本地方法棧
- 本地方法棧與虛拟機所發揮的作用是非常相似的,它們之間的差別不過是虛拟機棧為虛拟機執行java方法(也就是位元組碼)服務,而本地方法棧則為虛拟機使用到的Native方法服務。
- 在虛拟機規範中對本地方法棧中方法使用的語言、使用方式與資料結構并沒有強制規定,是以具體的虛拟機可以自由實作它。
- 有的虛拟機直接把本地方法棧和虛拟機棧合二為一(e.g.Sun HotSpot虛拟機)。
- 與虛拟機棧一樣,本地方法棧區域也會抛出StackOverflowError和OutOfMemoryError異常。
Java堆
- 對于大多數應用來說,java堆是java虛拟機所管理的記憶體中最大的一塊。
- java堆是被所有線程共享的一塊記憶體區域,在虛拟機啟動時建立。此記憶體區域的唯一目的就是存放對象執行個體,幾乎所有的對象執行個體都在這裡配置設定記憶體。
- java堆是垃圾收集器管理的主要區域,是以很多時候也被稱為”GC堆“。從記憶體回收的角度來看,由于現在收集器基本都采用分代收集算法,是以java堆中還可以細分為:新生代和老年代;再細緻一點的有Eden空間、From Survivor空間、To Survivor空間等。從記憶體配置設定的角度來看,線程共享的java堆中可能劃分出多個線程私有的配置設定緩沖區(TLAB)。不過無論如何劃分,都與存放内容無關,無論哪個區域,存儲的都仍然是對象執行個體。
- java堆可以處于實體不連續的記憶體空間中,隻要邏輯上是連續的即可,就像我們的磁盤空間一樣。
- 在實作時,既可以實作成固定大小的,也可以時可擴充的,不過目前主流的虛拟機都是按照可擴充來實作的(通過-Xmx和-Xms控制)。
- 如果在堆中沒有記憶體完成執行個體配置設定,并且堆也無法再擴充時,将會抛出OutOfMemoryError異常。
方法區
- 方法區與java堆一樣,是各個線程共享的記憶體區域,它用于存儲已被虛拟機加載的類資訊、常量、靜态變量、即時編譯器編譯後的代碼等資料。
- 對于習慣在HotSpot虛拟機上開發、部署程式的開發者來說,很多人都更願意把方法區稱為”永久代“,本質上兩者并不等價,僅僅是因為HotSpot虛拟機的設計團隊選擇把GC分代收集擴充至方法區,或者說使用永久代來實作方法區而已,這樣HotSpot的垃圾收集器可以像管理java堆一樣管理這部分記憶體,能夠省專門為方法區編寫記憶體管理代碼的工作。對于其他虛拟機來說是不存在永久代的概念的。
- java虛拟機規範對方法區的限制非常寬松,除了和java堆一樣不需要連續的記憶體和可以選擇固定大小或者可拓展外,還可以選擇不實作垃圾收集。
- 根據java虛拟機規範的規定,當方法區無法滿足記憶體配置設定需求時,将抛出OutOfMemoryError異常。
運作時常量池
- 運作時常量池時方法區的一部分。Class檔案中除了有類的版本、字段、方法、接口等描述資訊外,還有一項資訊時常量池,用于存放編譯期生成的各種字面量和符号引用,這部分内容将在類加載後進入方法區的運作時常量池中存放。
- java虛拟機對class檔案每一部分(自然也包括常量池)的格式都有嚴格規定,每一個位元組用于存儲哪種資料都必須符合規範上的要求才會被虛拟機認可、裝載和執行,但對于運作時常量池,java虛拟機規範沒有做任何細節要求,不同的提供商實作的虛拟機可以按照自己的需求來實作這個記憶體區域。不過,一般來說,除了儲存class檔案中描述的符号引用外,還會把翻譯出來的直接引用也存儲在運作時常量池中。
- 運作時常量池相對于class檔案常量池的另外一個重要特征時具備動态性,java語言并不要求常量一定隻有編譯期才能産生,也就是并非預置入class檔案中常量池的内容才能進入方法區運作時常量池,運作期間也可能将新的常量放入池中,這種特性被開發人員利用得比較多的便是String類的intern()方法。
- 運作時常量池是方法區的一部分,自然也受到方法區記憶體的限制,當常量池無法再申請到記憶體是會抛出OutOfMemoryError異常。
直接記憶體
- 直接記憶體并不是虛拟機運作時資料區的一部分,也不是java虛拟機規範中定義的記憶體區域。但是這部分記憶體也被頻繁地使用,而且也可能導緻OutOfMemoryError異常出現。
- 在jdk1.4新加入了NIO(New Input/Output)類,引入了一種基于通道(channel)和緩沖區(buffer)的I/O方式,它可以使用Native函數庫直接配置設定堆外記憶體,然後通過一個存儲在java堆中的DirectByteBuffer對象作為這塊記憶體的引用進行操作。這樣能在一些場景中顯著提高性能,因為避免了在java堆和native堆中來回複制資料。
- 顯然,本機直接記憶體的配置設定不會受到java堆大小的限制,但是,既然是記憶體,肯定還是會受到本機總記憶體大小以及處理器尋址的限制。
- 伺服器管理者在配置虛拟機參數時,會根據實際記憶體設定-Xmx等參數資訊,但經常忽略直接記憶體,使得各個記憶體區域總和大于實體記憶體限制(包括實體的和作業系統級的限制),進而導緻動态擴充時出現OutOfMemoryError異常。