最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

Java基礎(chǔ)-class

2023-04-14 21:11 作者:明厚-H  | 我要投稿

本文同步于我的個人博客 ->>?https://blog.hehouhui.cn/archives/43? 點擊前往查看更多內(nèi)容

對象,類

類加載

一個類的完整生命周期如下:

Class 文件需要加載到虛擬機中之后才能運行和使用,那么虛擬機是如何加載這些 Class 文件呢?

系統(tǒng)加載 Class 類型的文件主要三步:加載->連接->初始化。連接過程又可分為三步:驗證->準備->解析。

加載

類加載過程的第一步,主要完成下面3件事情:

  1. 通過全類名獲取定義此類的二進制字節(jié)流

  2. 將字節(jié)流所代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)換為方法區(qū)的運行時數(shù)據(jù)結(jié)構(gòu)

  3. 在內(nèi)存中生成一個代表該類的 Class 對象,作為方法區(qū)這些數(shù)據(jù)的訪問入口

虛擬機規(guī)范上面這3點并不具體,因此是非常靈活的。比如:“通過全類名獲取定義此類的二進制字節(jié)流” 并沒有指明具體從哪里獲取、怎樣獲取。比如:比較常見的就是從 ZIP 包中讀取(日后出現(xiàn)的JAR、EAR、WAR格式的基礎(chǔ))、其他文件生成(典型應(yīng)用就是JSP)等等。

一個非數(shù)組類的加載階段(加載階段獲取類的二進制字節(jié)流的動作)是可控性最強的階段,這一步我們可以去完成還可以自定義類加載器去控制字節(jié)流的獲取方式(重寫一個類加載器的 loadClass() 方法)。數(shù)組類型不通過類加載器創(chuàng)建,它由 Java 虛擬機直接創(chuàng)建。

類加載器、雙親委派模型也是非常重要的知識點,這部分內(nèi)容會在后面的文章中單獨介紹到。

加載階段和連接階段的部分內(nèi)容是交叉進行的,加載階段尚未結(jié)束,連接階段可能就已經(jīng)開始了。

驗證

準備

準備階段是正式為類變量分配內(nèi)存并設(shè)置類變量初始值的階段,這些內(nèi)存都將在方法區(qū)中分配。對于該階段有以下幾點需要注意:

  1. 這時候進行內(nèi)存分配的僅包括類變量(static),而不包括實例變量,實例變量會在對象實例化時隨著對象一塊分配在 Java 堆中。

  2. 這里所設(shè)置的初始值"通常情況"下是數(shù)據(jù)類型默認的零值(如0、0L、null、false等),比如我們定義了public static int value=111 ,那么 value 變量在準備階段的初始值就是 0 而不是111(初始化階段才會賦值)。特殊情況:比如給 value 變量加上了 fianl 關(guān)鍵字public static final int value=111 ,那么準備階段 value 的值就被賦值為 111。

基本數(shù)據(jù)類型的零值:

解析

解析階段是虛擬機將常量池內(nèi)的符號引用替換為直接引用的過程。解析動作主要針對類或接口、字段、類方法、接口方法、方法類型、方法句柄和調(diào)用限定符7類符號引用進行。

符號引用就是一組符號來描述目標,可以是任何字面量。直接引用就是直接指向目標的指針、相對偏移量或一個間接定位到目標的句柄。在程序?qū)嶋H運行時,只有符號引用是不夠的,舉個例子:在程序執(zhí)行方法時,系統(tǒng)需要明確知道這個方法所在的位置。Java 虛擬機為每個類都準備了一張方法表來存放類中所有的方法。當需要調(diào)用一個類的方法的時候,只要知道這個方法在方發(fā)表中的偏移量就可以直接調(diào)用該方法了。通過解析操作符號引用就可以直接轉(zhuǎn)變?yōu)槟繕朔椒ㄔ陬愔蟹椒ū淼奈恢?,從而使得方法可以被調(diào)用。

綜上,解析階段是虛擬機將常量池內(nèi)的符號引用替換為直接引用的過程,也就是得到類或者字段、方法在內(nèi)存中的指針或者偏移量。

初始化

初始化是類加載的最后一步,也是真正執(zhí)行類中定義的 Java 程序代碼(字節(jié)碼),初始化階段是執(zhí)行初始化方法 ?()方法的過程。

對于() 方法的調(diào)用,虛擬機會自己確保其在多線程環(huán)境中的安全性。因為 () 方法是帶鎖線程安全,所以在多線程環(huán)境下進行類初始化的話可能會引起死鎖,并且這種死鎖很難被發(fā)現(xiàn)。

對于初始化階段,虛擬機嚴格規(guī)范了有且只有5種情況下,必須對類進行初始化(只有主動去使用類才會初始化類):

  1. 當遇到 new 、 getstatic、putstatic或invokestatic 這4條直接碼指令時,比如 new 一個類,讀取一個靜態(tài)字段(未被 final 修飾)、或調(diào)用一個類的靜態(tài)方法時。

    • 當jvm執(zhí)行new指令時會初始化類。即當程序創(chuàng)建一個類的實例對象。

    • 當jvm執(zhí)行g(shù)etstatic指令時會初始化類。即程序訪問類的靜態(tài)變量(不是靜態(tài)常量,常量會被加載到運行時常量池)。

    • 當jvm執(zhí)行putstatic指令時會初始化類。即程序給類的靜態(tài)變量賦值。

    • 當jvm執(zhí)行invokestatic指令時會初始化類。即程序調(diào)用類的靜態(tài)方法。

  2. 使用 java.lang.reflect 包的方法對類進行反射調(diào)用時如Class.forname("…"),newInstance()等等。 ,如果類沒初始化,需要觸發(fā)其初始化。

  3. 初始化一個類,如果其父類還未初始化,則先觸發(fā)該父類的初始化。

  4. 當虛擬機啟動時,用戶需要定義一個要執(zhí)行的主類 (包含 main 方法的那個類),虛擬機會先初始化這個類。

  5. MethodHandle和VarHandle可以看作是輕量級的反射調(diào)用機制,而要想使用這2個調(diào)用, 就必須先使用findStaticVarHandle來初始化要調(diào)用的類。

  6. 「補充,來自issue745」 當一個接口中定義了JDK8新加入的默認方法(被default關(guān)鍵字修飾的接口方法)時,如果有這個接口的實現(xiàn)類發(fā)生了初始化,那該接口要在其之前被初始化。

卸載

卸載這部分內(nèi)容來自 issue#662由 guang19 補充完善。

卸載類即該類的Class對象被GC。

卸載類需要滿足3個要求:

  1. 該類的所有的實例對象都已被GC,也就是說堆不存在該類的實例對象。

  2. 該類沒有在其他任何地方被引用

  3. 該類的類加載器的實例已被GC

所以,在JVM生命周期類,由jvm自帶的類加載器加載的類是不會被卸載的。但是由我們自定義的類加載器加載的類是可能被卸載的。

只要想通一點就好了,jdk自帶的BootstrapClassLoader,ExtClassLoader,AppClassLoader負責加載jdk提供的類,所以它們(類加載器的實例)肯定不會被回收。而我們自定義的類加載器的實例是可以被回收的,所以使用我們自定義加載器加載的類是可以被卸載掉的。

對象的創(chuàng)建

下圖便是 Java 對象的創(chuàng)建過程,我建議最好是能默寫出來,并且要掌握每一步在做什么。

類加載檢查

虛擬機遇到一條 new 指令時,首先將去檢查這個指令的參數(shù)是否能在常量池中定位到這個類的符號引用,并且檢查這個符號引用代表的類是否已被加載過、解析和初始化過。如果沒有,那必須先執(zhí)行相應(yīng)的類加載過程。

分配內(nèi)存

在類加載檢查通過后,接下來虛擬機將為新生對象分配內(nèi)存。對象所需的內(nèi)存大小在類加載完成后便可確定,為對象分配空間的任務(wù)等同于把一塊確定大小的內(nèi)存從 Java 堆中劃分出來。分配方式有 “指針碰撞” 和 “空閑列表” 兩種,選擇哪種分配方式由 Java 堆是否規(guī)整決定,而 Java 堆是否規(guī)整又由所采用的垃圾收集器是否帶有壓縮整理功能決定。

內(nèi)存分配的兩種方式:(補充內(nèi)容,需要掌握)

選擇以上兩種方式中的哪一種,取決于 Java 堆內(nèi)存是否規(guī)整。而 Java 堆內(nèi)存是否規(guī)整,取決于 GC 收集器的算法是"標記-清除",還是"標記-整理"(也稱作"標記-壓縮"),值得注意的是,復(fù)制算法內(nèi)存也是規(guī)整的

內(nèi)存分配并發(fā)問題(補充內(nèi)容,需要掌握)

在創(chuàng)建對象的時候有一個很重要的問題,就是線程安全,因為在實際開發(fā)過程中,創(chuàng)建對象是很頻繁的事情,作為虛擬機來說,必須要保證線程是安全的,通常來講,虛擬機采用兩種方式來保證線程安全:

  • CAS+失敗重試: CAS 是樂觀鎖的一種實現(xiàn)方式。所謂樂觀鎖就是,每次不加鎖而是假設(shè)沒有沖突而去完成某項操作,如果因為沖突失敗就重試,直到成功為止。虛擬機采用 CAS 配上失敗重試的方式保證更新操作的原子性。

  • TLAB: 為每一個線程預(yù)先在 Eden 區(qū)分配一塊兒內(nèi)存,JVM 在給線程中的對象分配內(nèi)存時,首先在 TLAB 分配,當對象大于 TLAB 中的剩余內(nèi)存或 TLAB 的內(nèi)存已用盡時,再采用上述的 CAS 進行內(nèi)存分配

初始化零值

內(nèi)存分配完成后,虛擬機需要將分配到的內(nèi)存空間都初始化為零值(不包括對象頭),這一步操作保證了對象的實例字段在 Java 代碼中可以不賦初始值就直接使用,程序能訪問到這些字段的數(shù)據(jù)類型所對應(yīng)的零值。

設(shè)置對象頭

初始化零值完成之后,虛擬機要對對象進行必要的設(shè)置,例如這個對象是哪個類的實例、如何才能找到類的元數(shù)據(jù)信息、對象的哈希碼、對象的 GC 分代年齡等信息。 這些信息存放在對象頭中。 另外,根據(jù)虛擬機當前運行狀態(tài)的不同,如是否啟用偏向鎖等,對象頭會有不同的設(shè)置方式。

執(zhí)行init方法

在上面工作都完成之后,從虛擬機的視角來看,一個新的對象已經(jīng)產(chǎn)生了,但從 Java 程序的視角來看,對象創(chuàng)建才剛開始,方法還沒有執(zhí)行,所有的字段都還為零。所以一般來說,執(zhí)行 new 指令之后會接著執(zhí)行 方法,把對象按照程序員的意愿進行初始化,這樣一個真正可用的對象才算完全產(chǎn)生出來。

對象的內(nèi)存區(qū)域

在 Hotspot 虛擬機中,對象在內(nèi)存中的布局可以分為 3 塊區(qū)域:對象頭、實例數(shù)據(jù)對齊填充。

Hotspot 虛擬機的對象頭包括兩部分信息第一部分用于存儲對象自身的運行時數(shù)據(jù)(哈希碼、GC 分代年齡、鎖狀態(tài)標志等等),另一部分是類型指針,即對象指向它的類元數(shù)據(jù)的指針,虛擬機通過這個指針來確定這個對象是那個類的實例。

實例數(shù)據(jù)部分是對象真正存儲的有效信息,也是在程序中所定義的各種類型的字段內(nèi)容。

對齊填充部分不是必然存在的,也沒有什么特別的含義,僅僅起占位作用。 因為 Hotspot 虛擬機的自動內(nèi)存管理系統(tǒng)要求對象起始地址必須是 8 字節(jié)的整數(shù)倍,換句話說就是對象的大小必須是 8 字節(jié)的整數(shù)倍。而對象頭部分正好是 8 字節(jié)的倍數(shù)(1 倍或 2 倍),因此,當對象實例數(shù)據(jù)部分沒有對齊時,就需要通過對齊填充來補全。

對象的訪問定位

建立對象就是為了使用對象,我們的 Java 程序通過棧上的 reference 數(shù)據(jù)來操作堆上的具體對象。對象的訪問方式由虛擬機實現(xiàn)而定,目前主流的訪問方式有①使用句柄②直接指針兩種:

  1. 句柄: 如果使用句柄的話,那么 Java 堆中將會劃分出一塊內(nèi)存來作為句柄池,reference 中存儲的就是對象的句柄地址,而句柄中包含了對象實例數(shù)據(jù)與類型數(shù)據(jù)各自的具體地址信息;

  1. 直接指針: 如果使用直接指針訪問,那么 Java 堆對象的布局中就必須考慮如何放置訪問類型數(shù)據(jù)的相關(guān)信息,而 reference 中存儲的直接就是對象的地址。

這兩種對象訪問方式各有優(yōu)勢。使用句柄來訪問的最大好處是 reference 中存儲的是穩(wěn)定的句柄地址,在對象被移動時只會改變句柄中的實例數(shù)據(jù)指針,而 reference 本身不需要修改。使用直接指針訪問方式最大的好處就是速度快,它節(jié)省了一次指針定位的時間開銷。

本文來源?https://blog.hehouhui.cn/archives/43?


Java基礎(chǔ)-class的評論 (共 條)

分享到微博請遵守國家法律
金平| 砚山县| 房产| 潜山县| 大田县| 巧家县| 二连浩特市| 丘北县| 金塔县| 巍山| 阿克| 观塘区| 福贡县| 新乐市| 江阴市| 永登县| 濮阳县| 常山县| 资源县| 曲松县| 泰顺县| 台中市| 邓州市| 台湾省| 呼伦贝尔市| 杭州市| 山西省| 嘉善县| 蒙自县| 汝城县| 景谷| 巫山县| 遂昌县| 乌什县| 思南县| 武清区| 甘孜| 肇庆市| 合川市| 贵港市| 宝应县|