Java虛擬機區(qū)域
運行的時候講內(nèi)存分為多個不同的數(shù)據(jù)區(qū)域

程序計數(shù)器
比較小的內(nèi)存空間,可以看成當(dāng)前程序所執(zhí)行的字節(jié)碼的行號指示器。
虛擬機棧
描述了Java方法執(zhí)行的內(nèi)存模型,每個方法執(zhí)行的時候會創(chuàng)建棧幀,存儲局部變量表、操作數(shù)、動態(tài)連接、返回地址等。調(diào)用方法的過程是一個進棧和出棧的過程。
異常說明:線程請求棧深度大于虛擬機所允許的情況下,拋出StackOverflowErro異常;當(dāng)??梢詣討B(tài)擴展的時候,也可能拋出OutOfMemoryError
本地方法棧
為虛擬機使用到的Native方法提供服務(wù)
堆
虛擬機所管理的內(nèi)存中最大的一塊。被所有的線程所共享,虛擬機啟動的時候創(chuàng)建。用于存放對象的實例。也是垃圾收集器的主要工作區(qū)域。堆中是分代的:新生代,老年代。
主流虛擬機都可以動態(tài)擴展空間,可以通過(-Xmx -Xms)參數(shù)控制。
方法區(qū)
各個線程共享的內(nèi)存區(qū)域,存儲被虛擬機加載的類信息、常量、靜態(tài)變量、即時編譯后的代碼等數(shù)據(jù)。
運行時常量池是方法區(qū)的一部分。用于存放編譯期生成的各種字面量和符號引用。
注意:運行期也可以將新的常量放入常量池中
虛擬機中的對象
對象的創(chuàng)建
當(dāng)遇到一個new指令的時候,虛擬機是如何工作的那
- 常量池中檢查是否存在此類的符號引用,并檢查這個類是否被加載、解析、初始化過
- 如果沒有:進行類的加載過程
- 類加載檢查完成之后,虛擬機將為新對象分配內(nèi)存。
說明:內(nèi)存分配存在指針碰撞和空閑列表兩種方法。虛擬機采用CAS和失敗重試保證內(nèi)存分配操作的原子性。還可以使用本地線程緩沖的方法(-XX:+/-UseTLAB) - 分配好的對象內(nèi)存空間初始化為0
- 對對象進行必要設(shè)置,設(shè)置在對象頭
- 執(zhí)行初始化函數(shù),完成對象初始化操作
對象在內(nèi)存中的布局
內(nèi)存中的對象有三個區(qū)域:對象頭;實例數(shù)據(jù);對齊填充
- 對象頭:包括對象自身的運行時數(shù)據(jù),類型指針(指向它的類元數(shù)據(jù))
- 實例數(shù)據(jù):代碼中定義的各種類型的字段內(nèi)容
- 對齊填充:并非必須,占位。整個對象必須滿足8字節(jié)的整數(shù)倍
對象的訪問定位
程序會通過棧上的引用訪問堆中的對象,如何通過引用去定位對象規(guī)范沒有具體定義。
- 句柄:在堆中分配句柄池,reference中存儲的是句柄的地址,句柄中包含具體類的信息
- 直接地址訪問:reference中存儲的是直接的對象地址
虛擬機的溢出
堆溢出
不斷創(chuàng)建對象,并且保證對象不被回收,產(chǎn)生堆溢出。通過VM參數(shù)限制堆不能自動擴展(Xms與Xmx相同)
import java.util.ArrayList;
import java.util.List;
/**
* 產(chǎn)生堆內(nèi)存不足異常
* VM Args : -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
*
* @author linxm
*
*/
public class HeapOOM {
static class OOMObject{
}
public static void main(String[] args) {
List<OOMObject> list = new ArrayList<OOMObject>();
for(;;){
list.add(new OOMObject());
}
}
}
棧溢出
/**
* 產(chǎn)生棧溢出
* VM Args:-Xss128k
*
* @author linxm
*
*/
public class JavaVMStackOF {
private static int stackLength = 1;
public void stackLeak(){
stackLength++;
stackLeak();
}
public static void main(String[] args) throws Throwable{
JavaVMStackOF oom = new JavaVMStackOF();
try{
oom.stackLeak();
}catch(Throwable e){
System.out.println("堆棧深度:" + stackLength);
throw e;
}
}
}
方法區(qū)溢出
通過Spring動態(tài)產(chǎn)生大量的類
import java.lang.reflect.Method;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
/**
* Java方法區(qū)產(chǎn)生溢出
* VM Args: -XX:PermSize=2M -XX:MaxPermSize=2M
*
* @author linxm
*
*/
public class JavaMethodAreaOOM {
public static void main(String[] args) {
while(true){
Enhancer enchancer = new Enhancer();
enchancer.setSuperclass(OOMObject.class);
enchancer.setUseCache(false);
enchancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object arg0, Method arg1, Object[] arg2,
MethodProxy arg3) throws Throwable {
return arg3.invokeSuper(arg0, arg2);
}
});
}
}
class OOMObject{
}
}
本機直接溢出
直接內(nèi)存可以通過 -XX: MaxDirectMemorySize指定,如不指定則默認(rèn)與堆最大值(Xmx)大小相同。在使用NIO的時候,有可能會有這種溢出
import java.lang.reflect.Field;
/**
* 直接內(nèi)存溢出
* VM Args:-Xmx20M -XX:MaxDirectMemorySize=10M
*
* @author linxm
*
*/
public class DirectMemoryOOM {
private static final int _1Mb = 1024 * 1024;
@SuppressWarnings("restriction")
public static void main(String[] args) throws IllegalArgumentException, IllegalAccessException {
Field unsafeField = sun.misc.Unsafe.class.getDeclaredFields()[0];
unsafeField.setAccessible(true);
sun.misc.Unsafe unsafe = (sun.misc.Unsafe)unsafeField.get(null);
while(true){
unsafe.allocateMemory(_1Mb);
}
}
}
垃圾回收
需要思考3件事情
- 那些內(nèi)存需要回收
- 什么時候回收
- 怎么回收
虛擬機中程序計數(shù)器,本地方法區(qū),虛擬機棧隨著線程而消亡;棧中的棧幀隨著方法調(diào)入和調(diào)出而產(chǎn)生和消亡。
垃圾回收主要考慮的是堆和方法區(qū)
什么需要回收
堆中的對象實例是主要回收的內(nèi)容,需要判斷是否不再被使用。主流的虛擬機都是通過可達性算法來實現(xiàn)。
通過GC Root對象為起點,從這些節(jié)點開始搜索,走過的路徑就是引用鏈,如果一個對象沒有任何引用鏈可以連接到GC Root則判斷為需要回收
GC Root包括如下幾種:
- 虛擬機棧中引用的對象
- 方法區(qū)中靜態(tài)屬性引用的對象
- 方法區(qū)中常量引用的對象
- 本地方法棧中引用的對象
以上是判斷什么需要回收的條件,所有的描述都是圍繞著“引用”來討論的。
關(guān)于引用又有4中強度的區(qū)分:
- 強引用:只要存在就不會被回收
Object.obj = new Object();
- 軟引用:還有用但并非必須的對象,系統(tǒng)會在要發(fā)生內(nèi)存溢出之前對這些對象進行二次回收。SoftReference來實現(xiàn)
- 弱引用:有用但并非必須,對象只能生存到下一次垃圾回收之前。WeakReference來實現(xiàn)
- 虛引用:最弱的一種引用關(guān)系,無法通過虛引用獲取對象實例。只是在回收的時候收到一個系統(tǒng)通知。PhantomReference來實現(xiàn)
import java.lang.ref.SoftReference;
/**
* 演示軟引用的使用方法
*
* @author Administrator
*
*/
public class fReferenceType {
public static void main(String[] args) {
// 這是一個強引用
Object o = new Object();
System.out.println(o.hashCode());
// o會在適當(dāng)時機被回收
o = null;
Object objRef = new Object();
System.out.println(objRef.hashCode());
// 軟引用
SoftReference<Object> aSoftRef = new SoftReference<Object>(objRef);
// SoftReference依然保持有objRef的引用,不會馬上回收,但是在OutOfMemoryError之前回收對象
objRef = null;
// 回收前可以依然獲取對象
objRef = aSoftRef.get();
System.out.println(objRef.hashCode());
}
}
什么時候回收
對象不可達表示可以回收,但是不是馬上就回收,只是“死緩”而已。真正到達死亡的過程,需要兩次標(biāo)注:
發(fā)現(xiàn)對象不可達,進行一次標(biāo)注并且進行一次篩選:看對象是否覆蓋了finalize(),如果覆蓋了并且虛擬機沒有執(zhí)行過finalize方法,則放入F-Queue隊列中,表示等待銷毀。finalize方法中可以拯救對象,只要這個時候與GC Root產(chǎn)生引用鏈,依然可以擺脫死亡。
/**
* 拯救對象的方法
* 1. 對象在GC時候自救
* 2. 對象自救只有一次機會,因為一個對象的finalize()方法只執(zhí)行一次
*
* @author linxm
*
*/
public class FinalizeEspaceGC {
public static FinalizeEspaceGC SAVE_HOOK = null;
public void isAlive(){
System.out.println("我還活著!");
}
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("finalize方法被執(zhí)行了!");
SAVE_HOOK = this;
}
public static void main(String[] args) throws InterruptedException {
SAVE_HOOK = new FinalizeEspaceGC();
SAVE_HOOK = null;
// 第一次拯救自己
System.gc();
// 因為finalize方法優(yōu)先級比較低,暫停一會
Thread.sleep(1000);
if(SAVE_HOOK == null){
System.out.println("拯救自己失敗");
}else{
SAVE_HOOK.isAlive();
}
SAVE_HOOK = null;
// 第二次拯救自己
System.gc();
// 因為finalize方法優(yōu)先級比較低,暫停一會
Thread.sleep(1000);
if(SAVE_HOOK == null){
System.out.println("拯救自己失敗");
}else{
SAVE_HOOK.isAlive();
}
}
}
任何對象的finalize系統(tǒng)只會執(zhí)行一次!
回收方法區(qū)
方法區(qū)也就是永久代也有可能進行垃圾回收。主要是回收廢棄的常量和無用的類,但是效率很差。
- 判斷是否為廢棄的常量比較簡單,只要沒有常量的引用即可
- 判斷是否為無用的類比較麻煩,至少需要滿足下面的條件
- 類的所有實例已被回收
- 加載該類的ClassLoader已經(jīng)被回收
- 該類對應(yīng)的java.lang.Class對象沒有任何地方被引用,無法通過反射構(gòu)造此類
是否進行類的回收通過參數(shù)可以設(shè)置
-Xnoclassc
在大量使用反射和自定義ClassLoader的時候,需要虛擬機具有自動裝卸類的功能
怎么回收垃圾
下面看一下垃圾回收的算法。
- 標(biāo)記-清除算法:效率不高,空間碎片
- 復(fù)制算法:使用一半的內(nèi)存,交替使用?,F(xiàn)代虛擬機不使用1:1的內(nèi)存方式。使用一塊大的Eden空間和兩個小的Survivor空間。HotSpot默認(rèn)的比例是8:1,當(dāng)Survivor內(nèi)存不夠的時候,就會使用老年代進行分配擔(dān)保。
- 標(biāo)記-整理算法:一般用于老年代
- 分代收集算法:java的堆分為新生代和老年代,采用不同的算法。新生代使用復(fù)制算法;老年代使用標(biāo)記清除或者標(biāo)記整理。
算法實現(xiàn)
枚舉GC Root的算法,如何盡可能不造成卡頓是一個難題。
我們使用OopMap的結(jié)構(gòu),在虛擬機中存儲引用關(guān)系,以供GC Root算法進行查看。不可能對每一個指令都生成OopMap(消耗太多的資源),這就有了安全點的概念,在安全點上生成OopMap,安全點選取的頻率是一個對性能影響比較大的因素。
程序運行到安全點停下來的方法:搶占式中斷,主動式中斷(設(shè)置中斷標(biāo)志,各個線程輪休這個中斷標(biāo)志)
在進行GC的時候
- JVM對所有線程發(fā)起中斷請求
- 如果程序的中斷點不在安全點上,則運行到安全點
- 對于一些處于Sleep或者Blocked狀態(tài)的線程,無法接收到JVM的中斷請求,這時候需要使用安全區(qū)域(在一段代碼中,引用關(guān)系不會發(fā)生變化)的概念來解決
垃圾回收器
有些垃圾回收器可以搭配使用,連線的表示可以配合使用

重要的概念:
- Minor GC
又稱新生代GC,指發(fā)生在新生代的垃圾收集動作;
因為Java對象大多是朝生夕滅,所以Minor GC非常頻繁,一般回收速度也比較快;
觸發(fā)條件:當(dāng)Eden區(qū)滿時,觸發(fā)Minor GC。 - Full GC
又稱Major GC或老年代GC,指發(fā)生在老年代的GC;
出現(xiàn)Full GC經(jīng)常會伴隨至少一次的Minor GC(不是絕對,Parallel Sacvenge收集器就可以選擇設(shè)置Major GC策略);
Major GC速度一般比Minor GC慢10倍以上;
Full GC觸發(fā)條件:
(1)調(diào)用System.gc時,系統(tǒng)建議執(zhí)行Full GC,但是不必然執(zhí)行
(2)老年代空間不足
(3)方法區(qū)空間不足
(4)通過Minor GC后進入老年代的平均大小大于老年代的可用內(nèi)存
(5)由Eden區(qū)、From Space區(qū)向To Space區(qū)復(fù)制時,對象大小大于To Space可用內(nèi)存,則把該對象轉(zhuǎn)存到老年代,且老年代的可用內(nèi)存小于該對象大小
垃圾回收器的具體說明
- Serial收集器:
單線程,會暫停所有的工作進行垃圾回收,默認(rèn)用于Java client中。設(shè)置參數(shù):-XX:+UseSerialGC - ParNew:
Serial的多線程版本。設(shè)置參數(shù)
-XX:+UseConcMarkSweepGC,指定使用CMS后,會默認(rèn)使用ParNew作為新生代收集器
-XX:+UseParNewGC,強制指定使用ParNew
-XX:ParallelGCThreads,指定垃圾收集的線程數(shù)量,ParNew默認(rèn)開啟的收集線程與CPU的數(shù)量相同 - Parallel Scavenge:
新生代的收集器,使用復(fù)制算法,并行的多線程收集器。主要的特點是控制吞吐量。
-XX:MaxGCPauseMillis,用于設(shè)置回收器的停頓時間;
-XX:GCTimeRatio,用于設(shè)置吞吐量的大小;
-XX:+UseAdaptiveSizePolicy,打開這個參數(shù)則系統(tǒng)會自動設(shè)置很多參數(shù),根據(jù)GC運行的情況進行調(diào)節(jié)(自省是這個收集器的主要特點) - Serial Old:
Serial收集器的老年代版本,可以作為CMS收集器的后備預(yù)案,在并發(fā)收集發(fā)生Concurrent Mode Failure時使用 - Parallel Old:
Parallel Scavenge收集器的老年代版本 - CMS
是一種以最小停頓為目標(biāo)的設(shè)計方案,是基于標(biāo)記-清除的算法
-XX:+UseConcMarkSweepGC,指定使用CMS收集器
-XX:+UseCMSCompactAtFullCollection,打開這個參數(shù)則在需要Full GC的時候使用內(nèi)存碎片的合并整理過程
-XX:+CMSFullGCsBeforeCompaction,設(shè)置執(zhí)行多少次不壓縮的Full GC后,來一次壓縮整理,為減少合并整理過程的停頓時間,默認(rèn)值為0 - G1
面向服務(wù)端應(yīng)用的收集器,并行與并發(fā);分代收集;空間整合;可預(yù)期的停頓
-XX:+UseG1GC,指定使用G1收集器;
-XX:InitiatingHeapOccupancyPercent,當(dāng)整個Java堆的占用率達到參數(shù)值時,開始并發(fā)標(biāo)記階段;默認(rèn)為45;
-XX:MaxGCPauseMillis,為G1設(shè)置暫停時間目標(biāo),默認(rèn)值為200毫秒;
-XX:G1HeapRegionSize,設(shè)置每個Region大小,范圍1MB到32MB;目標(biāo)是在最小Java堆時可以擁有約2048個Region;