世界上沒有完美的程序,但寫程序是不斷追求完美的過程。
Devices(設備、裝置)、GlassFish(商業兼容應用服務器)
目錄
Java虛擬機棧(Java Virtual Machine Stacks“棧”)
1. Java技術體系包括:
- Java程序設計語言
- 各種硬件平臺上的JVM
- Class文件格式
- Java API 類庫
- 第三方類庫
JDK = Java程序設計語言+JVM+Java API
JRE = Java API 類庫中Java SE API子集 + JVM
ERP(Enterprise Resource Planning企業資源計劃 )& CRM(Customer Relationship Management 客戶關係管理)
Java技術體系的4個平臺
- Java Card:Java小程序(Applets)運行於小內存設備上的平臺
- Java ME(Micro Edition):支持Java程序運行在移動終端上的平臺
- Java SE(Standard Edition):支持面向桌面級應用(如Window下的應用程序)
- Java EE(Enterprice Edition):支持使用多層架構應用的企業應用(ERP、CRM)
虛擬機分類
Classic VM:內置JIT(Just In Time)編譯器,“世界上第一款商用的Java虛擬機”,使用純解釋器方式執行Java代碼。由於解釋器不能和編譯器配合工作。JDK1.4時不使用了,與Exact VM進入 Sun Labs Research VM之中。
Exact VM:Solaris平臺出現,解釋器和編譯器混合工作模式,準確式內存管理
HotSpot VM:內置JIT(Just In Time)編譯器,熱點代碼探測技術。通過計數器找到最具編譯價值的代碼,通知JIT編譯器以方法爲單位進行編譯。
JRockit:BEA公司
KVM(Kilobyte):強調簡單、輕量、高度可移植,在Andriod、ios等操作系統出現前被廣泛應用
J9:IBM
Azul VM:特定硬件平臺專有的虛擬機,在HotSpot
“元循環”:Meta-Circular,使用語言自身實現其運行環境。
通過TCK(Technology Compatibility Kit)的兼容性測試
HotSpot VM
如果一個方法被頻繁調用或方法中有效循環次數多,分別觸發標準編譯和OSR(棧上替換)編譯動作,可在最優化的程序響應時間與最佳執行性能取得平衡
動態語言支持(通過內置Mozilla JavaScript Rhono引擎實現)、提供編譯API和微型HTTP服務器API等。JVM改進 鎖與同步、·垃圾手機、 類加載等
JDK1.7計劃改進:Lambda項目(Lambda表達式、函數式編程)、Jigsaw項目(虛擬機模塊化支持)、動態語言支持、GarbageFirst收集器和Coin項目(語言細節進化)
JDK1.7實際改進:新的G1收集器、加強對非Java語言的調用支持、升級 類加載架構等
模塊化、混合編程
開放服務網關協議OSGi技術、Clojure、JRuby、Groovy運行在JVM上
多核並行
Fork/Join模式是處理並行編程的一個經典方法。
OpenJDK的子項目Sumatra提供使用GPU(Graphics Processing Units圖形處理器)和APU(Accelerated Processing Units)運算能力的工具
進一豐富語法
Java5添加:自動裝箱、泛型、動態註解、枚舉、可變長參數、遍歷循環
Java7-8:在Coin項目中二進制的原生支持、在switch語句中支持字符串、“<>”操作符、異常處理的改進、簡化變長參數方法調用、面向資源的try-catch-finally語句等
64位虛擬機
內存問題:指針膨脹和各種數據類型對齊補白的原因,比32位系統額外增加10%~30%的內存消耗。
JDK底層方法都是本地化(Native)
獲取JDK源碼
OpenJDK是Sun把Java開源而形成的項目
自動內存管理機制
Java和C++之間差異:內存動態分配和垃圾收集技術
Java不需要爲每個new操作寫配對的delete/free代碼,不容易出現內存泄漏和內存溢出問題,由虛擬機管理內存
運行時數據區域
JVM在執行Java程序的過程將內存分成若干個不同的數據區域。隨虛擬機進程的啓動而存在,有些區域依賴用戶線程的啓動和結束建立和銷燬。
JVM管理的內存包括如下幾個運行時數據區域:
- 方法區:Method Area
- 虛擬機棧:VM Stack
- 本地方法棧:Native Method Stack
- 堆:Heap
- 程序計數器:Program Counter Engister
程序計數器
當前線程執行的字節碼的行號指示器,字節碼解釋器是通過改變計數器的值來選取下一條需要執行的字節碼指令,分支、循環、跳轉、異常處理
線程恢復的基礎功能依賴計數器實現。
JVM的多線程是通過線程輪流切換並分配處理器執行時間的方式實現的。一個處理器執行一條線程的指令。各個線程之間計數器互不影響。即線程私有。
計數器記錄正在執行的虛擬機字節碼指令的地址;如果在正在執行的Native方法,計數器值爲空(Undefined)。唯一在JVM規範中沒規定任何OutOfMemoryError情況的區域。
Java虛擬機棧(Java Virtual Machine Stacks“棧”)
虛擬機棧描述的是Java方法執行的內存模型:每個方法在執行的同時都會創建一個棧幀(Stack Frame)用於存儲局部變量表、操作數棧、動態連接、方法出口等信息。
虛擬機棧中局部變量表存放:基礎數據類型(boolean、byte、int、double、char、short、long、float)、對象引用(reference類型,不等同於對象本身,可能是指向對象起始地址的引用指針或指向一個代表對象的句柄或其他與對象相關的位置)、returnAddress類型(指向一條字節碼指令的地址)
64位長度的long和double類型的數據佔用2個局部變量空間(Slot),其餘數據類型佔用1個。局部變量表所需的內存空間在編譯期間分配,運行期間不改變局部變量表的大小。
兩種異常情況:1.線程請求的棧深度大於虛擬機所允許的深度,拋出StackOverflowError異常。2.虛擬機戰可以動態空戰(也允許固定長度的虛擬機棧)如果擴展是無法申請到足夠的內存,拋出OutOfMemoryError異常
本地方法棧(Native Method Stack)
虛擬機棧和本地方法棧區別:虛擬機棧爲虛擬機執行Java方法(即字節碼)服務,本地方法棧爲虛擬機使用的Native方法服務
本地方法棧區域也會拋出StackOverflowError和OutOfMemoryError異常。
Java堆(Heap)
JVM所管理的內存中最大一塊。Java堆被所有線程共享的一塊內存區域,虛擬機啓動時創建。
用來存放對象實例,幾乎所有的對象實例在這分配內存。
Java堆是垃圾收集器管理的主要區域,有時叫“GC堆”(Garbage Collected Heap)
採用分代收集算法,Java堆分爲:新生代和老年代。(Eden、From Survivor、To survivor)
方法區(Method Area)
各個線程共享的內存區域,用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等。別名:Non-Heap(非堆)
“永久代”(Permanent Generation),使用永久代實現方法區,Hotspot的垃圾收集器可以像管理Java堆一樣管理這部分內存,但是容易遇到內存溢出問題。和Java堆一樣不需要連續的內存和可選擇固定大小或可擴展、也可不實現垃圾收集。
並非數據進入方法區就能“永久”存在,這區域的內存回收目標主要是針對常量池的回收和對類型的卸載
運行時常量池(Runtime Constant Pool)
方法區的一部分,存放編譯期生成的各種字面量和符號引用,在類加載後加入方法區的運行時常量池。
運行時常量池相對於Class文件常量池的特徵:動態性。運行期也可將常量放入池中,如String類的intern()方法
直接內存(Direct Memory)
JDK1.4新加入NOI(New Input/Output)類,引入一個基於通道(Channel)與緩衝區(Buffer)的I/O方式,可以使用Native函數庫直接分配堆外內存,通過存儲在Java堆中的DirectByteBuffer對象堆這塊內存的引用進行操作。避免Java堆和Native堆來回複製數據。
受本機總內存(RAM和SWAP區或分頁文件)大小和處理器尋址空間的限制。
Java堆中對象的創建
創建對象(例如克隆、反序列化)通常僅僅是一個new關鍵字,在虛擬機中,對象(Java對象,不包括數組和Class對象)的創建過程:
JVM遇到一條new指令
1.檢查這個指令的參數是否能在常量池中定位到一個類的符號引用,檢查符號引用代表的類是否已被加載、解析和初始化。
2.類加載檢查通過後,虛擬機爲新生成的對象分配內存。對象所需內存的大小在類加載完成後可完全確定。
根據Java堆是否規整決定使用下面哪種分配方式:
“指針碰撞”(Bump the Pointer)就是將用過的內存放一邊,空閒的內存放一邊,中間指針作爲分界點的指示器,分配內存就是指針向空閒空間挪動一段與對象大小相等的距離。
“空閒列表”是虛擬機必須維護一個列表,記錄哪些內存塊可用。
使用Serial、ParNew等帶Compact過程的收集器是,系統採用指針碰撞;使用CMS這種基於Mark-Sweep算法的收集器時,採用空閒列表。
對象創建都在JVM是頻繁的行爲沒在併發情況下並不是線程安全,解決方案如下:
1.採用CAS配上失敗重試的方式保證更新操作的原子性
2.“本地線程分配緩衝”(Thread Local Allocation Buffer):按照線程劃分不同的空間之中進行,每個線程在Java堆中預先分配一小塊內存。哪個線程需要分配就在哪個TLAB上分配,只有TLAB用完了再分配新的TLAB,才需同步鎖定。
內存分配完成後,虛擬機需要分配到的內存空間初始化爲零值(不包括對象頭Object Header)。
JVM對對象進行必要的設置,例如這個對象是哪個類的實例、如何才能找到類的元數據信息、對象的哈希碼、對象的GC分代年齡等信息。
對象的內存佈局
在HotSpot虛擬機中,對象在內存中存儲的佈局分3塊區域:對象頭(Header)、實例數據(Instance Data)和對齊填充(Padding)
對象頭包括兩部分信息:1.用於存儲對象自身的運行時數據,如哈希碼(HashCode)、GC分代年齡、鎖狀態標誌、線程鎖持有的鎖、偏向線程ID、偏向時間戳等。Mark Word分爲32bit和64bit,設計成一個非固定的數據結構一遍在極小的空間存儲儘量多的信息。
2.類型指針,即對象指向它的類元數據的指針,。JVM通過這個指針確定這個對象是哪個類的實例。如果對象是一個Java數組,對象頭中必須有一塊用於記錄數組長度的數據。
HotSpot虛擬機默認的分配策略爲longs/doubles,ints,shorts/chars,bytes/booleans,oops(Ordinary Object Pointers)
對齊填充起着佔位符的作用,對象的大小必須是8字節的整數倍。當對象實例數據部分沒有對齊時,需要通過對齊填充來補全。
對象的訪問定位
建立對象是爲了使用對象,通過棧上reference數據來操作堆上的具體對象。訪問方式分爲使用句柄和直接指針
- 句柄訪問:Java堆劃分出一塊內存來作爲句柄池。reference中存儲的是對象的句柄地址。句柄中包含了對象實例數據與類型數據各自的具體地址信息。
- 直接指針訪問,reference中儲存的是對象地址。
句柄訪問(常見)和直接指針訪問對象的區別:1.使用句柄來訪問最大好處是reference中存儲的是穩定的句柄地址,在對象被移動時只會改變句柄中的實例數據指針,而reference本身不需要改變。2.使用直接指針訪問的好處是速度更快。
Java堆溢出
Java堆用於存儲對象實例,只要不斷創建對象,並保證GC Roots到對象之間有可達路徑來避免垃圾回收機制清除對象。對象數量到達最大堆的容量限制後就會產生內存溢出異常。
解決方法:先通過內存映像分析工具(如Eclipse Memory Analyzer)對Dump出來的堆轉儲快照進行分析。重點是確認內存中的對象是否是必要的,弄清是內存泄漏(Memory Leak)還是內存溢出(Memory Overflow)。
如果是內存泄漏,可進一步通過工具查看泄漏對象到GC Roots的引用鏈。找到泄漏對象是通過怎樣的路徑與GC Roots相關聯並導致垃圾收集器無法自動回收。
如果不存在泄漏,即內存中的對象存活着,應當檢查虛擬機的堆參數(-Xmx與-Xms),與機器物理內存對比看是否還可以調大,從代碼上檢查是否存在某些對象生命週期過長、持有狀態時間過長的情況,減少程序運行期的內存銷耗。
虛擬機棧和本地方法棧溢出
-Xoss參數(設置本地方法棧大小)存在、實際無效,棧容量只由-Xss參數設定。Java虛擬機規範描述異常:
- 如果線程請求的棧深度大於虛擬機所允許的最大深度,拋出StackOverflowError異常
- 如果虛擬機在擴展棧是無法申請到足夠的內存空間,OutOfMemoryError異常
使用-Xss參數減少棧內存容量。結果:拋出StackOverflowError異常,異常出現是輸出堆棧升讀相應縮小。
定義大量的本地變量,增大其方法幀中本地變量表的長度
方法區和運行時常量池溢出
運行時常量池是方法區的一部分,兩個區域的溢出測試一起進行。
String.intern()是Native方法,作用是字符串常量池已經包含一個等於此String對象的字符串,返回代表池中這個字符串的String對象;否則將此String對象包含的字符串添加到常量池中,返回此String對象的引用。通過-XX:PermSize和-XX:MaxPermSize限制方法區大小
本機直接內存溢出
DirectMemory容量可通過-XX:MaxDirectMemorySIze指定。如果不指定,默認與Java堆最大值(-Xmx指定)一樣。申請分配內存的方法是unsafe.allocateMemory()
第三章 垃圾收集器與內存分配策略
判斷對象是否存活
- 引用計數算法(Reference Counting):給對象添加一個引用計數器,每當有一個地方使用它是,計數器值就加一;當引用失效時,計數器減一。JVM沒有使用是因爲很難解決對象之間相互循環引用的問題。
- 可達性分析算法(Reachability Analysis):通過一系列“GC Roots”的對象作爲起始點,從這些節點開始向下搜索,搜索所走過的路徑稱爲引用鏈(Reference Chain)。當一個對象到GC Roots沒有任何引用鏈相連,此對象就是不可用。作爲GC Roots的對象包括虛擬機棧中引用的對象、方法區中類靜態屬性引用的對象、方法區中常量引用的對象、本地方法棧中JNI(即Native方法)引用的對象。
引用
強引用(Strong Reference)、弱引用(Weak Reference)、軟引用(Soft Reference)、虛引用(Phantom Reference)
- 強引用(Strong Reference):類似“Object obj = new Object()”
- 軟引用:用來描述一些還有用但並非必須的對象,在系統將要發生內存溢出前將這些對象進行第二次回收。SoftReference類實現
- 弱引用:描述非必需對象的,強度比軟引用更弱。弱引用關聯的對象只能生存在下一次垃圾收集發生之前,利用WeakReference類實現。
- 虛引用::一個對象是否有虛引用的存在,完全不會對其生存時間構成影響,無法通過虛引用來取得一個對象實例。
生存還是死亡
經歷2次標記過程後背回收:如果對象在進行可達性分析後發現沒有與GC Roots相連接的引用鏈,將被第一次標記並進行篩選,篩選的條件是此對象是否有必要執行finalize()方法。當對象沒有覆蓋finalize()方法,或者finalize()方法已經被虛擬機調用過,虛擬機將這兩種情況視爲“沒有必要執行”,防止到F-Queue的隊列中。在F-Queue隊列中進行第二次標記。
任何一個對象的finalize()方法只會被系統自動調用一次,如果對象面臨下一次回收,它的finalize()方法不會被再次執行。
回收方法區
永久代的垃圾收集主要回收兩部分內容:廢棄常量+無用的類。回收廢棄常量與回收Java堆中的對象相似。
判斷一個常量是否是“無用的類”條件:1.該類所有的實例都已經被回收,Java堆中不存在該類的任何實例。2.加載該類的ClassLoader已經被回收3.該類對應的Java.lang.Class對象沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法。
使用反射、動態代理、CGlib等ByteCode框架、動態申城JSP以及OSGi這類頻繁自定義ClassLoader的場景都需要虛擬機具備卸載的功能,以保證永久代不會溢出。
標記-清除算法(Mark-Sweep)
最基礎的收集算法。
1.標記出所有需要回收的對象,在標記完成後統一回收所有被標記的對象,
不足有兩個:一個是效率問題,標記和清除兩個過程的效率都不高,
另一個是空間問題,標記清除之後會產生大量不連續的內存碎片,空間碎片太多可能會導致不發找到足夠的連續內存而不得不提前觸發另一次垃圾收集動作。
複製算法
將可用內存按容量劃分爲大小相等的兩塊,每次只使用其中一塊,當這塊內存用完就將還存活的對象複製到另一塊上。再把已使用的內存空間清理掉。只要移動堆頂指針,按順序分配內存即可。
HotSpot虛擬機將Eden和Survivor中還存活着的對象一次性複製到另一塊Survivor空間。Eden和Survivor的大小比例是8:1,當Survivor空間不夠,需要依賴其他內存(如老年代)進行分配擔保。
標記-整理算法(Mark-Compact)
與“標記-清除”算法類似,但是不直接堆可回收對象進行清理,而是所有存活的對象都想一端移動,清除端邊界以外的內存。
分代收集算法(Generation Collection)
根據對象存活週期的不同將內存分成幾塊,根據各個年代的特點採用最適當的收集算法。新生代採用複製算法,老年代採用“標記-清理”或“標記-整理”算法進行回收。
HotSpot的算法實現
枚舉根節點:從可達性分析中從GC Roots節點找引用鏈這個操作爲例,作爲GC Roos的節點主要在全局性的引用與執行上下文中。
安全點
HotSpot沒有爲每條指令生成OopMap。在特定的位置記錄某些信息,這些位置稱爲安全點。
只有到達安全點才能暫停。選定標準:是否具有讓程序長時間執行的特徵,例如方法調用、循環跳出、異常跳轉才產生Safepoint
另一個問題:如何在GC發生時讓所有線程都到達最近安全點上再停頓下來。兩種方案:
- 搶先式中斷(Preemptive Suspension)不需要線程的代碼主動去配合。在GC發生時,所有線程全部中斷,如果的、發現線程中斷的地方不在安全點上就恢復線程,讓它跑到安全點上。比較少用。
- 主動式中斷(Voluntary Suspension):當GC需要中斷線程時,不直接對線程操作,只是設置一個標籤。各個線程執行時主動輪詢這個標誌發現爲真就中斷掛起。
安全區域(Safe Region)
程序不執行時(沒有分配CPU)或線程處於Sleep狀態或Blocked狀態,線程不法響應JVM的中斷請求。利用安全區域解決。
安全區域是指一段代碼片段,引用關係不會發生變化。在這個區域內任何地方開始GC都是安全的
1.首先標識自己已經進入Safe Region,在這段時間裏JVM發起GC時,不用管標識自己爲Safe Region狀態的線程。線程離開Safe Region時,檢查系統是否已經完成根節點枚舉,如果完成線程繼續執行,否則必須等待直到收到可以安全離開Safe Region的信號爲止。
3.5 垃圾收集器
Serial收集器:單線程的收集器,只會使用一個CPU或一條收集線程去完成垃圾收集工作,它進行垃圾收集時,必須暫停其他工作線程。
Concurrent Mark Sweep(CMS)收集器:併發收集器,第一次實現讓垃圾收集線程與用戶線程同時工作。是一種以獲取最短回收停頓時間爲目標的收集器。應用場景:Java應用集中在互聯網或B/S系統的服務端上,重視服務的響應速度。運作過程如下:
- 初始標記(CMS initial mark):stop the world,標記GC Roots能直接關聯到的對象,速度很快
- 併發標記(CMS concurrent mark):進行GC Roots Tracing的過程
- 重新標記(CMS remark):stop the world,修正併發標記期間因用戶程序繼續運作而導致標記產生變動的那一部分對象的標記記錄。停頓時間比初始標記長, 比並發標記時間短
- 併發清除(CMS concurrent sweep)
缺點:對CPU資源敏感,無法處理浮動垃圾(Floating Garbage),空間碎片多(解決方法設置+UseCMSCompactAtFullCollection開關參數)
浮動垃圾:由於CMS併發清理階段用戶線程還在運行這,伴隨程序運行不斷產生新垃圾,這部分垃圾出現在標記過程之後,CMS無法在當次收集中處理掉他們,只能等待下次GC時再清理。
ParNew收集器:Serial收集器的多線程版本,所有控制參數、收集算法、Stop The World、對象分配規則、回收策略等於Serial收集器完全一樣。使用-XX:+UserConcMarkSweepGC選項後的默認新生代收集器,也可使用-XX:+UserParNewGC選項來強制指定
Parallel Scavenger收集器:新生代收集器,使用複製算法的收集器,並行的多線程收集器。達到一個可控制的吞吐量(Throughput)
吞吐量:CPU用於運行用戶代碼的時間與CPU總消耗時間的比值。吞吐量=運行用戶代碼時間/(運行用戶代碼時間+垃圾收集時間)
Parallel Scavenger收集器無法與CMS收集器配合工作。
控制最大垃圾收集停頓時間的-XX:MaxGCPauseMillis參數(大於0的毫秒數)設置吞吐量大小的-XX:GCTimeRatio參數(大於0小於100)
GC自適應的調節策略:-XX:+UserAdaptiveSizePolicy根據當前系統的運行情況收集性能監控信息,動態調整參數提供最適合的停頓時間和最大吞吐量
Serial Old收集器:Serial收集器的老年代,單線程收集器+“標記-整理”算法。應用場景:在於給Client模式下的虛擬機使用。與Parallel Scavenger收集器搭配
Parallel Old收集器:Parallel Scavenger收集器的老年代,使用多線程和“標記-整理”算法。應用場景:使用Parallel Scavenger收集器+Parallel Old收集器,注重吞吐量以及CPU資源敏感的場合
Garbage First(G1)收集器:面向服務端應用的垃圾收集器,特點如下:
- 並行與併發
- 分代收集
- 空間整合,基於“標記-整理”算法,從局部(兩個Region之間)上看來是基於“複製”算法
- 可預測的停頓
將Java堆分成多個大小相等的獨立區域(Region),新生代和老年代不再物理隔離,而都是一部分Region(不需要連續)的集合。優先回收價值最大的Region。
虛擬機使用Remembered Set避免全堆掃描。每個Region都有一個與之對應的Remembered Set。虛擬機發現程序對Reference類型的數據進行寫操作時,產生一個Write Barrier暫時中斷寫操作。檢查Reference引用的對象是否處於不同Region之中
如果不計算維護Remembered Set操作,G1收集器的運作大致可劃分爲如下步驟:
- 初始標記(initial marking)
- 併發標記(concurrent mark):對堆中對象進行可達性分析
- 最終標記(Final marking)
- 篩選回收(Live Data Counting and Evacuation)對各個Region的回收價值和成本進行排序,根據用戶所期望的GC停頓時間制定回收計劃
Full GC 觸發條件
老年代空間不足
永久代空間滿OOM:PermGen Space(Java8 之前)
CMS GC時出現Promotion Faield和Concurrent Model Failure
統計到升到老年代的對象的大小大大於老年代剩餘的空間大小
並行與併發的區別
並行(Parallel):指多條垃圾收集線程並行工作,但此時用戶線程仍處於等待狀態
併發(Concurrent):指用戶線程與垃圾收集線程同時執行(不一定並行,可能交替執行),用戶程序在繼續運行,垃圾收集程序運行在另一個CPU上
GC日誌
閱讀GC日誌是處理JVM內存問題的基礎技能
垃圾收集相關參數
內存分配與回收策略
自動內存管理解決的問題:給對象分配內存以及回收分給對象的內存
- 對象的內存分配:堆上分配,對象主要分配在新生代的Eden區上。如果啓動本地線程分配緩衝,將按線程優先在TLAB上分配。少數分配在老年代中。取決於當前使用的是哪一種垃圾收集器組合,還有虛擬機中與內存相關的參數的設置
對象優先在Eden分配
當Eden區沒有足夠空間分配時,虛擬機發起Minor GC。
大對象直接進入老年代
大對象指需要大量連續內存空間的Java對象,如很長的字符串以及數組,byte[]數組。虛擬機提供一個-XX:PretenureizeThreshold參數,令大於這個設置值的對象直接在老年代分配。
長期存回的對象將進入老年代
虛擬機給那個對象定義了一個對象年齡(Age)計數器。
如果對象在Eden出生並經過第一次Minor GC 後仍然存活,並能被Survivor容納的話,移動到Survivor空間,對象年齡設爲1.
對象在Survivor區中在經過一次Minor GC,年齡增加1歲
當年齡增加到一定程度(默認15歲)晉升到老年代。通過-XX:MaxTenuringThreshold設置
-Xms20M -Xmx20M -Xmn10M
動態對象年齡判定
如果在Survivor空間中相同年齡所有對象大小的總和大於Survivor空間的一半,年齡大於或等於該年齡的對象可以進入老年代,無需等到MaxTenuringThreshold中要求的年齡
空間分配擔保
Minor GC確保安全條件:老年代最大可用連續空間大於新生代所有對象總空間
新生代使用複製收集算法,只使用其中一個Survivor空間作爲輪換備份,當出現大量對象在Minor GC後仍然存活,就需要老年代進行分配擔保。
擔保失敗後(HandlePromotionFailure)重新發起一次Full GC。
虛擬機提供多種不同的收集器以及大量的調節參數。
目標:瞭解每個具體收集器的行爲、優勢、劣勢、調節參數。