<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ### JVM工作原理 JVM主要由**ClassLoader**和**執行引擎**兩子系統組成,運行時數據區分為五個部分: 方法區、堆、棧、程序計數器、本地方法棧。方法區和堆是所有線程共享的,JVM將臨時變量放在棧中,每個線程都有自己獨立的棧空間和程序計數器。任何一個Java類的main函數運行都會創建一個JVM實例,JVM實例啟動時默認啟動幾個守護線程,比如:垃圾回收的線程,而main方法的執行在一個單獨的非守護線程中執行。只要非守護線程結束JVM實例就銷毀了。那么在Java類main函數運行過程中,JVM的工作原理如下: 1. 根據系統環境變量,創建裝載JVM的環境與配置; 2. 尋找JRE目錄,尋找jvm.dll,并裝載jvm.dll; 3. 根據JVM的參數配置,如:內存參數,初始化jvm實例; 4. JVM實例產生一個引導類加載器實例(Bootstrap Loader),加載Java核心庫,然后引導類加載器自動加載擴展類加載器(Extended Loader),加載Java擴展庫,最后擴展類加載器自動加載系統類加載器(AppClass Loader),加載當前的Java類; 5. 當前Java類加載至內存后,會經過 驗證、準備、解析三步,將Java類中的類型信息、屬性信息、常量池存放在方法區內存中,方法指令直接保存到棧內存中,如:main函數; 6. 執行引擎開始執行棧內存中指令,由于main函數是靜態方法,所以不需要傳入實例,在類加載完畢之后,直接執行main方法指令; 7. main函數執行主線程結束,隨之守護線程銷毀,最后JVM實例被銷毀; ### JVM Java 虛擬機(JVM)是運行 Java 程序必不可少的機制。JVM實現了Java語言最重要的特征:即平臺無關性。原理:編譯后的 Java 程序指令并不直接在硬件系統的 CPU 上執行,而是由 JVM 執行。JVM屏蔽了與具體平臺相關的信息,使Java語言編譯程序只需要生成在JVM上運行的目標字節碼(.class),就可以在多種平臺上不加修改地運行。Java 虛擬機在執行字節碼時,把字節碼解釋成具體平臺上的機器指令執行。因此實現java平臺無關性。JVM 是 編譯后的 Java 程序(.class文件)和硬件系統之間的接口 ( 編譯后:javac 是收錄于 JDK 中的 Java 語言編譯器。該工具可以將后綴名為. java 的源文件編譯為后綴名為. class 的可以運行于 Java 虛擬機的字節碼。) JVM = 類加載器classloader + 執行引擎 execution engine + 運行時數據區域runtime data area,classloader 把硬盤上的class 文件加載到JVM中的運行時數據區域, 但是它不負責這個類文件能否執行,而是由執行引擎負責的。 ## classloader 類加載器 作用:裝載.class文件到JVM中的運行時數據區。classloader 有兩種裝載class的方式(時機): - 顯示加載:在代碼中通過調用ClassLoader加載class對象,如直接使用Class.forName(name)或this.getClass().getClassLoader().loadClass()加載class對象。 - 隱式加載:不直接在代碼中調用ClassLoader的方法加載class對象,而是通過虛擬機自動加載到內存中,如在加載某個類的class文件時,該類的class文件中引用了另外一個類的對象,此時額外引用的類將通過JVM自動加載到內存中。 ### 類的加載過程 ![](https://box.kancloud.cn/499300c5e84b26ec80fc3630c5431801_899x292.png) 1. 加載:通過一個類的全限定名來獲取其定義的二進制字節流。二進制字節流可以從Class文件中獲取,還可以從Jar、EAR、War包中獲取、從網絡中獲取、由其他文件生成(JSP應用)、運行時計算生成(比如動態代理)等。將這個字節流所代表的靜態存儲結構(類信息、靜態變量、字節碼、常量這些)轉化為方法區的運行時數據結構。在內存中生成一個代表這個.class文件的java.lang.Class對象,作為方法區這個類的各種數據的訪問入口。一般這個Class是在堆里的,不過HotSpot虛擬機比較特殊,這個Class對象是放在方法區中的。 2. 驗證:確保Class文件中的字節流包含的信息符合當前虛擬機的要求,而且不會危害虛擬機自身的安全。 3. 準備:為類變量(即static修飾的字段變量)分配內存并且設置該類變量的初始值即0(如static int i=5;這里只將i初始化為0,至于5的值將在初始化時賦值),這里不包含用final修飾的static,因為final在編譯的時候就會分配了,注意這里不會為實例變量分配初始化,類變量會分配在方法區中,而實例變量是會隨著對象一起分配到Java堆中。 4. 解析:將常量池內的符號引用替換為直接引用的過程。Class文件中不會保存各個方法和字段的最終內存布局信息,因此,這些字段和方法的符號引用不經過轉換是無法直接被虛擬機使用的。當虛擬機運行時,需要從常量池中獲得對應的符號引用,再在類加載過程中的解析階段將其替換為直接引用,并翻譯到具體的內存地址中。 5. 初始化:給static變量賦予用戶指定的值以及執行靜態代碼塊。虛擬機規范嚴格規定了有且只有5種情況必須立即對類進行初始化: 1. 使用new關鍵字實例化對象時、讀取或設置一個類的靜態字段(static)時(被static修飾又被final修飾的,已在編譯期把結果放入常量池的靜態字段除外)、以及調用一個類的靜態方法時。 2. 使用Java.lang.refect包的方法對類進行反射調用時。 3. 當初始化一個類的時候,如果發現其父類還沒有進行初始化,則需要先觸發其父類的初始化。 4. 當虛擬機啟動時,用戶需要指定一個主類,虛擬機會先執行該主類。 5. 當使用Jdk1.7的動態語言支持時,如果一個java.lang.invoke.MethodHandle實例最后解析的結果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且這個方法句柄所對應的類沒有進行過初始化,則需要進行初始化。 以上5中場景稱之為對一個類的主動引用。除此之外,所有引用類的方式都不會觸發初始化,稱為被動引用。關于被動引用:比如如下的場景:子類引用父類的靜態字段,不會導致子類初始化;通過數組定義來引用類,不會觸發類的初始化;引用靜態常量時,常量在編譯階段會存入該類的常量池中,本質上并沒有直接引用到定義常量的類 6. 使用 7. 卸載:卸載是對象被GC的階段,JVM中的Class只有滿足下面的三個條件,才會被卸載,也就是被回收:該類所有的實例都被GC,不存在該類的任何實例;加載該類的ClassLoader已經被GC;該類的java.lang.Class對象沒有在任何地方被引用,比如不能再任何地方通過反射訪問該類的方法 ### 類加載器 通過一個類的全限定名來獲取描述此類的二進制字節流的代碼塊稱之為類加載器。 比較兩個類是否"相等",只有在這兩個類是由同一個類加載器加載的前提下才有意義,否則即使這兩個類來源于同一個.class文件,被同一個虛擬機加載,只要加載它們的類加載器不同,這兩個類必定不相等。"相等"包括代表類的.class對象的equals()方法、isAssignableFrom()方法、isInstance()方法的返回結果,也包括使用instanceof關鍵字做對象所屬關系判定等情況。 類加載器可以大致劃分為以下三類: - 啟動類加載器:Bootstrap ClassLoader。負責加載存放在JDK\jre\lib,或被-Xbootclasspath參數指定的路徑中的,并且能被虛擬機識別的類庫(如rt.jar,所有的java.*開頭的類均被Bootstrap ClassLoader加載)。啟動類加載器是無法被Java程序直接引用的。 - 擴展類加載器:Extension ClassLoader,該加載器由sun.misc.Launcher$ExtClassLoader實現,它負責加載JDK\jre\lib\ext目錄中,或者由java.ext.dirs系統變量指定的路徑中的所有類庫(如javax.*開頭的類),開發者可以直接使用擴展類加載器。 - 應用程序類加載器:Application ClassLoader,該類加載器由sun.misc.Launcher$AppClassLoader來實現,它負責加載用戶類路徑(ClassPath)所指定的類,開發者可以直接使用該類加載器,如果應用程序中沒有自定義過自己的類加載器,一般情況下這個就是程序中默認的類加載器。 應用程序都是由這三種類加載器互相配合進行加載的,如果有必要,我們還可以加入自定義的類加載器。因為JVM自帶的ClassLoader只是從本地文件系統加載標準的java class文件,如果編寫了自己的ClassLoader,便可以做到如下幾點: - 在執行非置信代碼之前,自動驗證數字簽名。 - 動態地創建符合用戶特定需要的定制化構建類。 - 從特定的場所取得Java class,例如數據庫中和網絡中。 ### 雙親委派模型 Java虛擬機對class文件采用的是按需加載的方式,也就是說當需要使用該類時才會將它的class文件加載到內存生成class對象,而且加載某個類的class文件時,Java虛擬機采用的是雙親委派模式即把請求交由父類處理。雙親委派模式要求除了頂層的啟動類加載器外,其余的類加載器都應當有自己的父類加載器,父子關系并非通常所說的類繼承關系,而是采用組合關系來復用父類加載器的相關代碼,類加載器間的關系如下: ![](https://img-blog.csdn.net/20170625231013755?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvamF2YXplamlhbg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) **雙親委派模型的工作過程**:如果一個類加載器收到了類加載的請求,它首先不會自己去嘗試加載這個類,而是把請求委托給父加載器去完成,依次向上,因此,所有的類加載請求最終都應該被傳遞到頂層的啟動類加載器中,只有當父加載器在它的搜索范圍中沒有找到所需的類時,即無法完成該加載,子加載器才會嘗試自己去加載該類。 **雙親委派模式的優勢**:Java類隨著它的類加載器一起具備了一種帶有優先級的層次關系,通過這種層級關可以避免類的重復加載,當父親已經加載了該類時,就沒有必要子ClassLoader再加載一次。其次是考慮到安全因素,java核心api中定義類型不會被隨意替換,假設通過網絡傳遞一個名為java.lang.Integer的類,通過雙親委托模式傳遞到啟動類加載器,而啟動類加載器在核心Java API發現這個名字的類,發現該類已被加載,并不會重新加載網絡傳遞的過來的java.lang.Integer,而直接返回已加載過的Integer.class,這樣便可以防止核心API庫被隨意篡改。可能你會想,如果我們在classpath路徑下自定義一個名為java.lang.SingleInterge類(該類是胡編的)呢?該類并不存在java.lang中,經過雙親委托模式,傳遞到啟動類加載器中,由于父類加載器路徑下并沒有該類,所以不會加載,將反向委托給子類加載器加載,最終會通過系統類加載器加載該類。但是這樣做是不允許,因為java.lang是核心API包,需要訪問權限,強制加載將會報出異常。相反,如果沒有雙親委派模型,由各個類自己去加載的話,如果用戶自己編寫了一個java.lang.Object,并放在CLASSPATH下,那系統中將會出現多個不同的Object類,Java體系中最基礎的行為也將無法保證,應用程序也將會變得一片混亂。如果一個對象每次加載都是由不同的類加載器加載的,就會出現很多同名但不是同一個類的類。 **雙親委派模型的缺陷**:雙親委派模型解決了各個類加載器的基礎類的統一問題,越基礎的類由越上層的加載器進行加載,基礎類的代碼總是作為被用戶代碼調用的API,如果基礎類要調用用戶的代碼,這就有問題了。比如JNDI服務(JNDI的目的是對資源進行集中管理和查找,要調用由獨立廠商實現并部署在應用程序的ClassPath下的JNDI接口提供者(SPI)的代碼),啟動類加載器不認可這些代碼,所以引入了線程上下文類加載器(Thread Context ClassLoader),用父類加載器請求子類加載器完成類加載。JNDI,JDBC等都是采用這種方式。 **OSGI原理**:模塊熱部署,打破了雙親委派模型。OSGI實現模塊化熱部署的關鍵是它自定義的類加載器機制的實現,每一個程序模塊(Bundle)都有自己的類加載器,當需要更換一個Bundle時,就把Bundle連同類加載器一起換掉實現模塊熱部署。OSGI的類加載器不再是雙親委派模型中的樹狀結構,而是復雜的網狀結構。例如bundleA、B都依賴于bundleC,當他們訪問bundleC中的類時,就會委托給bundleC的類加載器,由它來查找類;如果它發現還要依賴bundleE中的類,就會再委托給bundleE的類加載器。 ## 運行時數據區 JVM 運行時數據區 (JVM Runtime Area) 其實就是指 JVM 在運行期間,其對JVM內存空間的劃分和分配。JVM在運行時將數據劃分為了6個區域來存儲。 ### Java內存區域 * **程序計數器**:一塊較小的內存空間,它是當前線程所執行的字節碼的行號指示器,當線程在執行一個Java方法時,該計數器記錄的是正在執行的虛擬機字節碼指令的地址,當線程在執行的是Native方法時,該計數器的值為空。線程私有,不存在內存溢出的問題。 * **Java虛擬機棧**:線程私有,描述的是java方法執行的內存模型,每個方法執行時會創建一個棧幀,用于存儲局部變量表、操作數棧、動態鏈接、方法返回地址等信息,每一個方法從調用到執行完畢的過程,就對應著一個棧幀在虛擬機中入棧到出棧的過程。 * **本地方法棧**:調用本地方法(Native)服務。 * **Java堆**:所有線程共享,幾乎所有的對象實例和數組都在這里分配內存。是垃圾收集器管理的主要區域。Java堆分為新生代(Eden、From survivor、To Survivor)和老年代。 * **方法區**:線程共享區域,用于存儲被虛擬機加載的類信息、常量、靜態變量,即時編譯器編譯后的代碼等數據。Sun HotSpot虛擬機中方法區又稱為永久代。運行時常量池是方法區的一部分,Class文件中除了有類的版本、字段、方法、接口等描述信息外,還有存有常量池(Class文件常量池),用于存放編譯器生成的各種字面量和符號引用,這部分內容在類加載后存放到方法區的運行時常量池中。在JDK8之前的HotSpot虛擬機中,類的這些“永久的”數據存放永久代。在JVM啟動之前可以通過設置-XX:MaxPermSize的值來控制永久代的大小,32位機器默認的永久代的大小為64M,64位的機器則為85M。永久代的垃圾回收和老年代的垃圾回收是綁定的,一旦其中一個區域被占滿,這兩個區都要進行垃圾回收。 在JDK7之前的HotSpot虛擬機中,納入字符串常量池的字符串被存儲在永久代中,因此導致了一系列的性能問題和內存溢出錯誤。Java8中移除了永久代。類的元數據信息被移到了一個與堆不相連的本地內存區域叫**元空間**。這項改動是很有必要的,因為對永久代進行調優是很困難的。永久代中的元數據可能會隨著每一次Full GC發生而進行移動。并且為永久代設置空間大小也是很難確定的。將元數據從永久代剝離出來,可以簡化Full GC以及對以后的并發隔離類元數據等方面進行優化。如果不設置元空間的大小,JVM會自動根據類的元數據大小動態增加元空間的容量。 * **直接內存**:不是虛擬機運行時數據區的一部分,也不是Java虛擬機規范中定義的內存區域,它直接從操作系統中分配,不受Java堆大小的限制,但是會受到本機總內存的大小及處理器尋址空間的限制,因此它也可能導致OutOfMemoryError異常出現。在JDK1.4中新引入了NIO機制,使用Native函數庫直接分配堆外內存,然后通過一個存儲在Java堆中的DirectByteBuffer對象作為這塊內存的引用進行操作,可以直接從操作系統中分配直接內存,即在堆外分配內存,這樣能在一些場景中提高性能,因為避免了在Java堆和Native堆中來回復制數據。直接內存也叫做堆外內存。 ### 每個區域可能造成的內存溢出現象 * 堆內存OutOfMemoryError:只要不斷創建對象并且對象不被回收,那么對象數量達到最大堆容量限制后就會產生內存溢出異常了。 * 棧溢出(StockOverflowError 和 OutOfMemoryError): * 方法調用的深度太深,就會產生棧溢出。我們只要寫一個無限調用自己的方法,就會出現方法調用的深度太深的場景。 * 過不斷創建線程的方式可以產生OutOfMemoryError,因為每個線程都有自己的棧空間。 * 方法區和運行時常量池溢出:運行時常量池也是方法區的一部分。這個區域的OutOfMemoryError可以利用String.intern()方法來產生。這是一個Native方法,意思是如果常量池中有一個String對象的字符串就返回池中的這個字符串的String對象;否則,將此String對象包含的字符串添加到常量池中去,并且返回此String對象的引用。JDK1.7下是不會有這個異常的,while循環將一直下去,字符串常量池移動到堆中了。JDK1.8移除了永久代并采用元空間來實現方法區的規劃了。 ### Java對象創建 在語言層面上,創建對象(克隆、反序列化)就是一個new關鍵字而已,在虛擬機層面上創建對象的步驟: 1. 虛擬機遇到一條new指令,首先檢查這個指令的參數能否在常量池中定位到一個類的符號引用,并且檢查這個符號引用代表的類是否已經被加載、解析和初始化。如果沒有,那么必須先執行類的初始化過程。 2. 類加載檢查通過后,為新生對象分配內存。對象所需內存大小在類加載完成后便可以確定,為對象分配空間就是從Java堆中劃分出一塊確定大小的內存。 3. 內存分配結束,虛擬機將分配到的內存空間都初始化為零值(不包括對象頭)。這一步保證了對象的實例字段在Java代碼中可以不用賦初始值就可以直接使用,程序能訪問到這些字段的數據類型所對應的零值。 4. 對對象進行必要的設置,例如這個對象是哪個類的實例、如何才能找到類的元數據信息、對象的哈希碼、對象的GC分代年齡等信息,這些信息存放在對象的對象頭中。 5. 執行對象構造器`<init>`方法,進行初始化,這樣一個真正可用的對象才算完全產生出來。 ### Java對象的訪問定位 對內存分配情況分析最常見的示例便是對象實例化: `Object obj = new Object();` 這段代碼的執行會涉及java棧、Java堆、方法區。假設該語句出現在方法體中,obj會作為引用類型的數據保存在Java棧的本地變量表中,在Java堆中保存該引用的實例化對象,Java堆中還必須包含能查找到此對象類型數據的地址信息,這些類型數據則保存在方法區中。 另外,由于reference類型在Java虛擬機規范里面只規定了一個指向對象的引用,不同虛擬機實現的對象訪問方式會有所不同,主流的訪問方式有兩種:使用句柄池和直接使用指針。 通過句柄池訪問的方式如下:使用句柄訪問方式的最大好處就是reference中存放的是穩定的句柄地址,在對象被移動(垃圾收集時移動對象)時只會改變句柄中的實例數據指針,而reference本身不需要修改 ![](http://img.blog.csdn.net/20131226172011765) 通過直接指針訪問的方式如下: ![](http://img.blog.csdn.net/20131226172113234) 使用直接指針訪問方式的最大好處是速度快,它節省了一次指針定位的時間開銷。目前Java默認使用的HotSpot虛擬機采用的是直接指針進行對象訪問的。 對象在堆中的布局 HotSpot虛擬機中,對象在堆內存中的布局分為三塊區域:對象頭、實例數據和對齊填充。 1. 對象頭:包括兩部分:Mark Word 和 類型指針。 * Mark Word:存儲對象自身的運行時數據,如哈希碼、GC分代年齡、鎖狀態標志、線程持有的鎖、偏向線程ID、偏向時間戳等等,占用內存大小與虛擬機位長一致。 * 類型指針:指向方法區中的對象類型數據,虛擬機通過這個指針確定該對象是哪個類的實例。 2. 實例數據:對象真正存儲的有效信息。 3. 對齊填充:由于HotSpot虛擬機要求對象的大小必須是8字節的整數倍。而對象頭部分正好是8字節的倍數,當對象實例數據部分沒有對齊的時候,就需要通過對齊填充來補全。對齊填充不是必然存在的。 #### 如何保證new對象時候的線程安全性。 因為可能出現虛擬機正在給對象A分配內存,指針還沒有來得及修改,對象B又同時使用了原來的指針來分配內存的情況。虛擬機采用了CAS配上失敗重試的方式保證更新操作的原子性和TLAB兩種方式來解決這個問題。TLAB:內存分配的動作,每個線程在Java堆中預先分配一小塊內存,稱為本地線程分配緩沖(Thread Local Allocation Buffer,TLAB)。哪個線程需要分配內存,就在哪個線程的TLAB上分配。虛擬機是否使用TLAB,可以通過-XX:+/-UseTLAB參數來設定。這么做的目的之一,也是為了并發創建一個對象時,保證創建對象的線程安全性。TLAB比較小,直接在TLAB上分配內存的方式稱為快速分配方式,而TLAB大小不夠,導致內存被分配在Eden區的內存分配方式稱為慢速分配方式。 #### 對象引用 - 強引用:如“Object?obj?=?new?Object()”,這類引用是Java程序中最普遍的。只要強引用還存在,垃圾收集器就永遠不會回收掉被引用的對象。 - 軟引用:它用來描述一些可能還有用,但并非必須的對象。在系統內存不夠用時,這類引用關聯的對象將被垃圾收集器回收。JDK1.2之后提供了SoftReference類來實現軟引用。 ~~~ SoftReference<User> softReference = new SoftReference<User>(new User()); ? strangeReference = softReference.get(); //通過get方法獲得強引用 ? ~~~ - 弱引用:被弱引用關聯的對象只能生存到下一次垃圾收集發生之前。當垃圾收集器工作時,無論當前內存是否足夠,都會回收掉只被弱引用關聯的對象。在JDK1.2之后,提供了WeakReference類來實現弱引用。jdk中的ThreadLocal就是弱引用的 ~~~ WeakReference<User> weakReference = new WeakReference<User>(new User()); ? ~~~ - 虛引用:最弱的一種引用關系,完全不會對其生存時間構成影響,也無法通過虛引用來取得一個對象實例。為一個對象設置虛引用關聯的唯一目的是希望能在這個對象被收集器回收時收到一個系統通知。JDK1.2之后提供了PhantomReference類來實現虛引用。虛引用PhantomReference<T>的聲明的借助強引用或者匿名對象,結合泛型ReferenceQueue<T>初始化,具體如下: ~~~ PhantomReference<User> phantomReference = new PhantomReference<User>(new User(),new ReferenceQueue<User>()); ? ~~~ ### 垃圾對象的判定 - 引用計數算法:給對象添加一個引用計數器,每當有一個地方引用它時,計數器值就加1,當引用失效時,計數器值就減1,計數器為0的對象就是不可能再被使用的。引用計數算法的實現簡單,判定效率也很高,Java語言并沒有選擇這種算法來進行垃圾回收,因為它很難解決對象之間的相互循環引用問題。 - 可達性分析算法:Java和C#中都是采用可達性分析算法來判定對象是否存活的。這種算法的基本思路是通過一系列名為“GC Roots”的對象作為起始點,從這些節點開始向下搜索,搜索所走過的路徑稱為引用鏈,當一個對象到GC Roots沒有任何引用鏈相連時,就證明此對象是不可用的。可作為GC Roots的對象包括下面幾種: 1. 虛擬機棧(棧幀中的本地變量表)中引用的對象。 2. 方法區中的類靜態屬性引用的對象。 3. 方法區中的常量引用的對象。 4. 本地方法棧中JNI(Native方法)的引用對象。 在可達性分析算法中,要真正宣告一個對象死亡,至少要經歷兩次標記過程:如果對象在進行可達性分析后發現沒有與GC Roots相連接的引用鏈,那它會被第一次標記并且進行一次篩選,篩選的條件是此對象是否有必要執行finalize()方法。當對象沒有覆蓋finalize()方法,或finalize()方法已經被虛擬機調用過,則沒有必要執行。 ### 方法區回收 虛擬機規范中不要求方法區一定要實現垃圾回收,而且方法區中進行垃圾回收的效率也確實比較低,但是HotSpot對方法區也是進行回收的,主要回收的是廢棄常量和無用的類兩部分。 - 廢棄常量:只要當前系統中沒有任何一處引用該常量就是廢棄常量 - 無用的類需要同時滿足以下三個條件: 1. 該類所有實例都已經被回收,Java堆中不存在該類的任何實例 2. 加載該類的ClassLoader已經被回收 3. 該類對應的java.lang.Class對象沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法 在大量使用反射、動態代理、CGLib等ByteCode框架、動態生成JSP以及OSGi這類頻繁自定義ClassLoader的場景都需要虛擬機具備類卸載功能,以保證方法區不會溢出。 ### 內存分配策略? 1. 對象優先在Eden區分配:當Eden區沒有足夠空間進行分配時,虛擬機將發起一次Minor GC 2. 大對象直接進入老年代:大對象是指需要大量連續內存空間的Java對象,比如很長的字符串以及數組,老年代發生Full GC 3. 長期存活的對象將進入老年代:如果對象在Eden區出生并且經過第一次Minor GC后任然存在,并且能被Survivor容納,則被移動到Survivor空間中,并且對象年齡+1,當對象年齡達到“-XX:MaxTenuringThreshold”設置的值(默認15)的時候,對象就會被晉升到老年代中 ### 垃圾收集算法 1. 標記-清除算法:首先標記出所有需要回收的對象,然后統一回收所有被標記的對象;缺點是效率不高且容易產生大量不連續的內存碎片, 當程序需要分配較大對象時無法找到連續內存而不得不觸發另一次垃圾收集動作。 2. 復制算法:將可用內存分為大小相等的兩塊,每次只使用其中一塊;當這一塊用完了,就將還活著的對象復制到另一塊上,然后把已使用過的內存清理掉。在HotSpot里,考慮到大部分對象存活時間很短,將內存分為Eden和兩塊Survivor,默認比例為8:1:1。代價是存在部分內存空間浪費,且可能存在空間不夠需要分配擔保的情況,所以適合在新生代使用; 3. 標記-整理算法:首先標記出所有需要回收的對象,然后讓所有存活的對象都向一端移動,然后直接清理掉端邊界以外的內存。適用于老年代。 4. 分代收集算法:一般把Java堆分新生代和老年代,在新生代用復制算法,新生代每次垃圾收集時都會有大量對象死去,只有少量存活。老年代對象存活率高、沒有額外額空間對他進行分配擔保,用標記-清理或標記-整理算法,是現代虛擬機通常采用的算法。 ### Minor GC和Full GC的區別 1. 新生代GC(Minor GC):發生在新生代的垃圾收集動作 2. 老年代GC(Major GC/Full GC):發生在老年代的垃圾收集動作,出現了Major GC,經常會伴隨至少一次的Minor GC(但并不是絕對的)。Major GC的速度一般要比Minor GC慢上10倍以上 ### 垃圾收集器 1. Serial收集器:復制算法的單線程的收集器,它只會使用一條線程去完成垃圾收集工作,進行垃圾收集時必須暫停其他線程的所有工作,直到它收集結束為止。垃圾收集的過程中會Stop The World(服務暫停)。參數控制: -XX:+UseSerialGC 串行收集器 2. ParNew收集器:ParNew收集器其實就是Serial收集器的多線程版本。新生代并行,老年代串行;新生代復制算法、老年代標記-壓縮。-XX:+UseParNewGC ParNew收集器,-XX:ParallelGCThreads 限制線程數量 3. Parallel收集器:Parallel Scavenge收集器類似ParNew收集器,Parallel收集器更關注系統的吞吐量。可以通過參數來打開自適應調節策略,虛擬機會根據當前系統的運行情況收集性能監控信息,動態調整這些參數以提供最合適的停頓時間或最大的吞吐量;也可以通過參數控制GC的時間不大于多少毫秒或者比例;新生代復制算法、老年代標記-壓縮。參數控制: -XX:+UseParallelGC 使用Parallel收集器+ 老年代串行 4. Parallel Old 收集器:Parallel Scavenge收集器的老年代版本,使用多線程和“標記-整理”算法。這個收集器是在JDK 1.6中才開始提供,參數控制: -XX:+UseParallelOldGC 使用Parallel收集器+ 老年代并行 5. CMS收集器:Concurrent Mark Sweep收集器是一種以獲取最短回收停頓時間為目標的收集器。目前很大一部分的Java應用都集中在互聯網站或B/S系統的服務端上,這類應用尤其重視服務的響應速度,希望系統停頓時間最短,以給用戶帶來較好的體驗。從名字(包含“Mark Sweep”)上就可以看出CMS收集器是基于“標記-清除”算法實現的,它的運作過程相對于前面幾種收集器來說要更復雜一些,整個過程分為4個步驟,包括: 1. 初始標記:僅僅只是標記一下GC Roots能直接關聯到的對象,速度很快,單線程,會發生Stop The World 2. 并發標記:與應用線程一起運行,是CMS最主要的工作階段,通過直達對象,掃描全部的對象,進行標記 3. 重新標記:STW,修正并發標記時由于應用程序還在并發運行產生的對象的修改,多線程,速度快,需要全局停頓 4. 并發清除:與應用線程一起運行,清理垃圾對象 優點: 并發收集、低停頓 缺點: 產生大量空間碎片、并發階段會降低吞吐量 參數控制: -XX:+UseConcMarkSweepGC 使用CMS收集器 -XX:+ UseCMSCompactAtFullCollection Full GC后,進行一次碎片整理;整理過程是獨占的,會引起停頓時間變長 -XX:+CMSFullGCsBeforeCompaction 設置進行幾次Full GC后,進行一次碎片整理 -XX:ParallelCMSThreads 設定CMS的線程數量(一般情況約等于可用CPU數量) ![](http://mmbiz.qpic.cn/mmbiz_png/PgqYrEEtEnqLYgY6g5DgUKYUPgXXTjorfdaee1XLicg5ZLjhNiajxyD8X78TBrHpnfu3cdtu30apSxDF1PhYcRrw/640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1) 6. G1收集器:G1是目前技術發展的最前沿成果之一,HotSpot開發團隊賦予它的使命是未來可以替換掉JDK1.5中發布的CMS收集器。與CMS收集器相比G1收集器有以下特點: - 空間整合:G1收集器采用標記整理算法,不會產生內存空間碎片。分配大對象時不會因為無法找到連續空間而提前觸發下一次GC。 - 可預測停頓,這是G1的另一大優勢,降低停頓時間是G1和CMS的共同關注點,但G1除了追求低停頓外,還能建立可預測的停頓時間模型,能讓使用者明確指定在一個長度為N毫秒的時間片段內,消耗在垃圾收集上的時間不得超過N毫秒,這幾乎已經是實時Java(RTSJ)的垃圾收集器的特征了。 上面提到的垃圾收集器,收集的范圍都是整個新生代或者老年代,而G1不再是這樣。使用G1收集器時,Java堆的內存布局與其他收集器有很大差別,它將整個Java堆劃分為多個大小相等的獨立區域(Region),雖然還保留有新生代和老年代的概念,但新生代和老年代不再是物理隔閡了,它們都是一部分(可以不連續)Region的集合。 ![](http://mmbiz.qpic.cn/mmbiz_jpg/PgqYrEEtEnqLYgY6g5DgUKYUPgXXTjoru5HeMdP7OnkFlDIpg71gf6utSNzcoH5E3BpzwZ9ytvFKHBxvf929Dw/640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy=1) 收集步驟: 1. 標記階段,首先初始標記(Initial-Mark),這個階段是停頓的(Stop the World Event),并且會觸發一次普通Mintor GC。對應GC log:GC pause (young) (inital-mark) 2. Root Region Scanning,程序運行過程中會回收survivor區(存活到老年代),這一過程必須在young GC之前完成。 3. Concurrent Marking,在整個堆中進行并發標記(和應用程序并發執行),此過程可能被young GC中斷。在并發標記階段,若發現區域對象中的所有對象都是垃圾,那個這個區域會被立即回收(圖中打X)。同時,并發標記過程中,會計算每個區域的對象活性(區域中存活對象的比例)。![](http://mmbiz.qpic.cn/mmbiz_png/PgqYrEEtEnqLYgY6g5DgUKYUPgXXTjorMmqL4XiaoBlyRy1ziascfFousWcicSvFxOrFN2GIIgyiagPaWljnbtMzibg/640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1) 4. Remark, 再標記,會有短暫停頓(STW)。再標記階段是用來收集 并發標記階段 產生新的垃圾(并發階段和應用程序一同運行);G1中采用了比CMS更快的初始快照算法:snapshot-at-the-beginning (SATB)。 5. Copy/Clean up,多線程清除失活對象,會有STW。G1將回收區域的存活對象拷貝到新區域,清除Remember Sets,并發清空回收區域并把它返回到空閑區域鏈表中。![](http://mmbiz.qpic.cn/mmbiz_png/PgqYrEEtEnqLYgY6g5DgUKYUPgXXTjornTUQo5RqW79icN3rDRuZ4bl8Y2GU8q7CuM73hJwQpia5Ek52DicIpUDTQ/640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1) 6. 復制/清除過程后。回收區域的活性對象已經被集中回收到深藍色和深綠色區域。![](http://mmbiz.qpic.cn/mmbiz_png/PgqYrEEtEnqLYgY6g5DgUKYUPgXXTjor8XeOMiay8P27IQnWawjlyJdvYriba2ae07zxODmvMOgHqVxCiazYnGia1g/640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1) 參考文章 1. https://mp.weixin.qq.com/s?__biz=MzI4NDY5Mjc1Mg==&mid=2247484530&idx=1&sn=149bfd8bbdcdeac0410d39ee42cc52dd&chksm=ebf6dc0ddc81551b6f258dcd060a4266dfd0eeeded6dae94a58020f927ecb1239d83633bd504&mpshare=1&scene=1&srcid=0312dWSUN8BkQxWvmwT8RDhc#rd
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看