JVM - 基礎知識

關注公衆號:xy的技術圈java

如何判斷對象已死?

有兩種方法,分別爲:引用計數法和可達性分析法算法

引用計數算法

給對象中添加一個引用計數器,每當有一個地方引用它時,計數器值+1;當引用失效時,計數器值-1,任什麼時候刻計數器值爲0的對象就是不能再被使用的。數組

此方式高效簡單,但不能解決循環引用的問題。安全

可達性分析算法

經過一系列的稱爲「GC Roots」的對象做爲起始點,從這些節點開始向下搜索,搜索走過的路徑稱爲引用鏈,當一個對象到GC Roots沒有任何引用鏈相連(圖論術語:從GC Roots到這個對象是不可到達的),則此對象是不可用的。架構

若是對象在進行可達性分析後發現沒有與GC Roots相連的引用鏈,也不會當即死亡。它會暫時被標記上而且進行一次篩選,篩選的條件是是否有必要執行finalize()方法。若是被斷定有必要執行finaliza()方法,就會進入F-Queue隊列中,並有一個虛擬機自動創建的、低優先級的線程去執行它。稍後GC將對F-Queue中的對象進行第二次小規模標記。若是這時仍是沒有新的關聯出現,那基本上就真的被回收了。app

Java中可做爲GC Roots的對象包括:ide

  • 虛擬機棧(棧幀中的局部變量表)中引用的對象。
  • 方法區中類靜態屬性引用的對象。
  • 方法區中常量引用的對象。
  • 本地方法棧中JNI引用的對象。

爲何它們能夠做爲GC Roots?由於這些對象確定不會被回收。好比,虛擬機棧中是正在執行的方法,因此裏面引用的對象不會被回收。函數

JVM內存分哪幾個區,每一個區的做用是什麼?

程序計數器

指向當前線程正在執行的字節碼指令的地址(行號),線程CPU調度的最小單位(進程是資源分配的最小單位),CPU時間片是能夠被強佔的,因此要記住行號。JVM中惟一一個沒有規定任何OutOfMemoryError狀況的區域。性能

虛擬機棧

存儲當前線程運行方法所須要的數據、指令和返回地址。(單位棧幀) (局部變量表(編譯時期確認大小),操做數棧,動態連接(多態),返回地址) 。每一個方法在執行的同時都會建立一個棧幀,用於存儲局部變量表、操做數棧、動態連接、方法出口等信息。網站

每個方法從調用直至執行完成的過程,就對應着一個棧幀在虛擬機中入棧到出站的過程。

可能會拋出棧深度異常StackOverflowError)或內存溢出異常(OutOfMemoryError)

本地方法棧

與虛擬機棧相似,不過是調用native方法的棧。

方法區

保存了類信息、常量、靜態變量(static)、JIT、運行時常量池。有的人稱爲「永久代」,後更名爲「元空間」。

堆是Java對象的存儲區域,任何用new字段分配的Java對象實例和數組,都被分配在堆上,Java堆可以使用-Xms -Xmx進行內存控制,值得一提的是從JDK1.7版本以後,運行時常量池從方法區移到了堆上

什麼是類加載器,類加載器有哪些?

實現經過類的權限定名獲取該類的二進制字節流的代碼塊叫作類加載器。主要有如下四種類加載器:

啓動類加載器(Bootstrap ClassLoader):用來加載java核心類庫,沒法被java程序直接引用。

擴展類加載器(extensions class loader):它用來加載 Java 的擴展庫。Java 虛擬機的實現會提供一個擴展庫目錄。該類加載器在此目錄裏面查找並加載 Java 類。

系統類加載器(system class loader):它根據 Java 應用的類路徑(CLASSPATH)來加載 Java 類。通常來講,Java 應用的類都是由它來完成加載的。能夠經過 ClassLoader.getSystemClassLoader()來獲取它。

用戶自定義類加載器:經過繼承 java.lang.ClassLoader類的方式實現。

JDK 9以後的改變

保持三級分層類加載器架構以實現向後兼容。可是,從模塊系統加載類的方式有一些變化。JDK 9類加載器層次結構以下圖所示。

JDK9的類加載器

在JDK 9中,引導類加載器是由類庫和代碼在虛擬機中實現的。爲了向後兼容,它在程序中仍然由null表示。例如,Object.class.getClassLoader()仍然返回null。 可是,並非全部的Java SE平臺和JDK模塊都由引導類加載器加載。舉幾個例子,引導類加載器加載的模塊是java.base,java.logging,java.prefs和java.desktop。其餘Java SE平臺和JDK模塊由平臺類加載器和應用程序類加載器加載。

JDK 9中再也不支持用於指定引導類路徑,-Xbootclasspath和-Xbootclasspath/p選項以及系統屬性sun.boot.class.path。-Xbootclasspath/a選項仍然受支持,其值存儲在jdk.boot.class.path.append的系統屬性中。JDK 9再也不支持擴展機制。可是,它將擴展類加載器保留在名爲平臺類加載器的新名稱下ClassLoader類包含一個名爲getPlatformClassLoader()的靜態方法,該方法返回對平臺類加載器的引用。

平臺類加載器用於另外一目的。默認狀況下,由引導類加載器加載的類將被授予全部權限。可是,幾個類不須要全部權限。這些類在JDK 9中已經被取消了特權,而且它們被平臺類加載器加載以提升安全性。

爲何要使用雙親委派模型?

使用雙親委派模型,有一個顯而易見的好處就是Java類隨着它的類加載器一塊兒具有了一種帶有優先級的層次關係。例如類java.lang.Object,它存放在rt.jar中,不管哪一個類加載器要加載這個類,始終都要委派給啓動類加載器進行加載,所以,Object類在程序的各類類加載器環境中都是同一個類。

相反,若是沒有使用雙親委派模型,由各個類加載器自行去加載的話,假如用戶本身編寫了一個稱爲java.lang.Object的類,並放在程序的ClassPath中,那麼系統就會出現多個不一樣的Object類,那麼程序也將會變得一片混亂。(若是真的這樣作,將會發現正常編譯,但運行失敗,即便使用自定義的類加載器,強行用defaineClass()方法去加載一個以「java.lang」開頭的類也不會成功,若是這樣作,將會收到一個由虛擬機本身拋出的「java.lang.SecurityException:Prohibited package name:java.lang」)

如何破壞雙親委派模型?

這並不是是不可能的事情,一個典型的例子即是JNDI服務,它的代碼由啓動類加載器去加載(在JDK1.3時放進rt.jar),但JNDI的目的就是對資源進行集中管理和查找,它須要調用獨立廠商實現部署在應用程序的classpath下的JNDI接口提供者(SPI, Service Provider Interface)的代碼,但啓動類加載器不可能「認識」之些代碼,該怎麼辦?

爲了解決這個困境,Java設計團隊只好引入了一個不太優雅的設計:線程上下文件類加載器(Thread Context ClassLoader)。這個類加載器能夠經過java.lang.Thread類的setContextClassLoader()方法進行設置,若是建立線程時還未設置,它將會從父線程中繼承一個;若是在應用程序的全局範圍內都沒有設置過,那麼這個類加載器默認就是應用程序類加載器。

有了線程上下文類加載器,JNDI服務使用這個線程上下文類加載器去加載所須要的SPI代碼,也就是父類加載器請求子類加載器去完成類加載動做,這種行爲實際上就是打通了雙親委派模型的層次結構來逆向使用類加載器,已經違背了雙親委派模型,但這也是迫不得已的事情。

Java中全部涉及SPI的加載動做基本上都採用這種方式,例如JNDI,JDBC,JCE,JAXB和JBI等。

雙親委派模型的第三次「被破壞」是因爲用戶對程序的動態性的追求致使的,例如OSGi的出現。在OSGi環境下,類加載器再也不是雙親委派模型中的樹狀結構,而是進一步發展爲網狀結構。

JVM直接內存概念和異常

直接內存不是Java虛擬機規範中定義的內存區域,可是這部份內存也被頻繁地使用,並且也可能致使OutOfMemoryError異常出現。

在JDK1.4中新加入了NIO(New Input/Output)類,引入了一種基於通道(Channel)與緩衝區(Buffer)的I/O方式,它可使用native函數庫直接分配堆外內存,而後經過一個存儲在Java堆裏面的DirectByteBuffer對象做爲這塊內存的引用進行操做。這樣能在一些場景中顯著提升性能,由於避免了在Java堆和native堆中來回複製數據。

Java中垃圾收集用到的方法有哪些?

一、引用計數算法(Reference Counting)

給對象添加一個引用計數器,每當一個地方引用它時,數據器加1;當引用失效時,計數器減1;計數器爲0的便可被回收。

優勢是實現簡單,判斷效率高

缺點是很難解決對象之間的相互循環引用(objA.instance = objB; objB.instance = objA)的問題,因此java語言並無選用引用計數法管理內存

二、可達性分析算法(GC Root Tracing)

Java和C#都是使用根搜索算法來判斷對象是否存活。經過一系列的名爲「GC Root」的對象做爲起始點,從這些節點開始向下搜索,搜索全部走過的路徑稱爲引用鏈(Reference Chain),當一個對象到GC Root沒有任何引用鏈相連時(用圖論來講就是GC Root到這個對象不可達時),證實該對象是能夠被回收的。

三、標記-清除算法(Mark-Sweep)

這是垃圾收集算法中最基礎的,根據名字就能夠知道,它的思想就是標記那些要被回收的對象,而後統一回收。這種方法很簡單,可是會有兩個主要問題:

  • 效率不高,標記和清除的效率都很低;

  • 會產生大量不連續的內存碎片,致使之後程序在分配較大的對象時,因爲沒有充足的連續內存而提早觸發一次GC動做。

四、標記-整理算法(Mark-Compact)

該算法是爲了解決標記-清楚,產生大量內存碎片的問題;當對象存活率較高時,也解決了複製算法的效率問題。它的不一樣之處就是在清除對象的時候先將可回收的對象移動到一端,而後清除掉這一端邊界之外的對象,這樣就不會產生內存碎片。

五、複製算法(Copying)

爲了解決效率問題,複製算法將可用內存按容量劃分相等的兩部分,而後每次只使用其中的一塊,當第一塊內存用完時,就將還存活的對象複製到第二塊內存上,而後一次性清除完第一塊內存,再將第二塊上的對象複製到第一塊。可是這種方式,內存的代價過高,每次基本上都要浪費一塊內存。

因而將該算法進行了改進,內存區域再也不是按照1:1去劃分,而是將內存劃分爲8:1:1三部分,較大的那分內存叫Eden區,其他兩塊較小的內存叫Survior區。每次都會先使用Eden區,若Eden區滿,就將對象賦值到第二塊內存上,而後清除Eden區,若是此時存活的對象太多,以致於Survivor不夠時,會將這些對象經過分配擔保機制賦值到老年代中。(Java堆又分爲新生代和老年代)。

六、分代收集算法(Generational Collection)

根據對象的存活週期的不一樣將內存劃分爲幾塊,通常就分爲新生代和老年代,根據各個年代的特色採用不一樣的收集算法。新生代(少許存活)用複製算法,老年代(對象存活率高)「標記-清理」算法。

認真寫文章,用心作分享。

我的網站:yasinshaw.com

公衆號:xy的技術圈

相關文章
相關標籤/搜索