上一篇簡單記錄了Java class文件的格式,它以標準的二進制形式來表現Java類型。本篇咱們來看下當二進制的類型數據被導入到和Java虛擬機中時,到底會發生什麼。咱們以一個Java類型(類或接口)的生命週期(從進入虛擬機開始到最終退出)爲例來討論開始階段的裝載、鏈接和初始化,以及佔Java類型生命週期絕大部分時間的對象實例化、垃圾收集和對象finalize,而後是Java類型生命週期的結束(從虛擬機中卸載)java
Java虛擬機經過裝載
、鏈接
和初始化
一個Java類型,使該類型能夠被正在運行的Java程序所使用。網絡
裝載
---就是把二進制形式的Java類型讀入Java虛擬機中鏈接
---就是把這種已經讀入虛擬機的二進制形式的類型數據合併到虛擬機的運行時狀態中去。鏈接分爲三個子步驟:
初始化
---給類變量賦予正確的初始值。總體流程以下:數據結構
如圖所示,裝載、鏈接和初始化這三個階段必須按順序進行。惟一例外的就是鏈接階段的第三步(解析),它能夠在初始化以後再進行。app
在類和接口被裝載和鏈接的時機上,Java虛擬機規範對具體實現提供了必定的靈活性。可是規範對於初始化的時機有着嚴格的規定。全部Java虛擬機實現必須在每一個類或接口首次主動使用時初始化
。下面6種情形符合主動使用的情形。dom
除以上6種情形外,全部其餘使用 Java 類型的方式都是被動使用,它們都不會致使 Java 類型的初始化。jvm
第五條中任何一個類的初始化都要求它的超類在此以前完成初始化。然而對於接口來講,這條規則並不適用。只有在某個接口所聲明的非final字段被使用時,該接口才會被初始化。函數
首次主動使用時初始化
這個規則直接影響着類的裝載、鏈接和初始化的機制。虛擬機實現能夠自由選擇裝載、鏈接的時機。但不管如何,若是一個類型在它首次主動使用以前尚未被裝載和鏈接的話,那它必須在此時被裝載和鏈接,這樣它才能被初始化。性能
裝載動做由三個基本動做組成,要裝載一個類型,Java虛擬機必須:學習
java.lang.Class
類的實例Java虛擬機並無說Java類型的二進制數據應該怎樣產生。因此咱們能夠想象這幾種場景:ui
有了二進制數據後,Java虛擬機必須對這些數據進行足夠的處理,而後才能建立類java.lang.Class
的實例對象。而裝載步驟的最終產品就是這個Class類的實例對象,它成爲Java程序與內部數據結構之間的接口。要訪問關於該類型的信息,程序就要調用該類型對應的Class實例對象的方法。
前面講過Java類型的裝載要麼由啓動類裝載器裝載,要麼由用戶自定義的類裝載器裝載。而在裝載過程當中遇到問題,類裝載器應該在程序首次使用時報告問題。若是這個類一直沒有被程序主動使用,那麼該類裝載器不該該報告錯誤。
當類型被裝載後,就準備進行鏈接了。鏈接過程的第一步是驗證:確認類型符合Java語言的語義,而且不會危及虛擬機的完整性。
在驗證上,不一樣虛擬機的實現可能不太同樣。但虛擬機規範列出了虛擬機能夠拋出的異常以及在何種條件下必須拋出它們。
而在裝載
過程當中,也可能會作如下幾種數據檢查(雖然這些檢查在裝載期間完成,在正式的鏈接驗證階段以前進行,但在邏輯上屬於驗證階段。檢查被裝載類型是否有任何問題的過程都屬於驗證):
Object
以外的每個類都有一個超類。在大多數虛擬機的實現中,還有一種檢查每每發生在正式的驗證階段以後,那就是符號引用的驗證。
前面講過動態鏈接的過程包括經過保存在常量池彙總的符號引用查找被引用的類、接口、字段以及方法,把符號引用替換爲直接引用。
當虛擬機搜尋一個被符號引用的元素(類型、方法)時,它必須首先確認該元素存在。若是該元素存在,還要進一步檢查引用類型是否有訪問該元素的權限。這些存在性和訪問權限的檢查在邏輯上屬於鏈接的第一階段,可是每每在鏈接的第三階段解析
的時候發生。而解析
自身也可能延遲到符號引用第一次被程序使用的時候,因此這些檢查設置可能在初始化以後才進行。
任何在此以前沒有進行的檢查以及在此以後不會被檢查的項目都包含在內。
請注意,當須要查看其餘類型時,它只須要查看超類型。超類須要在子類初始化
前被初始化
,因此這些類應該已經被裝載了。而對於接口的初始化
來講,不須要父接口的初始化
,可是當子接口被裝載
時,父接口須要被裝載
(它們不會被初始化,只是被裝載
了,有些虛擬機實現也可能會進行鏈接
的操做)
CONSTANT_String_info
入口的 string_index項目必須是CONSTANT_Utf8_info
入口的索引)全部的Java虛擬機都必須設法爲它們執行的每一個方法檢驗字節碼的完整性。好比,不能由於超出了方法末尾的跳轉指令而致使虛擬機的崩潰,虛擬機必須在字節碼驗證的時候檢查出這樣的跳轉指令是非法的,從而拋出一個錯誤。
虛擬機的實現並無強求在正式的鏈接驗證階段進行字節碼驗證,因此虛擬機能夠選擇在執行每條語句的時候單獨進行驗證。然而Java虛擬機指令集設計的一個目標就是使得字節碼流可使用一個數據流分析器一次驗證,而不用在程序執行時動態驗證,對速度的提高有很大幫助。
隨着Java虛擬機裝載了一個類,並執行了一些它選擇進行的驗證後,類就能夠進入準備階段了。在準備階段,Java 虛擬機爲類變量分配內存,設置默認初始值。但在達到初始化
以前,類變量都沒有被設置爲真正的初始值(代碼裏聲明的)。準備階段是不會執行 Java 代碼的
在準備階段,虛擬機給類變量的默認初始值以下表(有木有感受跟 C 的默認數據類型很像)
類型 | 默認初始值 |
---|---|
int | 0 |
long | 0L |
short | 0 |
char | "\u0000" |
byte | 0 |
boolean | false |
reference | null |
float | 0.0f |
double | 0.0d |
Java 虛擬機一般把 boolean 實現爲一個 int,會被默認賦值爲0(對應 false)
在準備階段,Java 虛擬機實現可能也爲一些數據結構分配內存,目的是爲了提升運行程序的性能。這種數據結構好比方法表,它包含指向類中每個方法(包括從父類繼承的方法)的指針。方法表能夠在執行繼承的方法時不須要搜索父類。
Java 類型通過驗證和準備以後,就能夠進入解析階段了。解析的過程就是在類型的常量池中尋找類、接口、字段和方法的符號引用,並把這些符號引用轉換成直接引用的過程。
本篇要從宏觀角度看待生命週期,解析過程先簡單描述下,後面作詳細介紹
爲了準備讓一個類或接口被首次主動使用,最後一個步驟就是初始化。初始化就是爲類變量賦予正確的初始值(就是代碼裏指定的數值)。
在 Java 代碼中,一個正確的初始值是經過類變量初始化語句或者靜態初始化語句給出的。
類變量初始化語句(組成:=、表達式):
class ExampleA{
static int size = (int) (3 * Math.random());
}
複製代碼
靜態初始化語句(組成:static 代碼塊):
class ExampleB{
static int size ;
static {
size = (int) (3 * Math.random());
}
}
複製代碼
全部的類變量初始化語句和類型的靜態初始化器都被 Java 編譯器收集在一塊兒,放到一個特殊的方法中。在類和接口的 class 文件中,這個方法被稱爲<clinit>
。一般的 Java 程序方法是沒法調用這個<clinit>
方法的。這種方法只能被虛擬機調用,專門用來把類型的靜態變量設置爲它們的正確值。
初始化一個類包含兩個步驟:
而接口的初始化並不須要初始化它的父接口;若是接口存在一個初始化方法的話,就執行此方法。
<clinit>
方法的代碼並不會顯式調用超類的<clinit>
。在Java虛擬機調用類的<clinit>
方法以前,它必須確認超類的<clinit>
方法已經被執行了。
<clinit>
方法前面講到Java 編譯器把類變量初始化語句和靜態初始化代碼塊都放到 class 文件的<clinit>
方法中,順序就按照在類或接口中聲明的順序。
以下類:
class ExampleB{
static int width ;
static int height = 4 * ExampleA.random();
static {
width = 9 * ExampleA.random() + 10;
}
}
複製代碼
Java 編譯器生成了以下<clinit>
方法
static <clinit>()V
L0
LINENUMBER 14 L0
ICONST_4
INVOKESTATIC hua/lee/jvm/ExampleA.random ()I
IMUL
PUTSTATIC hua/lee/jvm/ExampleB.height : I
L1
LINENUMBER 16 L1
BIPUSH 9
INVOKESTATIC hua/lee/jvm/ExampleA.random ()I
IMUL
BIPUSH 10
IADD
PUTSTATIC hua/lee/jvm/ExampleB.width : I
L2
LINENUMBER 17 L2
RETURN
MAXSTACK = 2
MAXLOCALS = 0
複製代碼
並不是全部的類編譯後都存在<clinit>
方法:
<clinit>
方法。<clinit>
方法。<clinit>
方法。下面的類是不會執行<clinit>
方法的。
class ExampleB{
static final int angle = 10;
static final int length = angle * 2;
}
複製代碼
ExampleB 聲明瞭兩個常量angle
和 length
,並經過表達式賦予了初始值,這些表達式是編譯時常量。編譯器知道angle
表示十、length
表示20。因此在ExampleB被裝載時,angle
和 length
並無做爲類變量保存在方法區中,它們是常量,被編譯器特殊處理了。
angle
和 length
做爲常量,Java 虛擬機在使用它們的全部類的常量池或者字節碼流中直接存放的是它們表示的int 數值。好比,若是一個類A
使用了 Example 的angle
字段,在編譯時虛擬機不會在類 A
的常量池中保存一個指向Example類 angle
的符號引用,而是直接在類 A
的字節碼流中嵌入一個值10
。若是angle
的常量值超過了 short
範圍限制,好比 angle=40000
,那麼類會將它保存在常量池的 CONSTANT_Integer中,值爲40000。
而對於接口來講,咱們能夠關注下面這個代碼
interface ExampleI{
int ketch = 9;
int mus = (int) (Math.random()*10);
}
複製代碼
編譯後的<clinit>
方法爲
static <clinit>()V
L0
LINENUMBER 22 L0
INVOKESTATIC java/lang/Math.random ()D
LDC 10.0
DMUL
D2I
PUTSTATIC hua/lee/jvm/ExampleI.mus : I
RETURN
MAXSTACK = 4
MAXLOCALS = 0
複製代碼
請注意,只有mus
被<clinit>
初始化了。由於ketch
字段被初始化爲了一個編譯時常量,被編譯器特殊處理了。和類中的邏輯同樣,對於其餘引用到mus
的類,編譯器會保存指向這個字段的符號引用;而對於引用ketch
的類,會在編譯時替換爲常量值。
主動使用
和被動使用
前面說過,Java 虛擬機在首次使用類型時初始化它們。只有6種活動被認爲是主動使用:
使用一個非 final 的靜態字段只有當類或者接口明確聲明了這個字段時纔是主動使用。好比,父類種聲明的字段可能會被子類引用;接口中聲明的字段可能會被實現者引用。對於子類、子接口和接口實現類來講,這就是被動使用。而被動調用並不會觸發子類(調用字段的類)的初始化。
示例:
class NewParent{
static int hoursOfSleep = (int) (Math.random() * 3);
static{
System.out.println("NewParent was initialized.");
}
}
class NewKid extends NewParent{
static int hoursOfCrying = 6+(int) (Math.random() * 2);
static{
System.out.println("NewKid was initialized.");
}
}
public class Test {
public static void main(String[] args) {
int h = NewKid.hoursOfSleep;
System.out.println(h);
}
static{
System.out.println("MethodTest was initialized.");
}
}
複製代碼
輸出以下:
MethodTest was initialized.
NewParent was initialized.
2
複製代碼
從log看,執行Test
的main
方法只會致使Test
和NewParent
的初始化,NewKid
沒有被初始化。
若是一個字段既是static
的又是final
的,而且使用一個編譯時常量表達式初始化,使用這樣的字段,也不是對聲明該字段類的主動使用。
看下面的代碼:
interface Angry {
String greeting = "Grrrr!";//常量表達式
int angerLevel = Dog.getAngerLevel();//很是量表達式,會打包進<clinit>方法,並且調用DOG的靜態方法,主動使用。
}
class Dog{
static final String greeting = "woof woof world";
static{
System.out.println("Dog was initialized");
}
/** * 靜態方法 */
static int getAngerLevel(){
System.out.println("Angry was initialized");
return 1;
}
}
class Example01{
public static void main(String[] args) {
System.out.println(Angry.greeting);
System.out.println(Dog.greeting);
}
static{
System.out.println("Example was initialized");
}
}
class Example02{
public static void main(String[] args) {
System.out.println(Angry.angerLevel);
}
static{
System.out.println("Example was initialized");
}
}
複製代碼
Example01 只是引用了靜態常量(常量表達式形式的初始化)Angry.greeting
和Dog.greeting
,因此編譯時已經替換爲了實際數值,不屬於主動使用,不會初始化對應的類。
Example01 的輸出:
Example01 was initialized
Grrrr!
woof woof world
複製代碼
Example02引用了Angry.angerLevel
,雖然是靜態常量,可是是經過方法調用的方式Dog.getAngerLevel()
初始化數值,屬於主動使用Angry
。而Angry
調用了Dog
的靜態方法getAngerLevel()
,屬於主動使用Dog
。
Example02 的輸出:
Example02 was initialized
Dog was initialized
Angry was initialized
1
woof woof world
複製代碼
一旦一個類被裝載
、鏈接
和初始化
。它就隨時可用了。程序訪問它的靜態字段,調用它的靜態方法,或者建立它的實例。
在Java程序中,類能夠被明確或者隱含的實例化。明確的實例化一個類有四種途徑:
new
操做符Class
或java.lang.reflect.Constructor
對象的newInstance()
clone()
方法java.io.ObjectInputStream
類的getObject()
方法反序列化另外存在下面幾種隱含實例化類的方式:
以下代碼示例:
class Example{
public static void main(String[] args) {
if (args.length != 2){
System.out.println("illegal args");
return;
}
System.out.println(args[0] + args[1]);
}
static{
System.out.println("Example was initialized");
}
}
複製代碼
字節碼內容:
public static main([Ljava/lang/String;)V L0 LINENUMBER 20 L0 ALOAD 0 ARRAYLENGTH ICONST_2 IF_ICMPEQ L1 L2 LINENUMBER 21 L2 GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
LDC "illegal args"
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L3
LINENUMBER 22 L3
RETURN
L1
LINENUMBER 24 L1
FRAME SAME
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
NEW java/lang/StringBuilder
DUP
INVOKESPECIAL java/lang/StringBuilder.<init> ()V
ALOAD 0
ICONST_0
AALOAD
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
ALOAD 0
ICONST_1
AALOAD
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L4
LINENUMBER 25 L4
RETURN
L5
LOCALVARIABLE args [Ljava/lang/String; L0 L5 0
MAXSTACK = 4
MAXLOCALS = 1
複製代碼
請關注 args[0] + args[1]
,編譯器會建立StringBuilder
實例,經過 StringBuilder.append
鏈接,再經過 StringBuilder.toString
轉成 String
對象
當 Java 虛擬機建立一個類的新實例時,無論明確的仍是隱含的,首先都須要在堆中爲保存對象的實例變量分配內存,包括在當前類和它的超類中所聲明的變量。一旦虛擬機爲新的對象準備好了堆內存,它當即把實例變量初始化爲默認初始值(虛擬機默認值)。隨後纔會爲實例變量賦予正確的初始值(碼農指望的)。
Java 編譯器爲它編譯的每個類都至少生成一個實例初始化方法(構造函數)。在 Java class文件種,實例初始化方法被稱爲<init>
。針對源碼中每個類的構造方法,Java 編譯器都產生一個對應的<init>
方法。若是類沒有明確聲明構造方法,編譯器默認產生一個無參構造方法。
構造方法代碼示例:
class ExampleCons{
private int width = 3;
public ExampleCons() {
this(1);
System.out.println("ExampleCons(),width = " + width);
}
public ExampleCons(int width) {
this.width = width;
System.out.println("ExampleCons(int),width = " + width);
}
public ExampleCons(String msg) {
super();
System.out.println("ExampleCons(String),width = " + width);
System.out.println(msg);
}
public static void main(String[] args) {
String msg = "Test Constructor MSG";
ExampleCons one = new ExampleCons();
ExampleCons two = new ExampleCons(2);
ExampleCons three = new ExampleCons(msg);
}
}
複製代碼
控制檯輸出:
ExampleCons(int),width = 1
ExampleCons(),width = 1
ExampleCons(int),width = 2
ExampleCons(String),width = 3
Test Constructor MSG
複製代碼
而從字節碼上看,全部的<init>
方法都默認執行了父類的無參構造方法:
// class version 52.0 (52)
// access flags 0x20
class hua/lee/jvm/ExampleCons {
// compiled from: Angry.java
// access flags 0x2
private I width
// access flags 0x1
public <init>()V
L0
LINENUMBER 39 L0
ALOAD 0
ICONST_1
INVOKESPECIAL hua/lee/jvm/ExampleCons.<init> (I)V
L1
LINENUMBER 40 L1
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
NEW java/lang/StringBuilder
DUP
INVOKESPECIAL java/lang/StringBuilder.<init> ()V
LDC "ExampleCons(),width = "
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
ALOAD 0
GETFIELD hua/lee/jvm/ExampleCons.width : I
INVOKEVIRTUAL java/lang/StringBuilder.append (I)Ljava/lang/StringBuilder;
INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L2
LINENUMBER 41 L2
RETURN
L3
LOCALVARIABLE this Lhua/lee/jvm/ExampleCons; L0 L3 0
MAXSTACK = 3
MAXLOCALS = 1
// access flags 0x1
public <init>(I)V
L0
LINENUMBER 43 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
L1
LINENUMBER 36 L1
ALOAD 0
ICONST_3
PUTFIELD hua/lee/jvm/ExampleCons.width : I
L2
LINENUMBER 44 L2
ALOAD 0
ILOAD 1
PUTFIELD hua/lee/jvm/ExampleCons.width : I
L3
LINENUMBER 45 L3
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
NEW java/lang/StringBuilder
DUP
INVOKESPECIAL java/lang/StringBuilder.<init> ()V
LDC "ExampleCons(int),width = "
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
ILOAD 1
INVOKEVIRTUAL java/lang/StringBuilder.append (I)Ljava/lang/StringBuilder;
INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L4
LINENUMBER 46 L4
RETURN
L5
LOCALVARIABLE this Lhua/lee/jvm/ExampleCons; L0 L5 0
LOCALVARIABLE width I L0 L5 1
MAXSTACK = 3
MAXLOCALS = 2
// access flags 0x1
public <init>(Ljava/lang/String;)V
L0
LINENUMBER 48 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
L1
LINENUMBER 36 L1
ALOAD 0
ICONST_3
PUTFIELD hua/lee/jvm/ExampleCons.width : I
L2
LINENUMBER 49 L2
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
NEW java/lang/StringBuilder
DUP
INVOKESPECIAL java/lang/StringBuilder.<init> ()V
LDC "ExampleCons(String),width = "
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
ALOAD 0
GETFIELD hua/lee/jvm/ExampleCons.width : I
INVOKEVIRTUAL java/lang/StringBuilder.append (I)Ljava/lang/StringBuilder;
INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L3
LINENUMBER 50 L3
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
ALOAD 1
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L4
LINENUMBER 51 L4
RETURN
L5
LOCALVARIABLE this Lhua/lee/jvm/ExampleCons; L0 L5 0
LOCALVARIABLE msg Ljava/lang/String; L0 L5 1
MAXSTACK = 3
MAXLOCALS = 2
// access flags 0x9
public static main([Ljava/lang/String;)V L0 LINENUMBER 54 L0 LDC "Test Constructor MSG" ASTORE 1 L1 LINENUMBER 55 L1 NEW hua/lee/jvm/ExampleCons DUP INVOKESPECIAL hua/lee/jvm/ExampleCons.<init> ()V ASTORE 2 L2 LINENUMBER 56 L2 NEW hua/lee/jvm/ExampleCons DUP ICONST_2 INVOKESPECIAL hua/lee/jvm/ExampleCons.<init> (I)V ASTORE 3 L3 LINENUMBER 57 L3 NEW hua/lee/jvm/ExampleCons DUP ALOAD 1 INVOKESPECIAL hua/lee/jvm/ExampleCons.<init> (Ljava/lang/String;)V ASTORE 4 L4 LINENUMBER 58 L4 RETURN L5 LOCALVARIABLE args [Ljava/lang/String; L0 L5 0
LOCALVARIABLE msg Ljava/lang/String; L1 L5 1
LOCALVARIABLE one Lhua/lee/jvm/ExampleCons; L2 L5 2
LOCALVARIABLE two Lhua/lee/jvm/ExampleCons; L3 L5 3
LOCALVARIABLE three Lhua/lee/jvm/ExampleCons; L4 L5 4
MAXSTACK = 3
MAXLOCALS = 5
}
複製代碼
對於Java程序來講,程序能夠明確或隱含的爲對象分配內存,可是不能明確的釋放內存。咱們前面講過,Java 虛擬機的實現應該具備某種自動堆儲存管理策略,而大部分採用垃圾收集器。當一個對象再也不被程序所引用了,虛擬機必須回收那部份內存。
若是類聲明瞭一個fianlize()
的方法,垃圾回收器會在釋放這個實例佔據的內存空間前執行這個方法。而垃圾收集器針對一個對象只會調用一次fianlize()
。若是執行fianlize()
期間對象被從新引用(復活了),隨後又不被引用,垃圾收集器也不會再次執行fianlize()
方法。
垃圾收集器篇幅較長,本篇還是以生命週期爲主線,後面詳細記錄
Java 類的生命週期和對象的生命週期很像。
類的垃圾收集和卸載之因此在虛擬機種很重要,是由於 Java 程序能夠在運行時經過用戶自定義的類裝載器裝載類型來動態地擴展程序。多有被裝載嗯類型都在方法區佔據內存空間。若是Java 程序持續經過用戶自定義的類裝載器裝載類型,方法區的內存足跡就會不斷增加。若是某些動態裝載的類型只是臨時須要,當他們再也不被引用以後,佔據的空間能夠經過卸載類型而釋放。
使用啓動類裝載器裝載的類型永遠是可觸及的,因此永遠不會被卸載。只有使用用戶自定義的類裝載器裝載的類型纔會變成不可觸及的。
判斷動態裝載的類型的 Class 實例是否能夠觸及有兩種方式:
對於可觸及,能夠看下面這個類,Java 虛擬機須要創建起一個完整的引用(觸及)鏈。
class MyThread extends Thread implements Cloneable{
}
複製代碼
引用關係圖形化描述以下:
從可觸及的MyThread
對象指針開始,垃圾收集器跟隨一個指向MyThread
類型數據的指針,它找到了:
從 Cloneable 的類型數據開始,垃圾收集找到了:
從 Thread 的類型數據開始,垃圾收集器找到了:
從 Runnable 的類型數據中,垃圾收集器找到了:
從 Object 的類型數據中,垃圾收集器找到了:
能夠看到從一個 MyThread 對象開始,垃圾收集器能夠觸及和 MyThread 有關的全部信息。 PS:設計真滴是優秀啊
Java 類型和對象的生命週期就學習到這裏。在類鏈接
和垃圾回收
部分只是作了簡單介紹,後面會補上。
下面一篇詳細介紹類的鏈接
過程-鏈接模型