自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

JVM源碼分析-對(duì)象的創(chuàng)建過(guò)程

開(kāi)發(fā) 后端
我們使用main函數(shù)創(chuàng)建School的一個(gè)對(duì)象,那么這個(gè)過(guò)程發(fā)生了哪些事情?在JVM內(nèi)存中多了什么呢?讓我們一起看下吧!

[[386820]]

在開(kāi)始MySQL的學(xué)習(xí)之前,還想寫(xiě)一篇文章把前面學(xué)習(xí)的知識(shí)點(diǎn)回顧一下,就有了今天的這篇文章。

示例

有類(lèi)School,這個(gè)類(lèi)中有3個(gè)成員變量:引用類(lèi)型String類(lèi)型的schoolName,通過(guò)顯式代碼塊初始化;基本數(shù)據(jù)類(lèi)型int型studentsNum,顯式初始化;引用類(lèi)型Class類(lèi)型student,通過(guò)School的構(gòu)造函數(shù)初始化。

我們使用main函數(shù)創(chuàng)建School的一個(gè)對(duì)象,那么這個(gè)過(guò)程發(fā)生了哪些事情?在JVM內(nèi)存中多了什么呢?讓我們一起看下吧!

  1. public class School { 
  2.     private String schoolName; 
  3.     private int studentsNum = 10000; 
  4.     private Student student; 
  5.  
  6.     { 
  7.         schoolName = "清華大學(xué)"
  8.     } 
  9.  
  10.     public School(){ 
  11.         student = new Student(); 
  12.     } 
  13.  
  14. class Student{ 
  15.  
  16.  
  17. class Test{ 
  18.     public static void main(String[] args) { 
  19.         School school = new School(); 
  20.     } 

 當(dāng)我們執(zhí)行new School()時(shí),進(jìn)行了對(duì)象的創(chuàng)建,大致可以分為以下5步:

在詳細(xì)了解這5個(gè)步驟之前我們?cè)僭敿?xì)聊一下對(duì)象頭,在synchronized鎖升級(jí)過(guò)程分析的時(shí)候我們已經(jīng)初步接觸過(guò)它。

對(duì)象的內(nèi)存布局

對(duì)象在堆空間的內(nèi)存布局包含了3個(gè)部分:對(duì)象頭(Header)、實(shí)例數(shù)據(jù)(Intance Data)、對(duì)齊填充(Padding)。

對(duì)象頭

對(duì)象頭包含了兩部分:運(yùn)行時(shí)元數(shù)據(jù)、指向類(lèi)元數(shù)據(jù)的指針kclass,確認(rèn)這個(gè)對(duì)象所屬的類(lèi)型。

運(yùn)行時(shí)元數(shù)據(jù)(Mark Word)包含:哈希值、GC分代年齡、鎖狀態(tài)標(biāo)志位、偏向線程ID。運(yùn)行時(shí)元數(shù)據(jù)的信息是變化的,在synchronized鎖的升級(jí)過(guò)程中,Mark Word在不同的鎖狀態(tài)下是不一樣的。

下圖展示展示了無(wú)鎖狀態(tài)、偏向鎖、輕量級(jí)鎖、重量鎖以及對(duì)象被GC標(biāo)記的對(duì)象頭中的運(yùn)行時(shí)數(shù)據(jù)信息:

實(shí)例數(shù)據(jù)

實(shí)例數(shù)據(jù)是對(duì)象真正存儲(chǔ)的有效信息,它包含了對(duì)象中定義的各種類(lèi)型的字段。這些字段有對(duì)象本身定義的,也有從所有父對(duì)象繼承的字段。

父類(lèi)的構(gòu)造方法先于子類(lèi)執(zhí)行,所以父類(lèi)變量的定義都在子類(lèi)前面。

對(duì)齊填充

對(duì)齊填充不是必須的,也沒(méi)有實(shí)在的意義,它僅僅是個(gè)占位符的作用。HotSpot虛擬機(jī)要求對(duì)象的起始地址必須是8字節(jié)的整數(shù)倍,因此當(dāng)對(duì)象沒(méi)有滿足的時(shí)候,就需要對(duì)齊填充來(lái)補(bǔ)全。

現(xiàn)在我們已經(jīng)了解了對(duì)象在堆內(nèi)存的布局,在之前的JVM文章中也學(xué)習(xí)了虛擬機(jī)棧結(jié)構(gòu)和方法區(qū)(JDK1.8之后稱(chēng)為元空間,勾勾之前習(xí)慣稱(chēng)為方法區(qū),但是怕大家混淆后續(xù)我們都用元空間表示),那么接下來(lái)我們?cè)敿?xì)分析school對(duì)象創(chuàng)建的整個(gè)過(guò)程。

對(duì)象創(chuàng)建的步驟

對(duì)象的創(chuàng)建是在主線程的main()方法中,所以在主線程的虛擬機(jī)棧中就會(huì)創(chuàng)建main()的棧幀,main()就是當(dāng)前方法。

我們回顧下棧和棧幀。

JVM內(nèi)存區(qū)域劃分為5個(gè)模塊:堆、元空間、虛擬機(jī)棧、本地方法棧和程序計(jì)數(shù)器(也成為pc寄存器)。

虛擬機(jī)棧和本地方法棧都屬于棧,本地方法棧中只存放native方法的棧信息。

虛擬機(jī)棧的生命周期和線程的生命周期一致,它隨著線程的創(chuàng)建而創(chuàng)建,隨著線程的銷(xiāo)毀而銷(xiāo)毀,所以它是線程私有的內(nèi)存區(qū)域。

虛擬機(jī)棧是由棧幀組成的,棧幀中包含了局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈接、方法返回地址、附加信息。棧幀是隨著方法的調(diào)用而創(chuàng)建的。所以當(dāng)主線程調(diào)用main()方法時(shí),此時(shí)在主線程的虛擬機(jī)棧中就創(chuàng)建了main()棧幀。

main()棧幀中的局部變量表包含兩個(gè)變量:args和school。

主線程的虛擬機(jī)棧的棧幀結(jié)構(gòu)如下圖:

main()方法想要將school這個(gè)局部變量實(shí)例化,就需要執(zhí)行School這個(gè)類(lèi)的實(shí)例化。

那么new School()發(fā)生了什么呢?我們接下來(lái)詳細(xì)分析之前的5個(gè)步驟。

判斷對(duì)象的類(lèi)是否已經(jīng)加載

當(dāng)虛擬機(jī)遇到new這個(gè)指令時(shí),會(huì)首先檢查這個(gè)指令的參數(shù)能否在元空間的常量池中定位到一個(gè)類(lèi)的符號(hào)引用,并且檢查這個(gè)符號(hào)引用代表的類(lèi)是否已經(jīng)被加載,即判斷元空間中是否包含這個(gè)類(lèi)的類(lèi)元信息。

我們通過(guò)javap -v -p Test.clas查看Test類(lèi)的字節(jié)碼信息:

  1. Classfile /E:/study/javacodegirl/src/main/java/com/study/test/code/girl/base/jvm/Test.class 
  2.   Last modified 2021-2-21; size 352 bytes 
  3.   MD5 checksum 2df3d394ac88d2aa4da9d27f848067c5 
  4.   Compiled from "School.java" 
  5. class com.study.test.code.girl.base.jvm.Test 
  6.   minor version: 0 
  7.   major version: 52 
  8.   flags: ACC_SUPER 
  9. Constant pool: 
  10.    #1 = Methodref          #5.#14         // java/lang/Object."<init>":()V 
  11.    #2 = Class              #15            // com/study/test/code/girl/base/jvm/School 
  12.    #3 = Methodref          #2.#14         // com/study/test/code/girl/base/jvm/School."<init>":()V 
  13.    #4 = Class              #16            // com/study/test/code/girl/base/jvm/Test 
  14.    #5 = Class              #17            // java/lang/Object 
  15.    #6 = Utf8               <init> 
  16.    #7 = Utf8               ()V 
  17.    #8 = Utf8               Code 
  18.    #9 = Utf8               LineNumberTable 
  19.   #10 = Utf8               main 
  20.   #11 = Utf8               ([Ljava/lang/String;)V 
  21.   #12 = Utf8               SourceFile 
  22.   #13 = Utf8               School.java 
  23.   #14 = NameAndType        #6:#7          // "<init>":()V 
  24.   #15 = Utf8               com/study/test/code/girl/base/jvm/School 
  25.   #16 = Utf8               com/study/test/code/girl/base/jvm/Test 
  26.   #17 = Utf8               java/lang/Object 
  27.   com.study.test.code.girl.base.jvm.Test(); 
  28.     descriptor: ()V 
  29.     flags: 
  30.     Code: 
  31.       stack=1, locals=1, args_size=1 
  32.          0: aload_0 
  33.          1: invokespecial #1                  // Method java/lang/Object."<init>":()V 
  34.          4: return 
  35.       LineNumberTable: 
  36.         line 28: 0 
  37.  
  38.   public static void main(java.lang.String[]); 
  39.     descriptor: ([Ljava/lang/String;)V 
  40.     flags: ACC_PUBLIC, ACC_STATIC 
  41.     Code: 
  42.       stack=2, locals=2, args_size=1 
  43.          0: new           #2                  // class com/study/test/code/girl/base/jvm/School 
  44.          3: dup 
  45.          4: invokespecial #3                  // Method com/study/test/code/girl/base/jvm/School."<init>":()V 
  46.          7: astore_1 
  47.          8: return 
  48.       LineNumberTable: 
  49.         line 30: 0 
  50.         line 31: 8 
  51. SourceFile: "School.java" 

 在main()中new指令的參數(shù)是#2,我們可以在Constant pool中找到#2對(duì)應(yīng)的類(lèi)信息。

如果沒(méi)有這個(gè)類(lèi)的信息,那么就會(huì)按照雙親委派模型加載School類(lèi)。

類(lèi)的加載過(guò)程:加載、連接、初始化,其中連接包括:驗(yàn)證、準(zhǔn)備、解析。

執(zhí)行類(lèi)的加載的是類(lèi)加載器,它分為:?jiǎn)?dòng)類(lèi)加載器、擴(kuò)展類(lèi)加載器、應(yīng)用類(lèi)加載器和自定義加載器。

School類(lèi)是ClassPath下的文件,它的類(lèi)加載是應(yīng)用類(lèi)加載器,當(dāng)應(yīng)用類(lèi)加載器按照ClassLoader+包名+類(lèi)名查找對(duì)應(yīng)的.class文件時(shí),如果找不到這個(gè)文件就會(huì)拋出ClassNotFoundException異常,如果找到了則進(jìn)行類(lèi)的加載,并生成對(duì)應(yīng)的Class類(lèi)對(duì)象。這個(gè)時(shí)候在元空間中就有了School的類(lèi)元數(shù)據(jù)了。

為對(duì)象分配內(nèi)存空間

接下來(lái)就需要計(jì)算對(duì)象占用的空間大小,基本類(lèi)型除了long和double是8個(gè)字節(jié),byte和boolean是1個(gè)字節(jié),char和short是2個(gè)字節(jié),其他基本類(lèi)型都是4個(gè)字節(jié),引用類(lèi)型也是4個(gè)字節(jié)。

內(nèi)存大小計(jì)算好之后在堆中劃分一塊內(nèi)存空間給新對(duì)象。大部分情況下,對(duì)象是在新生代的Eden區(qū)中分配,如果此時(shí)Eden區(qū)沒(méi)有足夠的內(nèi)存空間進(jìn)行分配,虛擬機(jī)將發(fā)起一次Minor GC。但是當(dāng)我們?yōu)橐粋€(gè)很長(zhǎng)的字符串或者數(shù)組分配內(nèi)存時(shí),這種類(lèi)型的大對(duì)象需要連續(xù)的內(nèi)存空間,可以直接在老年代進(jìn)行分配,這樣做可以避免Eden和兩個(gè)S區(qū)發(fā)生大量的內(nèi)存復(fù)制。但是大對(duì)象可能會(huì)導(dǎo)致連續(xù)空間不足而提前觸發(fā)GC,我們開(kāi)發(fā)中也應(yīng)該盡量避免大對(duì)象。

內(nèi)存分配有兩種方式:指針碰撞和空閑列表分配。

  • 指針碰撞:當(dāng)內(nèi)存使用的GC算法是標(biāo)記整理或者復(fù)制算法時(shí),內(nèi)存是規(guī)整的,此時(shí)我們?yōu)閷?duì)象分配內(nèi)存只需要移動(dòng)指針位置就可以。Serial和ParNew使用的GC回收算法是標(biāo)記復(fù)制算法,內(nèi)存的分配就是指針碰撞的方式。

 

  • 空閑列表分配:當(dāng)內(nèi)存使用的GC算法是標(biāo)記清除算法時(shí),內(nèi)存是規(guī)整的,這個(gè)時(shí)候維護(hù)了內(nèi)存空閑的列表,在為新對(duì)象分配內(nèi)存時(shí)從空閑列表中找到內(nèi)存就可以。CMS使用的GC回收算法是標(biāo)記清除算法,內(nèi)存的分配方式就是空閑列表分配。

看完內(nèi)存的分配你有沒(méi)有疑問(wèn)?堆內(nèi)存是所有線程共享的,如果兩個(gè)線程同時(shí)都想占用這一塊內(nèi)存空間怎么辦呢?這就涉及到了分配內(nèi)存空間時(shí)的并發(fā)安全問(wèn)題。

JVM提供了兩種處理并發(fā)安全的方式:一種是我們常用的CAS失敗重試+區(qū)域鎖來(lái)保證內(nèi)存分配的原子性,另外一種是通過(guò)開(kāi)啟-XX:+UseTLAB參數(shù)為每個(gè)線程預(yù)分配一塊TLAB,在JDK1.8中這個(gè)參數(shù)是默認(rèn)開(kāi)啟的。

經(jīng)過(guò)了這一步之后,堆內(nèi)存中就有了School實(shí)例的一塊內(nèi)存區(qū)域了:

初始化分配到的內(nèi)存空間

屬性的賦值操作分為3個(gè)類(lèi)型,我們?cè)谑纠卸加信e例:

  • 默認(rèn)值初始化
  • 顯式初始化和代碼塊初始化
  • 構(gòu)造方法初始化

初始化分配到的內(nèi)存空間是默認(rèn)值初始化,它為類(lèi)的成員變量設(shè)置默認(rèn)值,保證對(duì)象實(shí)例字段在不賦值時(shí)可以直接使用?;緮?shù)據(jù)類(lèi)型的默認(rèn)值為0,布爾類(lèi)型的默認(rèn)值為false,引用類(lèi)型的默認(rèn)值為null。

不要把這一步的初始化和類(lèi)加載過(guò)程中的初始化混淆了!

類(lèi)加載過(guò)程中的初始化是對(duì)類(lèi)的靜態(tài)變量初始化,不包含類(lèi)的實(shí)例變量。

執(zhí)行了這一步之后,內(nèi)存中的情況如下圖:

設(shè)置對(duì)象的對(duì)象頭

將對(duì)象的所屬的類(lèi)、對(duì)象的HashCode值、對(duì)象的GC信息、鎖信息等數(shù)據(jù)存放在對(duì)象頭中。它取決于JVM實(shí)現(xiàn)。對(duì)象頭的信息我們前面已經(jīng)講過(guò),這里不再贅述。

執(zhí)行了這一步之后內(nèi)存中的數(shù)據(jù)變化:

 

執(zhí)行init進(jìn)行初始化

這個(gè)時(shí)候初始化過(guò)程才真正開(kāi)始。這個(gè)過(guò)程是對(duì)應(yīng)字節(jié)碼invokespecial,執(zhí)行init方法。

它會(huì)執(zhí)行實(shí)例化代碼塊、調(diào)用類(lèi)的構(gòu)造方法、將堆內(nèi)對(duì)象的首地址賦值給引用變量。這一步之后真正可用的對(duì)象才算創(chuàng)建完成。

執(zhí)行了這一步之后內(nèi)存中的變化如下圖:

總結(jié)

對(duì)象的創(chuàng)建過(guò)程:類(lèi)元數(shù)據(jù)加載->分配內(nèi)存空間并解決并發(fā)問(wèn)題->初始化分配的內(nèi)存空間->設(shè)置對(duì)象頭信息->執(zhí)行init方法進(jìn)行初始化。

對(duì)象的整個(gè)創(chuàng)建過(guò)程大家要對(duì)JVM的內(nèi)存區(qū)域比較了解,熟悉每個(gè)區(qū)域存放的數(shù)據(jù),并知道在哪個(gè)過(guò)程存的數(shù)據(jù)。

類(lèi)元數(shù)據(jù)的加載是元空間的數(shù)據(jù)來(lái)源,我們還可以回顧下類(lèi)加載機(jī)制、雙親委派模型、哪些場(chǎng)景下需要打破雙親委派,之前勾勾分析了JDBC的SPI機(jī)制,利用線程上下文類(lèi)加載器打破雙親委派。

對(duì)象的創(chuàng)建都是基于堆空間的,我們可以回顧下堆空間的內(nèi)存分配、GC回收算法和GC回收器。

設(shè)置對(duì)象頭信息我們需要了解對(duì)象頭,還可以按照對(duì)象頭的數(shù)據(jù)變化回顧synchronized鎖的升級(jí)過(guò)程。

對(duì)象創(chuàng)建之后內(nèi)存的數(shù)據(jù)變化如下圖:

 

責(zé)任編輯:姜華 來(lái)源: 今日頭條
相關(guān)推薦

2017-02-27 11:48:58

JVM源碼分析Java

2022-03-28 11:00:34

JVMJava對(duì)象

2011-06-23 15:10:39

Qt 窗體

2017-01-12 14:52:03

JVMFinalRefere源碼

2015-11-16 11:22:05

Java對(duì)象內(nèi)存分配

2020-10-30 08:35:23

Java Virtua

2014-04-29 13:16:42

OpenGLAndroid庫(kù)加載過(guò)程

2010-09-17 13:32:22

JVM.dll

2017-01-11 14:02:32

JVM源碼內(nèi)存

2010-07-08 13:35:39

UML面向?qū)ο?/a>

2020-05-26 18:50:46

JVMAttachJava

2019-07-24 08:34:35

Java對(duì)象數(shù)據(jù)結(jié)構(gòu)

2010-09-27 10:30:42

JVM對(duì)象生命周期

2009-07-08 11:25:36

jvm.dll

2024-09-11 09:25:03

Tomcat組件PREP

2013-03-14 11:17:46

2025-03-14 10:37:24

SpringSpring IOC容器

2017-01-11 14:19:26

JVM源碼All

2024-02-22 07:37:37

對(duì)象JVM內(nèi)存

2012-03-01 10:51:37

JavaJVM
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)