Java 類加載機制 ClassLoader Class.forName 內存管理 垃圾回收GC

類加載是Java程序運行的第一步,研究類的加載有助於瞭解JVM執行過程,並指導開發者採取更有效的措施配合程序執行。
研究類加載機制的第二個目的是讓程序能動態的控制類加載,比如熱部署等,提高程序的靈活性和適應性。
一、簡單過程
Java程序運行的場所是內存,當在命令行下執行:
java HelloWorld
命令的時候,JVM會將HelloWorld.class加載到內存中,並形成一個Class的對象HelloWorld.class。
其中的過程就是類加載過程:
1、尋找jre目錄,尋找jvm.dll,並初始化JVM;
2、產生一個Bootstrap Loader(啓動類加載器);
3、Bootstrap Loader自動加載Extended Loader(標準擴展類加載器),並將其父Loader設爲Bootstrap Loader。
4、Bootstrap Loader自動加載AppClass Loader(系統類加載器),並將其父Loader設爲Extended Loader。
5、最後由AppClass Loader加載HelloWorld類。
以上就是類加載的最一般的過程。
二、類加載器各自搜索的目錄

爲了弄清楚這個問題,首先還要看看System類的API doc文檔。

1、Bootstrap Loader(啓動類加載器):加載System.getProperty("sun.boot.class.path")所指定的路徑或jar。
2、Extended Loader(標準擴展類加載器ExtClassLoader):加載System.getProperty("java.ext.dirs")所指定的路徑或jar。在使用Java運行程序時,也可以指定其搜索路徑,例如:java -Djava.ext.dirs=d:/projects/testproj/classes HelloWorld
3、AppClass Loader(系統類加載器AppClassLoader):加載System.getProperty("java.class.path")所指定的路徑或jar。在使用Java運行程序時,也可以加上-cp來覆蓋原有的Classpath設置,例如: java -cp ./lavasoft/classes HelloWorld
ExtClassLoader和AppClassLoader在JVM啓動後,會在JVM中保存一份,並且在程序運行中無法改變其搜索路徑。如果想在運行時從其他搜索路徑加載類,就要產生新的類加載器。
三、類加載器的特點
1、運行一個程序時,總是由AppClass Loader(系統類加載器)開始加載指定的類。
2、在加載類時,每個類加載器會將加載任務上交給其父,如果其父找不到,再由自己去加載。
3、Bootstrap Loader(啓動類加載器)是最頂級的類加載器了,其父加載器爲null.
四、類加載器的獲取
很容易,看下面例子
public  class HelloWorld { 
         public  static  void main(String[] args) { 
                 HelloWorld hello =  new HelloWorld(); 
                 Class c = hello.getClass(); 
                 ClassLoader loader = c.getClassLoader(); 
                 System.out.println(loader); 
                 System.out.println(loader.getParent()); 
                 System.out.println(loader.getParent().getParent()); 
         } 
}
打印結果:
sun.misc.Launcher$AppClassLoader@19821f 
sun.misc.Launcher$ExtClassLoader@addbf1 
null 

Process finished with exit code 0
從上面的結果可以看出,並沒有獲取到ExtClassLoader的父Loader,原因是Bootstrap Loader(啓動類加載器)是用C語言實現的,找不到一個確定的返回父Loader的方式,於是就返回null。
五、類的加載
類加載有三種方式:
1、命令行啓動應用時候由JVM初始化加載
2、通過Class.forName()方法動態加載
3、通過ClassLoader.loadClass()方法動態加載
三種方式區別比較大,看個例子就明白了:
public  class HelloWorld { 
         public  static  void main(String[] args)  throws ClassNotFoundException { 
                 ClassLoader loader = HelloWorld. class.getClassLoader(); 
                 System.out.println(loader); 
                 //使用ClassLoader.loadClass()來加載類,不會執行初始化塊 
                 loader.loadClass( "Test2"); 
                 //使用Class.forName()來加載類,默認會執行初始化塊 
//                 Class.forName("Test2"); 
                 //使用Class.forName()來加載類,並指定ClassLoader,初始化時不執行靜態塊 
//                 Class.forName("Test2", false, loader); 
         } 
}
public  class Test2 { 
         static { 
                 System.out.println( "靜態初始化塊執行了!"); 
         } 
}
分別切換加載方式,會有不同的輸出結果。
六、自定義ClassLoader
爲了說明問題,先看例子:
package test; 

import java.net.MalformedURLException; 
import java.net.URL; 
import java.net.URLClassLoader; 

/** 
* 自定義ClassLoader 

* @author leizhimin 2009-7-29 22:05:48 
*/
 
public  class MyClassLoader { 
         public  static  void main(String[] args)  throws MalformedURLException, ClassNotFoundException, IllegalAccessException, InstantiationException { 
                 URL url =  new URL( "file:/E://projects//testScanner//out//production//testScanner"); 
                 ClassLoader myloader =  new URLClassLoader( new URL[]{url}); 
                 Class c = myloader.loadClass( "test.Test3"); 
                 System.out.println( "----------"); 
                 Test3 t3 = (Test3) c.newInstance(); 
         } 
}
public  class Test3 { 
         static { 
                 System.out.println( "Test3的靜態初始化塊執行了!"); 
         } 
}
運行後:
---------- 
Test3的靜態初始化塊執行了! 

Process finished with exit code 0
可以看出自定義了ClassLoader myloader = new URLClassLoader(new URL[]{url});已經成功將類Test3加載到內存了,並通過默認構造方法構造了對象Test3 t3 = (Test3) c.newInstance();
有關ClassLoader還有很重要一點:
同一個ClassLoader加載的類文件,只有一個Class實例。但是,如果同一個類文件被不同的ClassLoader載入,則會有兩份不同的ClassLoader實例(前提是着兩個類加載器不能用相同的父類加載器)。

在java.lang包裏有個ClassLoader類,ClassLoader 的基本目標是對類的請求提供服務,按需動態裝載類和資
源,只有當一個類要使用(使用new 關鍵字來實例化一個類)的時候,類加載器纔會加載這個類並初始化。
一個Java應用程序可以使用不同類型的類加載器。例如Web Application Server中,Servlet的加載使用開發
商自定義的類加載器, java.lang.String在使用JVM系統加載器,Bootstrap Class Loader,開發商定義的其他類
則由AppClassLoader加載。在JVM裏由類名和類加載器區別不同的Java類型。因此,JVM允許我們使用不同
的加載器加載相同namespace的java類,而實際上這些相同namespace的java類可以是完全不同的類。這種
機制可以保證JDK自帶的java.lang.String是唯一的。
2. 加載類的兩種方式:
(1)  隱式方式
使用new關鍵字讓類加載器按需求載入所需的類
(2)  顯式方式
由 java.lang.Class的forName()方法加載
public static Class forName(String className)
public static Class forName(String className, boolean initialize,ClassLoader loader)
參數說明:
        className - 所需類的完全限定名
        initialize - 是否必須初始化類(靜態代碼塊的初始化)
        loader - 用於加載類的類加載器

調用只有一個參數的forName()方法等效於 Class.forName(className, true, loader)。
這兩個方法,最後都要連接到原生方法forName0(),其定義如下:
private static native Class forName0(String name, boolean initialize,ClassLoader loader)
 throws ClassNotFoundException;
只有一個參數的forName()方法,最後調用的是:
forName0(className, true, ClassLoader.getCallerClassLoader());
而三個參數的forName(),最後調用的是:
forName0(name, initialize, loader);
所以,不管使用的是new 來實例化某個類、或是使用只有一個參數的Class.forName()方法,內部都隱含
了“載入類 + 運行靜態代碼塊”的步驟。而使用具有三個參數的Class.forName()方法時,如果第二個參數
爲false,那麼類加載器只會加載類,而不會初始化靜態代碼塊,只有當實例化這個類的時候,靜態代碼塊
纔會被初始化,靜態代碼塊是在類第一次實例化的時候才初始化的。
直接使用類加載器
獲得對象所屬的類 : getClass()方法
獲得該類的類加載器 : getClassLoader()方法
3.執行java XXX.class的過程
找到JRE——》找到jvm.dll——》啓動JVM並進行初始化——》產生Bootstrap Loader——》
載入ExtClassLoader——》載入AppClassLoader——》執行java XXX.class




ClassLoader是用來處理類加載的類,它管理着具體類的運行時上下文。

1.ClassLoader存在的模塊意義:

1)從java的package定義出發:

   classloader是通過分層的關聯方式來管理運行中使用的類,不同的classloader中管理的類是不相同的,或者即便兩個類毫無二致(除了路徑)也是不同的兩個類,在進行強制轉換時也會拋出ClassCastException。所以,通過classloader的限制,我們可以建立不同的package路徑以區別不同的類(注意這裏的“不同”是指,命名和實現完全一致,但是有不同的包路徑。)。那麼也是因爲有特定的classloader,我們可以實現具體模塊的加載,而不影響jvm中其他類,即發生類加載的衝突。

2)但是,如果兩個在不同路徑下的類(我們假定,這兩個類定義中,不存在package聲明,完全一樣的兩個類),經過不同的classloader加載,這兩個類在jvm中產生的實例可以相互轉換嗎?

答案是否定的。即便這兩個類除了存在位置不同之外,都完全一樣。經由不同classloader加載的兩個類依然是不同的兩個對象。通過Class.newInstance()或者Class.getConstructor().newInstance()產生的對象是完全不同的實例。

以上兩種情況,package可以使得我們的軟件架構清晰,但那不是最終作用,如果跟classloader結合起來理解,效果更好。

2.ClassLoader的類加載機制:

   ClassLoader作爲java的一個默認抽象類,給我們帶來了極大的方便,如果我們要自己實現相應的類加載算法的話。

   每個類都有一個對應的class與之綁定,並且可以通過MyClass.class方式來獲取這個Class對象。通過Class對象,我們就能獲取加載這個類的classloader。但是,我們現在要研究的是,一個類,是如何通過classloader加載到jvm中的。

   其中有幾個關鍵方法,值得我們瞭解一番:

   protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException;

我們可以假設一個實例在建立時,例如通過new方式,是經由如此步驟實現:ClassLoader.loadClass("classname",false).newInstance()。

接下來需要考慮的是loadClass方法爲我們做了哪些工作?如何跟對應的.class文件結合,如何將對應的文件變成我們的Class對象,如何獲得我們需要的類?

在ClassLoader類中,已經有了loadClass默認實現。我們結合源代碼說明一下:

protected synchronized Class<?> loadClass(String name, boolean resolve)

throws ClassNotFoundException

    {

// 首先檢查,jvm中是否已經加載了對應名稱的類,findLoadedClass(String )方法實際上是findLoadedClass0方法的wrapped方法,做了檢查類名的工

       //作,而findLoadedClass0則是一個native方法,通過底層來查看jvm中的對象。

Class c = findLoadedClass(name);

if (c == null) {//類還未加載

    try {

if (parent != null) {

                    //在類還未加載的情況下,我們首先應該將加載工作交由父classloader來處理。

    c = parent.loadClass(name, false);

} else {

                    //返回一個由bootstrap class loader加載的類,如果不存在就返回null

    c = findBootstrapClassOrNull(name);

}

    } catch (ClassNotFoundException e) {

                // ClassNotFoundException thrown if class not found

                // from the non-null parent class loader

            }

            if (c == null) {

        // If still not found, then invoke findClass in order

        // to find the class.

        c = findClass(name);//這裏是我們的入手點,也就是指定我們自己的類加載實現

    }

}

if (resolve) {

    resolveClass(c);//用來做類鏈接操作

}

return c;

    }

 

在這段代碼中,應該已經說明了很多問題,那就是jvm會緩存加載的類,所以,在我們要求classloader爲我們加載類時,要先通過findLoadedClass方法來查看是否已經存在了這個類。不存在時,就要先由其parent class loader 來loadClass,當然可以迭代這種操作一直到找到這個類的加載定義。如果這樣還是不能解決問題,對於我們自己實現的class loader而言,可以再交由system class loader來loadClass,如果再不行,那就讓findBootstrapClassOrNull。經歷瞭如此路程,依然不能解決問題時,那就要我們出馬來擺平,通過自己實現的findClass(String)方法來實現具體的類加載。

這段實現代碼摘自Andreas Schaefer寫的文章中的代碼(這篇文章相當精彩)

protected Class findClass( String pClassName )

           throws ClassNotFoundException {

       try {

           System.out.println( "Current dir: " + new File( mDirectory ).getAbsolutePath() );

           File lClassFile = new File( mDirectory, pClassName + ".class" );

           InputStream lInput = new BufferedInputStream( new FileInputStream( lClassFile ) );

           ByteArrayOutputStream lOutput = new ByteArrayOutputStream();

           int i = 0;

           while( ( i = lInput.read() ) >= 0 ) {

               lOutput.write( i );

           }

           byte[] lBytes = lOutput.toByteArray();

           return defineClass( pClassName, lBytes, 0, lBytes.length );

       } catch( Exception e ) {

           throw new ClassNotFoundException( "Class: " + pClassName + " could not be found" );

       }

   }

findClass方法主要的工作是在指定路徑中查找我們需要的類。如果存在此命名的類,那麼就將class文件加載到jvm中,再由defineClass方法(一個native方法)來生成具體的Class對象。

一般來說,經過上述方式來加載類的話,我們的類可能都在一個classloader中加載完成。但是,再強調一下,那就是如果類有不同路徑或者不同包名,那就是不同類定義。

java classLoader 體系結構


  1. Bootstrap ClassLoader/啓動類加載器 
    主要負責jdk_home/lib目錄下的核心 api 或 -Xbootclasspath 選項指定的jar包裝入工作。
  2. Extension ClassLoader/擴展類加載器 
    主要負責jdk_home/lib/ext目錄下的jar包或 -Djava.ext.dirs 指定目錄下的jar包裝入工作。
  3. System ClassLoader/系統類加載器 
    主要負責java -classpath/-Djava.class.path所指的目錄下的類與jar包裝入工作。
  4. User Custom ClassLoader/用戶自定義類加載器(java.lang.ClassLoader的子類) 
    在程序運行期間, 通過java.lang.ClassLoader的子類動態加載class文件, 體現java動態實時類裝入特性。

類加載器的特性:

  1. 每個ClassLoader都維護了一份自己的名稱空間, 同一個名稱空間裏不能出現兩個同名的類。
  2. 爲了實現java安全沙箱模型頂層的類加載器安全機制, java默認採用了 " 雙親委派的加載鏈 " 結構。
classloader-architecture

classloader-architecture

classloader-class-diagram

classloader-class-diagram

類圖中, BootstrapClassLoader是一個單獨的java類, 其實在這裏, 不應該叫他是一個java類。因爲,它已經完全不用java實現了。它是在jvm啓動時, 就被構造起來的, 負責java平臺核心庫。

自定義類加載器加載一個類的步驟

classloader-load-class

classloader-load-class

ClassLoader 類加載邏輯分析, 以下邏輯是除 BootstrapClassLoader 外的類加載器加載流程:

  1. // 檢查類是否已被裝載過  
  2. Class c = findLoadedClass(name);  
  3. if (c == null ) {  
  4.      // 指定類未被裝載過  
  5.      try {  
  6.          if (parent != null ) {  
  7.              // 如果父類加載器不爲空, 則委派給父類加載  
  8.              c = parent.loadClass(name, false );  
  9.          } else {  
  10.              // 如果父類加載器爲空, 則委派給啓動類加載加載  
  11.              c = findBootstrapClass0(name);  
  12.          }  
  13.      } catch (ClassNotFoundException e) {  
  14.          // 啓動類加載器或父類加載器拋出異常後, 當前類加載器將其  
  15.          // 捕獲, 並通過findClass方法, 由自身加載  
  16.          c = findClass(name);  
  17.      }  
  18. }  

線程上下文類加載器
java默認的線程上下文類加載器是 系統類加載器(AppClassLoader)。

  1. // Now create the class loader to use to launch the application  
  2. try {  
  3.     loader = AppClassLoader.getAppClassLoader(extcl);  
  4. catch (IOException e) {  
  5.     throw new InternalError(  
  6. "Could not create application class loader" );  
  7. }   
  8.   
  9. // Also set the context class loader for the primordial thread.  
  10. Thread.currentThread().setContextClassLoader(loader);  

以上代碼摘自sun.misc.Launch的無參構造函數Launch()。

使用線程上下文類加載器, 可以在執行線程中, 拋棄雙親委派加載鏈模式, 使用線程上下文裏的類加載器加載類.
典型的例子有, 通過線程上下文來加載第三方庫jndi實現, 而不依賴於雙親委派.
大部分java app服務器(jboss, tomcat..)也是採用contextClassLoader來處理web服務。
還有一些採用 hotswap 特性的框架, 也使用了線程上下文類加載器, 比如 seasar (full stack framework in japenese).

線程上下文從根本解決了一般應用不能違背雙親委派模式的問題.
使java類加載體系顯得更靈活.

隨着多核時代的來臨, 相信多線程開發將會越來越多地進入程序員的實際編碼過程中. 因此,
在編寫基礎設施時, 通過使用線程上下文來加載類, 應該是一個很好的選擇。

當然, 好東西都有利弊. 使用線程上下文加載類, 也要注意, 保證多根需要通信的線程間的類加載器應該是同一個,
防止因爲不同的類加載器, 導致類型轉換異常(ClassCastException)。

爲什麼要使用這種雙親委託模式呢?

  1. 因爲這樣可以避免重複加載,當父親已經加載了該類的時候,就沒有必要子ClassLoader再加載一次。
  2. 考慮到安全因素,我們試想一下,如果不使用這種委託模式,那我們就可以隨時使用自定義的String來動態替代java核心api中定義類型,這樣會存在非常大的安全隱患,而雙親委託的方式,就可以避免這種情況,因爲String已經在啓動時被加載,所以用戶自定義類是無法加載一個自定義的ClassLoader。

java動態載入class的兩種方式:

  1. implicit隱式,即利用實例化才載入的特性來動態載入class
  2. explicit顯式方式,又分兩種方式:
    1. java.lang.Class的forName()方法
    2. java.lang.ClassLoader的loadClass()方法

用Class.forName加載類

Class.forName使用的是被調用者的類加載器來加載類的。
這種特性, 證明了java類加載器中的名稱空間是唯一的, 不會相互干擾。
即在一般情況下, 保證同一個類中所關聯的其他類都是由當前類的類加載器所加載的。

  1. public static Class forName(String className)  
  2.      throws ClassNotFoundException {  
  3.      return forName0(className, true , ClassLoader.getCallerClassLoader());  
  4. }   
  5.   
  6. /** Called after security checks have been made. */  
  7. private static native Class forName0(String name, boolean initialize,  
  8. ClassLoader loader)  
  9.      throws ClassNotFoundException;  

上面中 ClassLoader.getCallerClassLoader 就是得到調用當前forName方法的類的類加載器

static塊在什麼時候執行?

  • 當調用forName(String)載入class時執行,如果調用ClassLoader.loadClass並不會執行.forName(String,false,ClassLoader)時也不會執行.
  • 如果載入Class時沒有執行static塊則在第一次實例化時執行.比如new ,Class.newInstance()操作
  • static塊僅執行一次

各個java類由哪些classLoader加載?

  • java類可以通過實例.getClass.getClassLoader()得知
  • 接口由AppClassLoader(System ClassLoader,可以由ClassLoader.getSystemClassLoader()獲得實例)載入
  • ClassLoader類由bootstrap loader載入

NoClassDefFoundError和ClassNotFoundException

  • NoClassDefFoundError:當java源文件已編譯成.class文件,但是ClassLoader在運行期間在其搜尋路徑load某個類時,沒有找到.class文件則報這個錯
  • ClassNotFoundException:試圖通過一個String變量來創建一個Class類時不成功則拋出這個異常


垃圾回收分爲兩大步驟:識別垃圾 和回收垃圾

    識別垃圾有兩大基本方法

   1.計數器法

每個對象有一個相應的計數器,統計當前被引用的個數,每次被引用或者失去引用都會更新該計數器。

優點:識別垃圾快,只需判斷計數器是否爲零。

缺點:增加了維護計數器的成本,無法在對象互相引用的情況下識別垃圾,因此,適用於對實時性要求非常高的系統。

   2.追蹤法

從根對象(例如局部變量)出發,逐一遍歷它的引用。若無法被掃描到,即認定爲垃圾,實際情況中一般採用該方法。

    回收垃圾最重要的是要最大限度地減少內存碎片。

   兩種兩大基本方法:

   1.移動活對象覆蓋內存碎片,使對象間的內存空白增大。

   2.拷貝所有的活對象到另外一塊完整的空白內存,然後一次釋放原來的內存。

   通常第二種方法能夠最大的減少內存碎片,但是缺點是在拷貝過程中會終止程序的運行。

引入分級的概念,通常一個程序中大部分對象的生命週期很短,只有小部分的對象有比較長的生命。而恰恰使得拷貝方法性能打折扣的是重複拷貝那些長命的對象。因此,把對象分成幾個級別,在低級別呆到一定時間就將其升級。相應地越高級別,回收的次數越少。最理想的情況是,每次回收最低級別的對象全部失效,一次性就可以回收該級別所有內存,提高效率。同時,由於每次只回收一個級別,不需遍歷所有對象,控制了整個回收的時間。

   由於垃圾識別是通過識別引用來達到,爲了增加程序對垃圾回收的控制。提供了引用對象的概念,細化了引用的類型,分別是StrongReferenceSoftReference, WeakReference, PhantomReference。其中強引用就是普通的java引用,其他三種類型相當於一個包裝器,一方面使得垃圾回收器區分引用類型做不同的處理,另一方面程序通過他們仍然可以得到強引用。

       分代垃圾回收機制:

 

   如上圖所示,現代GC採用分區管理機制的JVMJVM所管理的所有內存資源分爲2個大的部分。永久存儲區(Permanent Space)和堆空間(The Heap Space)。其中堆空間又分爲新生區(Young (New) generation space)和養老區(Tenure (Old) generation space),新生區又分爲伊甸園(Eden space),倖存者0區(Survivor 0 space)和倖存者1區(Survivor 1 space)。具體分區如下圖:

   JVM他的這些分區各有什麼用途,請看下面的解說。

  永久存儲區(Permanent Space):永久存儲區是JVM的駐留內存,用於存放JDK自身所攜帶的Class,Interface的元數據,應用服務器允許必須的Class,Interface的元數據和Java程序運行時需要的ClassInterface的元數據。被裝載進此區域的數據是不會被垃圾回收器回收掉的,關閉JVM時,釋放此區域所控制的內存。 

   堆空間(The Heap Space):是JAVA對象生死存亡的地區,JAVA對象的出生,成長,死亡都在這個區域完成。堆空間又分別按JAVA對象的創建和年齡特徵分爲養老區和新生區。

   新生區(Young (New) generation space ):新生區的作用包括JAVA對象的創建和從JAVA對象中篩選出能進入養老區的JAVA對象。

   伊甸園(Eden space):JAVA對空間中的所有對象在此出生,該區的名字因此而得名。也即是說當你的JAVA程序運行時,需要創建新的對象,JVM將在該區爲你創建一個指定的對象供程序使用。創建對象的依據即是永久存儲區中的元數據。

   倖存者0區(Survivor 0 space)和倖存者1區(Survivor1 space):當伊甸園的控件用完時,程序又需要創建對象;此時JVM的垃圾回收器將對伊甸園區進行垃圾回收,將伊甸園區中的不再被其他對象所引用的對象進行銷燬工作。同時將伊甸園中的還有其他對象引用的對象移動到倖存者0區。倖存者0區就是用於存放伊甸園垃圾回收時所幸存下來的JAVA對象。當將伊甸園中的還有其他對象引用的對象移動到倖存者0區時,如果倖存者0區也沒有空間來存放這些對象時,JVM的垃圾回收器將對倖存者0區進行垃圾回收處理,將倖存者0區中不在有其他對象引用的JAVA對象進行銷燬,將倖存者0區中還有其他對象引用的對象移動到倖存者1區。倖存者1區的作用就是用於存放倖存者0區垃圾回收處理所幸存下來的JAVA對象。

   養老區(Tenure (Old) generation space):用於保存從新生區篩選出來的JAVA對象。

上面我們看了JVM的內存分區管理,現在我們來看JVM的垃圾回收工作是怎樣運作的。首先當啓動J2EE應用服務器時,JVM隨之啓動,並將JDK的類和接口,應用服務器運行時需要的類和接口以及J2EE應用的類和接口定義文件也及編譯後的Class文件或JAR包中的Class文件裝載到JVM的永久存儲區。在伊甸園中創建JVM,應用服務器運行時必須的JAVA對象,創建J2EE應用啓動時必須創建的JAVA對象;J2EE應用啓動完畢,可對外提供服務。

JVM在伊甸園區根據用戶的每次請求創建相應的JAVA對象,當伊甸園的空間不足以用來創建新JAVA對象的時候,JVM的垃圾回收器執行對伊甸園區的垃圾回收工作,銷燬那些不再被其他對象引用的JAVA對象(如果該對象僅僅被一個沒有其他對象引用的對象引用的話,此對象也被歸爲沒有存在的必要,依此類推),並將那些被其他對象所引用的JAVA對象移動到倖存者0區。

   如果倖存者0區有足夠控件存放則直接放到倖存者0區;如果倖存者0區沒有足夠空間存放,則JVM的垃圾回收器執行對倖存者0區的垃圾回收工作,銷燬那些不再被其他對象引用的JAVA對象(如果該對象僅僅被一個沒有其他對象引用的對象引用的話,此對象也被歸爲沒有存在的必要,依此類推),並將那些被其他對象所引用的JAVA對象移動到倖存者1區。

如果倖存者1區有足夠控件存放則直接放到倖存者1區;如果倖存者0區沒有足夠空間存放,則JVM的垃圾回收器執行對倖存者0區的垃圾回收工作,銷燬那些不再被其他對象引用的JAVA對象(如果該對象僅僅被一個沒有其他對象引用的對象引用的話,此對象也被歸爲沒有存在的必要,依此類推),並將那些被其他對象所引用的JAVA對象移動到養老區。

   如果養老區有足夠控件存放則直接放到養老區;如果養老區沒有足夠空間存放,則JVM的垃圾回收器執行對養老區區的垃圾回收工作,銷燬那些不再被其他對象引用的JAVA對象(如果該對象僅僅被一個沒有其他對象引用的對象引用的話,此對象也被歸爲沒有存在的必要,依此類推),並保留那些被其他對象所引用的JAVA對象。如果到最後養老區,倖存者1區,倖存者0區和伊甸園區都沒有空間的話,則JVM會報告“JVM堆空間溢出(java.lang.OutOfMemoryError: Java heap space)”,也即是在堆空間沒有空間來創建對象。

   這就是JVM的內存分區管理,相比不分區來說;一般情況下,垃圾回收的速度要快很多;因爲在沒有必要的時候不用掃描整片內存而節省了大量時間。

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