先前有個問題待解決,“一個對象在jvm 是如何存在的”, 今天花時間在這里總結(jié)一下。
類加載, 在方法區(qū)中, class類型, 靜態(tài)變量 所有線程共享
一個對象創(chuàng)建之前,會檢查對應(yīng)的class類型是否已經(jīng)加載進來,沒有,一般經(jīng)過類的加載機制,
雙親委派模式進行加載,將Class類加載進方法區(qū)中,class文件的版本,字段,方法等類描述信息,
還要靜態(tài)變量,常量。常量池, 符號引用, 字面量, 所有線程共享
class文件中存在一些符號引用和字面量,這些信息存儲到方法區(qū)里面的常量池堆heap, 對象的創(chuàng)建, 所有線程共享
對象的創(chuàng)建,根據(jù)class類,利用構(gòu)造函數(shù)進行創(chuàng)建對象,首先在heap堆中分配內(nèi)存,
這個對象是所有線程共享。 對象所需的內(nèi)存大小在類加載完成后便可確定,
為對象分配空間的任務(wù)等同于把一塊確定大小的內(nèi)存從 Java 堆中劃分出來。
3.1. 分配方式有 “指針碰撞” 和 “空閑列表” 兩種,選擇那種分配方式由 Java 堆是否規(guī)整決定,
而Java堆是否規(guī)整又由所采用的垃圾收集器是否帶有壓縮整理功能決定。
- 指針碰撞
Java 堆內(nèi)存規(guī)整,GC 收集器的算法采用了復(fù)制算法,標(biāo)記-整理算法
- 空閑列表
Java 堆內(nèi)存不規(guī)整,GC 收集器的算法采用了標(biāo)記-清除算法
```
堆一般是垃圾收集器管理的主要區(qū)域, 一般分為 新生代與老年代,perm持久代
新生代分為Eden區(qū), from survivor(s1), to survivor(s2),
一般創(chuàng)建的對象在eden區(qū),當(dāng)eden的空間滿了,發(fā)生minor gc,
就是將eden區(qū)存活對象通過標(biāo)記-整理算法(或者其他算法)移動到s0區(qū),同時記錄對象的存活次數(shù)。
然后eden區(qū)清空了,沒有對象,為下次創(chuàng)建對象做準(zhǔn)備。
然后新的創(chuàng)建的對象又在eden區(qū),eden區(qū)又滿了,此時會發(fā)生minor gc, 然后呢,將eden區(qū)存活的對象,
和s0區(qū)存活的對象通過gc算法(復(fù)制,標(biāo)記-整理)移動到s1區(qū),同時記錄對象存活的次數(shù)。
如此反復(fù),當(dāng)存活的對象的次數(shù)大于系統(tǒng)設(shè)置的閾值,此時會在發(fā)生minor GC的時候,將對象移動到老年代中。
當(dāng)老年代的空間也滿了,此時會發(fā)生full GC, 會將所有區(qū)間無引用的對象清除
持久代在方法區(qū)method area, 這里存儲了類的描述信息,常量與符號引用
```
3.2. 為對象的屬性初始化零值
內(nèi)存分配完成后,虛擬機需要將分配到的內(nèi)存空間都初始化為零值(不包括對象頭),這一步操作保證了對象的實例字段在 Java 代碼中可以不賦初始值就直接使用,
3.3. 設(shè)置對象頭
初始化零值完成之后,虛擬機要對對象進行必要的設(shè)置,例如這個對象是那個類的實例、
如何才能找到類的元數(shù)據(jù)信息、對象的哈希嗎、對象的 GC 分代年齡等信息。
這些信息存放在對象頭中。
3.4. 執(zhí)行 init 方法
執(zhí)行對象的init方法,比如{}
創(chuàng)建類型的變量, 棧
在棧里,一個線程創(chuàng)建對應(yīng)類型的變量,同時會將對象的內(nèi)存地址指向變量,在線程棧內(nèi),對象的其他屬性值會從
堆中拷貝一份到線程棧中,線程自己定義的屬性會在自己的線程棧中。調(diào)用方法, 棧
通過類型變量(引用)調(diào)用方法,此時會在棧中創(chuàng)建本地方法幀,在方法幀中定義的local variable局部變量,在方法幀(棧)
中,通過參數(shù)傳遞進來的,會從堆中拷貝一份到方法幀中,這里涉及到傳值還是傳址。調(diào)用的過程中,會使用到程序計數(shù)器,程序計數(shù)器處理記錄指令,分支,循環(huán),跳轉(zhuǎn),異常之類的情況。
方法調(diào)用之后,會將對應(yīng)的數(shù)據(jù)與引用釋放,此時對象的應(yīng)用釋放了。
當(dāng)遇到minor GC 時, 會將無引用的對象清除。無引用的對象指的是,該對象沒有指向,該對象的屬性沒有其他引用。該對象如果沒有被清除,且該計數(shù)值大于閾值,會進入老年代中。
最后的最后, 該對象最終會被清除的。如果該對象調(diào)用了本地方法,native方法, 會在本地方法棧中分配內(nèi)存進行調(diào)用本地方法
當(dāng)所有對象實例被回收,對應(yīng)的類加載器classloader被回收, class對象沒有對應(yīng)的反射,
這些條件都滿足時,此時會在持久代(method area)進行回收class類信息。
appendix
什么是符號引用?
符號引用拆開來看,先需要符號,然后查找對應(yīng)的符號引用關(guān)系,在jvm中,class文件會轉(zhuǎn)換成一堆堆
指令,一條指令存在引用其他符號的情況,比如invokevirtual #2, 會到常量池中尋找對應(yīng)的符號。直接引用
符號引用在jvm運行時,會轉(zhuǎn)換成直接引用, 直接引用是可以在運行時直接調(diào)用,比如方法調(diào)用,
運行轉(zhuǎn)換成直接引用,這個方法需要到虛方法表中查找。
refrence
- JVM里的符號引用如何存儲?
- https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html
- https://www.zhihu.com/search?type=content&q=java%20%E5%86%85%E5%AD%98
- http://www.itdecent.cn/p/9bd5afeb2f8e
PS: 若你覺得可以、還行、過得去、甚至不太差的話,可以“關(guān)注”一下,就此謝過!