JVM原理

數據類型

Java虛擬機中,數據類型可以分爲兩類:基本類型和引用類型。
基本類型的變量保存原始值,即:他代表的值就是數值本身;而引用類型的變量保存引用值。
“引用值”代表了某個對象的引用,而不是對象本身,對象本身存放在這個引用值所表示的地址的位置。
基本類型包括:byte,short,int,long,char,float,double,Boolean,returnAddress
引用類型包括:類類型,接口類型和數組。


堆與棧

棧是運行時的單位,而堆是存儲的單位。
棧解決程序的運行問題,即程序如何執行,或者說如何處理數據;堆解決的是數據存儲的問題,即數據怎麼
放、放在哪兒。
在Java中一個線程就會相應有一個線程棧與之對應,這點很容易理解,因爲不同的線程執行邏輯有所不同
因此需要一個獨立的線程棧。而堆則是所有線程共享的。棧因爲是運行單位,因此裏面存儲的信息都是跟當
線程(或程序)相關信息的。包括局部變量、程序運行狀態、方法返回值等等;而堆只負責存儲對象信息。
堆中存的是對象。棧中存的是基本數據類型和堆中對象的引用。


Java中的參數傳遞時傳值呢?還是傳引用?
要說明這個問題,先要明確兩點:
1. 不要試圖與C進行類比,Java中沒有指針的概念
2. 程序運行永遠都是在棧中進行的,因而參數傳遞時,只存在傳遞基本類型和對象引用的問題。不會直接
傳對象本身。
明確以上兩點後。Java在方法調用傳遞參數時,因爲沒有指針,所以它都是進行傳值調用(這點可以參考C的
傳值調用)。因此,很多書裏面都說Java是進行傳值調用,這點沒有問題,而且也簡化的C中複雜性。

堆和棧中,棧是程序運行最根本的東西。程序運行可以沒有堆,但是不能沒有棧。而堆是爲棧進行數據存儲
服務,說白了堆就是一塊共享的內存。不過,正是因爲堆和棧的分離的思想,才使得Java的垃圾回收成爲可
能。
Java中,棧的大小通過-Xss來設置,當棧中存儲數據比較多時,需要適當調大這個值,否則會出現
java.lang.StackOverflowError異常。常見的出現這個異常的是無法返回的遞歸,因爲此時棧中保存的信息都是
方法返回的記錄點。


Java對象的大小
在Java中,一個空Object對象的大小是8byte,這個大小隻是保存堆中一個沒有任何屬性的對象的大小。看
下面語句:

Object ob = new Object();

這樣在程序中完成了一個Java對象的生命,但是它所佔的空間爲:4byte+8byte。4byte是上面部分所說的
Java棧中保存引用的所需要的空間。而那8byte則是Java堆中對象的信息。因爲所有的Java非基本類型的對象都
需要默認繼承Object對象,因此不論什麼樣的Java對象,其大小都必須是大於8byte。
有了Object對象的大小,我們就可以計算其他對象的大小了。

Class NewObject {
int count;
boolean flag;
Object ob;
}

其大小爲:

空對象大小(8byte)+int大小(4byte)+Boolean大小(1byte)+空Object引用的大小(4byte)=17byte。

但是因爲Java在對對象內存分配時都是以8的整數倍來分,因此大於17byte的最接
近8的整數倍的是24,因此此對象的大小爲24byte。
這裏需要注意一下基本類型的包裝類型的大小。因爲這種包裝類型已經成爲對象了,因此需要把他們作爲對
象來看待。包裝類型的大小至少是12byte(聲明一個空Object至少需要的空間),而且12byte沒有包含任何有
效信息,同時,因爲Java對象大小是8的整數倍,因此一個基本類型包裝類的大小至少是16byte。這個內存佔
用是很恐怖的,它是使用基本類型的N倍(N>2),有些類型的內存佔用更是誇張(隨便想下就知道了)。

因此,可能的話應儘量少使用包裝類。在JDK5.0以後,因爲加入了自動類型裝換,因此,Java虛擬機會在存儲方
面進行相應的優化。


引用類型
對象引用類型分爲強引用、軟引用、弱引用和虛引用
強引用:就是我們一般聲明對象時虛擬機生成的引用,強引用環境下,垃圾回收時需要嚴格判斷當前對象是否
被強引用,如果被強引用,則不會被垃圾回收
軟引用:軟引用一般被做爲緩存來使用。與強引用的區別是,軟引用在垃圾回收時,虛擬機會根據當前系統的剩
餘內存來決定是否對軟引用進行回收。如果剩餘內存比較緊張,則虛擬機會回收軟引用所引用的空間;如果剩
餘內存相對富裕,則不會進行回收。換句話說,虛擬機在發生OutOfMemory時,肯定是沒有軟引用存在的。
弱引用:弱引用與軟引用類似,都是作爲緩存來使用。但與軟引用不同,**弱引用在進行垃圾回收時,是一定會被
回收掉的,因此其生命週期只存在於一個垃圾回收週期內**。
強引用不用說,我們系統一般在使用時都是用的強引用。而“軟引用”和“弱引用”比較少見。他們一般被
作爲緩存使用,而且一般是在內存大小比較受限的情況下做爲緩存。因爲如果內存足夠大的話,可以直接使用
強引用作爲緩存即可,同時可控性更高。因而,他們常見的是被使用在桌面應用系統的緩存。


JVM的生命週期

一、首先分析兩個概念
JVM實例和JVM執行引擎實例

  1. JVM實例對應一個獨立運行的java程序,它是進程級別。
  2. JVM執行引擎實例則對應了屬於用戶運行程序的線程,它是線程級別。

    二、JVM生命週期

    1. JVM實例的誕生
    當啓動一個java程序是,一個JVM實例就產生了,任何一個擁有public static void main(String[] args)函數的class都可以作爲JVM實例運行的起點。
    2. JVM實例的運行 main()作爲該程序初始線程的起點,任何其他線程均由該線程啓動。
    JVM內部有兩種線程:守護線程和非守護線程,main()屬於非守護線程,守護線程通常由JVM自己使用,java程序也可以標明自己創建的線程是守護線程。
    3.JVM實例的消亡
    當程序中的所有非守護線程都終止時,JVM才退出;若安全管理器允許,程序也可以使用Runtime類或者System.exit()來退出。


這裏寫圖片描述

三、JVM的內部體系結構分爲三部分

  1. 類裝載器(ClassLoader)子系統
    作用:用來裝載.class文件。
    2.執行引擎
    作用:執行字節碼或者執行本地方法。
    3.運行時數據區
    方法區、堆、java棧、PC寄存器、本地方法棧。

JVM類加載器
這裏寫圖片描述

(1)裝載
裝載過程負責找到二進制字節碼並加載至JVM中,JVM通過類名、類所在的包名通過ClassLoader來完成類的加載,同樣,也採用以上三個元素來標識一個被加載了的類:類名+包名+ClassLoader實例ID。
(2)鏈接
鏈接過程負責對二進制字節碼的格式進行校驗、初始化裝載類中的靜態變量以及解析類中調用的接口、類。
在完成了校驗後,JVM初始化類中的靜態變量,並將其值賦爲默認值。
最後一步爲對類中的所有屬性、方法進行驗證,以確保其需要調用的屬性、方法存在,以及具備應的權限(例如public、private域權限等),會造成NoSuchMethodError、NoSuchFieldError等錯誤信息。
(3)初始化
初始化過程即爲執行類中的靜態初始化代碼、構造器代碼以及靜態屬性的初始化,在四種情況下初始化過程會被觸發執行:
調用了new;反射調用了類中的方法;子類調用了初始化;JVM啓動過程中指定的初始化類

二、JVM兩種類裝載器包括:啓動類裝載器和用戶自定義類裝載器,
啓動類裝載器是JVM實現的一部分,用戶自定義類裝載器則是Java程序的一部分,必須是ClassLoader類的子類。

主要分爲以下幾類:
(1) Bootstrap ClassLoader
這是JVM的根ClassLoader,它是用C++實現的,JVM啓動時初始化此ClassLoader,並由此ClassLoader完成$JAVA_HOME中jre/lib/rt.jar(Sun JDK的實現)中所有class文件的加載,這個jar中包含了java規範定義的所有接口以及實現。
(2) Extension ClassLoader
JVM用此classloader來加載擴展功能的一些jar包
(3) System ClassLoader
JVM用此classloader來加載啓動參數中指定的Classpath中的jar包以及目錄,在Sun JDK中ClassLoader對應的類名爲AppClassLoader。
(4) User-Defined ClassLoader
User-DefinedClassLoader是Java開發人員繼承ClassLoader抽象類自行實現的ClassLoader,基於自定義的ClassLoader可用於加載非Classpath中的jar以及目錄。

三、ClassLoader抽象類提供了幾個關鍵的方法:
(1)loadClass
此方法負責加載指定名字的類,ClassLoader的實現方法爲先從已經加載的類中尋找,如沒有則繼續從parent ClassLoader中尋找,如仍然沒找到,則從System ClassLoader中尋找,最後再調用findClass方法來尋找,如要改變類的加載順序,則可覆蓋此方法
(2)findLoadedClass
此方法負責從當前ClassLoader實例對象的緩存中尋找已加載的類,調用的爲native的方法。
(3) findClass
此方法直接拋出ClassNotFoundException,因此需要通過覆蓋loadClass或此方法來以自定義的方式加載相應的類。
(4) findSystemClass
此方法負責從System ClassLoader中尋找類,如未找到,則繼續從Bootstrap ClassLoader中尋找,如仍然爲找到,則返回null。
(5)defineClass
此方法負責將二進制的字節碼轉換爲Class對象
(6) resolveClass
此方法負責完成Class對象的鏈接,如已鏈接過,則會直接返回。重點內容。


JVM執行引擎
一、JVM通過執行引擎來完成字節碼的執行,在執行過程中JVM採用的是自己的一套指令系統,
每個線程在創建後,都會產生一個程序計數器(pc)和棧(Stack),其中程序計數器中存放了下一條將要執行的指令,

Stack中存放Stack Frame,棧幀,表示的爲當前正在執行的方法,每個方法的執行都會產生Stack Frame,Stack Frame中存放了傳遞給方法的參數、方法內的局部變量以及操作數棧,

操作數棧用於存放指令運算的中間結果,指令負責從操作數棧中彈出參與運算的操作數,指令執行完畢後再將計算結果壓回到操作數棧,當方法執行完畢後則從Stack中彈出,繼續其他方法的執行。

在執行方法時JVM提供了invokestatic、invokevirtual、invokeinterface和invokespecial四種指令來執行

(1)invokestatic:調用類的static方法
(2) invokevirtual: 調用對象實例的方法
(3) invokeinterface:將屬性定義爲接口來進行調用
(4) invokespecial: JVM對於初始化對象(Java構造器的方法爲:)以及調用對象實例中的私有方法時。

二、反射機制是Java的亮點之一,基於反射可動態調用某對象實例中對應的方法、訪問查看對象的屬性等,
而無需在編寫代碼時就確定需要創建的對象,這使得Java可以實現很靈活的實現對象的調用,代碼示例如下:

Class actionClass=Class.forName(外部實現類);
Method method=actionClass.getMethod(“execute”,null);
Object action=actionClass.newInstance();
method.invoke(action,null);

反射的關鍵:要實現動態的調用,最明顯的方法就是動態的生成字節碼,加載到JVM中並執行。

(1)Class actionClass=Class.forName(外部實現類);
調用本地方法,使用調用者所在的ClassLoader來加載創建出Class對象;

(2)Method method=actionClass.getMethod(“execute”,null);
校驗此Class是否爲public類型的,以確定類的執行權限,如不是public類型的,則直接拋出
SecurityException;

調用privateGetDeclaredMethods來獲取到此Class中所有的方法,在privateGetDeclaredMethods對此Class中所有的方法的集合做了緩存,在第一次時會調用本地方法去獲取;

掃描方法集合列表中是否有相同方法名以及參數類型的方法,如有則複製生成一個新的Method對象返回;

如沒有則繼續掃描父類、父接口中是否有此方法,如仍然沒找到方法則拋出NoSuchMethodException;
(3) Object action=actionClass.newInstance();
第一步:校驗此Class是否爲public類型,如權限不足則直接拋出SecurityException;
第二步:如沒有緩存的構造器對象,則調用本地方法獲取到構造器,並複製生成一個新的構造器對象,放入緩存,如沒有空構造器則拋出InstantiationException;
第三步:校驗構造器對象的權限;
第四步:執行構造器對象的newInstance方法;構造器對象的newInstance方法判斷是否有緩存的ConstructorAccessor對象,如果沒有則調用sun.reflect.ReflectionFactory生成新的ConstructorAccessor對象;
第五步:sun.reflect.ReflectionFactory判斷是否需要調用本地代碼,可通過sun.reflect.noInflation=true來設置爲不調用本地代碼,在不調用本地代碼的情況下,就轉交給MethodAccessorGenerator來處理了;
第六步:MethodAccessorGenerator中的generate方法根據Java Class格式規範生成字節碼,字節碼中包括了ConstructorAccessor對象需要的newInstance方法,此newInstance方法對應的指令爲invokespecial,所需的參數則從外部壓入,生成的Constructor類的名字以:sun/reflect/GeneratedSerializationConstructorAccessor或sun/reflect/GeneratedConstructorAccessor開頭,後面跟隨一個累計創建的對象的次數;
第七步:在生成了字節碼後將其加載到當前的ClassLoader中,並實例化,完成ConstructorAccessor對象的創建過程,並將此對象放入構造器對象的緩存中;
最後一步:執行獲取的constructorAccessor.newInstance,這步和標準的方法調用沒有任何區別。

(4) method.invoke(action,null);

這步執行的過程和上一步基本類似,只是在生成字節碼時生成的方法改爲了invoke,其調用的目標改爲了傳入的對象的方法,同時生成的類名改爲了:sun/reflect/GeneratedMethodAccessor。

注:但是getMethod是非常耗性能的,一方面是權限的校驗,另外一方面所有方法的掃描以及Method對象的複製,因此在使用反射調用多的系統中應緩存getMethod返回的Method對象。

2、執行技術
主要的執行技術有:解釋,即時編譯,自適應優化、芯片級直接執行
(1)解釋屬於第一代JVM,
(2)即時編譯JIT屬於第二代JVM,
(3)自適應優化(目前Sun的HotspotJVM採用這種技術)則吸取第一代JVM和第二代JVM的經驗,採用兩者結合的方式
(4)自適應優化:開始對所有的代碼都採取解釋執行的方式,並監視代碼執行情況,然後對那些經常調用的方法啓動一個後臺線程,將其編譯爲本地代碼,並進行仔細優化。若方法不再頻繁使用,則取消編譯過的代碼,仍對其進行解釋執行。

JVM運行時數據區
一、JVM在運行時將數據劃分爲了6個區域來存儲,而不僅僅是大家熟知的Heap區域,這6個區域圖示如下:
第一塊: PC寄存器
PC寄存器是用於存儲每個線程下一步將執行的JVM指令,如該方法爲native的,則PC寄存器中不存儲任何信息。

第二塊:JVM棧
JVM棧是線程私有的,每個線程創建的同時都會創建JVM棧,JVM棧中存放的爲當前線程中局部基本類型的變量(java中定義的八種基本類型:boolean、char、byte、short、int、long、float、double)、部分的返回結果以及Stack Frame,非基本類型的對象在JVM棧上僅存放一個指向堆上的地址。

這裏寫圖片描述

第三塊:堆(Heap)
Heap是大家最爲熟悉的區域,它是JVM用來存儲對象實例以及數組值的區域,可以認爲Java中所有通過new創建的對象的內存都在此分配,Heap中的對象的內存需要等待GC進行回收。

這裏寫圖片描述
新生代舊生代
(1)New Generation
又稱爲新生代,程序中新建的對象都將分配到新生代中,新生代又由Eden Space和兩塊Survivor Space構成,可通過-Xmn參數來指定其大小
(2) Old Generation
又稱爲舊生代,用於存放程序中經過幾次垃圾回收還存活的對象,例如緩存的對象等,舊生代所佔用的內存大小即爲-Xmx指定的大小減去-Xmn指定的大小。

PermGen space

PermGen space的全稱是Permanent Generation space,是指內存的永久保存區域OutOfMemoryError: PermGen space從表面上看就是內存益出,解決方法也一定是加大內存。說說爲什麼會內存益出:這一部分用於存放Class和Meta的信息,Class在被 Load的時候被放入PermGen space區域,它和和存放Instance的Heap區域不同,GC(Garbage Collection)不會在主程序運行期對PermGen space進行清理,所以如果你的APP會LOAD很多CLASS的話,就很可能出現PermGen space錯誤。這種錯誤常見在web服務器對JSP進行pre compile的時候。 如果你的WEB APP下都用了大量的第三方jar, 其大小 超過了jvm默認的大小(4M)那麼就會產生此錯誤信息了。包括下面的數據:

1.Class的節本信息
Package Name
Super class package name
Class or interface
Type modifiers
Super inferface package name
2.其它信息
The constant pool for the type 
Field information 
Method information

對堆的解釋:

(1)堆是JVM中所有線程共享的,因此在其上進行對象內存的分配均需要進行加鎖,這也導致了new對象的開銷是比較大的。

(2)鑑於上面的原因,Sun Hotspot JVM爲了提升對象內存分配的效率,對於所創建的線程都會分配一塊獨立的空間,這塊空間又稱爲TLAB(Thread Local Allocation Buffer),其大小由JVM根據運行的情況計算而得,在TLAB上分配對象時不需要加鎖,因此JVM在給線程的對象分配內存時會盡量的在TLAB上分配,在這種情況下JVM中分配對象內存的性能和C基本是一樣高效的,但如果對象過大的話則仍然是直接使用堆空間分配。

(3)TLAB僅作用於新生代的Eden Space,因此在編寫Java程序時,通常多個小的對象比大的對象分配起來更加高效,
但這種方法同時也帶來了兩個問題,一是空間的浪費,二是對象內存的回收上仍然沒法做到像Stack那麼高效,同時也會增加回收時的資源的消耗,可通過在啓動參數上增加-XX:+PrintTLAB來查看TLAB這塊的使用情況。

第四塊:方法區域(Method Area)
(1)方法區域存放了所加載的類的信息(名稱、修飾符等)、類中的靜態變量、類中定義爲final類型的常量、類中的Field信息、類中的方法信息,當開發人員在程序中通過Class對象中的getName、isInterface等方法來獲取信息時,這些數據都來源於方法區域,可見方法區域的重要性,同樣,方法區域也是全局共享的,在一定的條件下它也會被GC,當方法區域需要使用的內存超過其允許的大小時,會拋出OutOfMemory的錯誤信息。
(2)在Sun JDK中這塊區域對應的爲Permanet Generation,又稱爲持久代,默認爲64M,可通過-XX:PermSize以及-XX:MaxPermSize來指定其大小。

sunJDK的方法區可能會拋出java.lang.OutOfMemoryError: PermGen space異常。

Java 8中已經放棄了對永久代的分配,使用Native Memory來進行管理。

第五塊:運行時常量池(Runtime Constant Pool)
類似C中的符號表,存放的爲類中的固定的常量信息、方法和Field的引用信息等,其空間從方法區域中分配。
這裏寫圖片描述

第六塊:本地方法堆棧(Native Method Stacks)
JVM採用本地方法堆棧來支持native方法的執行,此區域用於存儲每個native方法調用的狀態。

這裏寫圖片描述

基本垃圾回收算法
按照基本回收策略分
引用計數(Reference Counting):
比較古老的回收算法。原理是此對象有一個引用,即增加一個計數,刪除一個引用則減少一個計數。垃圾回收時,只收集計數爲0的對象。
此算法最致命的是無法處理循環引用的問題。

標記-清除(Mark-Sweep):
此算法執行分兩階段。第一階段從引用根節點開始標記所有被引用的對象,第二階段遍歷整個堆,把未標記的
對象清除。此算法需要暫停整個應用,同時,會產生內存碎片。

複製(Copying):
此算法把內存空間劃爲兩個相等的區域,每次只使用其中一個區域。垃圾回收時,遍歷當前使用區域,把正在
使用中的對象複製到另外一個區域中。次算法每次只處理正在使用中的對象,因此複製成本比較小,同時複製
過去以後還能進行相應的內存整理,不會出現“碎片”問題。當然,此算法的缺點也是很明顯的,就是需要兩
倍內存空間。

標記-整理(Mark-Compact):
此算法結合了“標記-清除”和“複製”兩個算法的優點。也是分兩階段,第一階段從根節點開始標記所有被引
用對象,第二階段遍歷整個堆,把清除未標記對象並且把存活對象“壓縮”到堆的其中一塊,按順序排放。此
算法避免了“標記-清除”的碎片問題,同時也避免了“複製”算法的空間問題。

按分區對待的方式分
增量收集(Incremental Collecting):

實時垃圾回收算法,即:在應用進行的同時進行垃圾回收。不知道什麼
原因JDK5.0中的收集器沒有使用這種算法的。

分代收集(Generational Collecting):

基於對對象生命週期分析後得出的垃圾回收算法。把對象分爲年青
代、年老代、持久代,對不同生命週期的對象使用不同的算法(上述方式中的一個)進行回收。現在的垃圾回
收器(從J2SE1.2開始)都是使用此算法的。

按系統線程分
串行收集:

串行收集使用單線程處理所有垃圾回收工作,因爲無需多線程交互,實現容易,而且效率比較高。但
是,其侷限性也比較明顯,即無法使用多處理器的優勢,所以此收集適合單處理器機器。當然,此收集器也可
以用在小數據量(100M左右)情況下的多處理器機器上。

並行收集:

並行收集使用多線程處理垃圾回收工作,因而速度快,效率高。而且理論上CPU數目越多,越能體現
出並行收集器的優勢。

併發收集:

相對於串行收集和並行收集而言,前面兩個在進行垃圾回收工作時,需要暫停整個運行環境,而只有
垃圾回收程序在運行,因此,系統在垃圾回收時會有明顯的暫停,而且暫停時間會因爲堆越大而越長。


JVM垃圾回收

一、 JVM中自動的對象內存回收機制稱爲:GC(Garbage Collection)
1、GC的基本原理
爲將內存中不再被使用的對象進行回收,GC中用於回收內存中不被使用的對象的方法稱爲收集器,由於GC需要消耗一些資源和時間的,Java在對對象的生命週期特徵進行分析後,在V 1.2以上的版本採用了分代的方式來進行對象的收集,即按照新生代、舊生代的方式來對對象進行收集,以儘可能的縮短GC對應用造成的暫停
(1)對新生代的對象的收集稱爲minor GC
(2)對舊生代的對象的收集稱爲Major GC
(3)程序中主動調用System.gc()強制執行的GC爲Full GC

二、 JVM中自動內存回收機制
(1)引用計數收集器
原理:
引用計數是標識Heap中對象狀態最明顯的一種方法,引用計數的方法簡單來說就是對每一個對象都提供一個關聯的引用計數,以此來標識該對象是否被使用,當這個計數爲零時,說明這個對象已經不再被使用了。

優點:
引用計數的好處是可以不用暫停應用,當計數變爲零時,即可將此對象的內存空間回收,但它需要給每個對象附加一個關聯引用計數

缺點:
並且引用計數無法解決循環引用的問題,因此JVM並沒有採用引用計數。

(2)跟蹤收集器
原理:
跟蹤收集器的方法爲停止應用的工作,然後開始跟蹤對象,跟蹤時從對象根開始沿着引用跟蹤,直到檢查完所有的對象。

根對象的來源主要有三種:
1.被加載的類的常量池中的對象引用
2.傳到本地方法中,沒有被本地方法“釋放”的對象引用
3.虛擬機運行時數據區中從垃圾收集器的堆中分配的部分

存在問題:
跟蹤收集器採用的均爲掃描的方法,但JVM將Heap分爲了新生代和舊生代,在進行minor GC時需要掃描是否有舊生代引用了新生代中的對象,但又不可能每次minor GC都掃描整個舊生代中的對象,因此JVM採用了一種稱爲卡片標記(Card Marking)的算法來避免這種現象。

(3)卡片標記算法
卡片標記的算法爲將舊生代以某個大小(例如512字節)進行劃分,劃分出來的每個區域稱爲卡片,JVM採用卡表維護卡的狀態,每張卡片在卡表中佔用一個字節的標識(有些JVM實現可能會不同),當Java代碼執行過程中發現舊生代的對象引用或釋放了對於新生代對象的引用時,就相應的修改卡表中卡的狀態,每次Minor GC只需掃描卡表中標識爲髒狀態的卡中的對象即可,圖示如下:
這裏寫圖片描述
1、跟蹤收集器在掃描時最重要的是要根據這些對象是否被引用來標識其狀態

2、JVM中將對象的引用分爲了四種類型,不同的對象引用類型會造成GC採用不同的方法進行回收:
(1)強引用:默認情況下,對象採用的均爲強引用
(這個對象的實例沒有其他對象引用,GC時纔會被回收)
(2)軟引用:軟引用是Java中提供的一種比較適合於緩存場景的應用
(只有在內存不夠用的情況下才會被GC)
(3)弱引用:在GC時一定會被GC回收
(4)虛引用:由於虛引用只是用來得知對象是否被GC

問題:能不能談談,java GC 是在什麼時候,對什麼東西,做了什麼事情?
—>程序員不能具體控制時間,系統在不可預測的時間調用System.gc()函數的時候;當然可以通過調優,用NewRatio控制newObject和oldObject的比例,用MaxTenuringThreshold 控制進入oldObject的次數,使得oldObject 存儲空間延遲達到full gc,從而使得計時器引發gc時間延遲OOM的時間延遲,以延長對象生存期。

超出了作用域或引用計數爲空的對象;從gc root開始搜索找不到的對象,而且經過一次標記、清理,仍然沒有復活的對象。

刪除不使用的對象,回收內存空間;運行默認的finalize,當然程序員想立刻調用就用dipose調用以釋放資源如文件句柄,JVM用from survivor、to survivor對它進行標記清理,對象序列化後也可以使它復活。

參考:JVM 工作原理·淘寶大學培訓

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章