日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪(fǎng)問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

尚硅谷2020最新版宋红康JVM教程-中篇-第3章类的加载过程(类的生命周期)详解-4-过程三:Initialization(初始化)阶段

發(fā)布時(shí)間:2024/3/7 编程问答 64 豆豆
生活随笔 收集整理的這篇文章主要介紹了 尚硅谷2020最新版宋红康JVM教程-中篇-第3章类的加载过程(类的生命周期)详解-4-过程三:Initialization(初始化)阶段 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

static與final的搭配問(wèn)題

初始化階段,簡(jiǎn)言之,為類(lèi)的靜態(tài)變量賦予正確的初始值。

具體描述

  • 類(lèi)的初始化是類(lèi)裝載的最后一個(gè)階段。如果前面的步驟都沒(méi)有問(wèn)題,那么表示類(lèi)可以順利裝載到系統(tǒng)中。此時(shí),類(lèi)才會(huì)開(kāi)始執(zhí)行Java字節(jié)碼。(即:到了初始化階段,才真正開(kāi)始執(zhí)行類(lèi)中定義的Java程序代碼。)
  • 初始化階段的重要工作是執(zhí)行類(lèi)的初始化方法:<clinit>()方法。
    • 該方法僅能由Java編譯器生成并由JVM調(diào)用,程序開(kāi)發(fā)者無(wú)法自定義一個(gè)同名的方法,更無(wú)法直接在Java程序中調(diào)用該方法,雖然該方法也是由字節(jié)碼指令所組成。
    • 它是由類(lèi)靜態(tài)成員的賦值語(yǔ)句以及static語(yǔ)句塊合并產(chǎn)生的。

代碼舉例

public class InitializationTest {public static int id = 1;public static int number;static {number = 2;System.out.println("father static(}");}// clinit方法:// 0 iconst_1// 1 putstatic #2 <T1/InitializationTest.id>// 4 iconst_2// 5 putstatic #3 <T1/InitializationTest.number>// 8 getstatic #4 <java/lang/System.out>//11 ldc #5 <father static(}>//13 invokevirtual #6 <java/io/PrintStream.println>//16 return }

說(shuō)明

  • 在加載一個(gè)類(lèi)之前,虛擬機(jī)總是會(huì)試圖加載該類(lèi)的父類(lèi),因此父類(lèi)的<clinit>總是在子類(lèi)<clinit>之前被調(diào)用。也就是說(shuō),父類(lèi)的static塊優(yōu)先級(jí)高于子類(lèi)。
  • Java編譯器并不會(huì)為所有的類(lèi)都產(chǎn)生<clinit>()初始化方法。哪些類(lèi)在編譯為字節(jié)碼后,字節(jié)碼文件中將不會(huì)包含<clinit>()方法?
    • 一個(gè)類(lèi)中并沒(méi)有聲明任何的類(lèi)變量,也沒(méi)有靜態(tài)代碼塊時(shí)
    • 一個(gè)類(lèi)中聲明類(lèi)變量,但是沒(méi)有明確使用類(lèi)變量的初始化語(yǔ)句以及靜態(tài)代碼塊來(lái)執(zhí)行初始化操作時(shí)
    • 一個(gè)類(lèi)中包含static final修飾的基本數(shù)據(jù)類(lèi)型的字段,這些類(lèi)字段初始化語(yǔ)句采用編譯時(shí)常量表達(dá)式

代碼舉列

/*** 哪些場(chǎng)景下,java編譯器就不會(huì)生成<cLinit>()方法*/ public class InitializationTest1 {//場(chǎng)景1:對(duì)應(yīng)非靜態(tài)的字段,不管是否進(jìn)行了顯式賦值,都不會(huì)生成<clinit>()方法public int num = 1;//場(chǎng)景2:靜態(tài)的字段,沒(méi)有顯式的賦值,不會(huì)生成<clinit>()方法public static int numl;//場(chǎng)景3:比如對(duì)于聲明為static final的基本數(shù)據(jù)類(lèi)型的字段,不管是否進(jìn)行了顯式賦值,都不會(huì)生成<clinit>()方法public static final int num2 = 1;}

關(guān)于static + final

/*** 說(shuō)明:使用static+ final修飾的字段的顯式賦值的操作,到底是在哪個(gè)階段進(jìn)行的賦值?* 情況1:在鏈接階段的準(zhǔn)備環(huán)節(jié)賦值* 情況2:在初始化階段<cLinit>()中賦值* * 結(jié)論:* 在鏈接階段的準(zhǔn)備環(huán)節(jié)賦值的情況:* 1.對(duì)于基本數(shù)據(jù)類(lèi)型的字段來(lái)說(shuō),如果使用static final修飾,則顯式賦值(直接賦值常量,而非調(diào)用方法)通常是在鏈接階段的準(zhǔn)備環(huán)節(jié)進(jìn)行* 2.對(duì)于String來(lái)說(shuō),如果使用字面量的方式賦值,使用static final修飾的話(huà),則顯式賦值通常是在鏈接階段的準(zhǔn)備環(huán)節(jié)進(jìn)行* * 在初始化階段<cLinit>()中賦值的情況:* 排除上述的在準(zhǔn)備環(huán)節(jié)賦值的情況之外的情況。* * 最終結(jié)論:使用static+final修飾,且顯示賦值中不涉及到方法或構(gòu)造器調(diào)用的基本數(shù)據(jù)類(lèi)到或String類(lèi)型的顯式財(cái)值,是在鏈接階段的準(zhǔn)備環(huán)節(jié)進(jìn)行。*/ public class InitializationTest2 {public static int a = 1; //在初始化階段<clinit>()中賦值public static final int INT_CONSTANT = 10; //在鏈接階段的準(zhǔn)備環(huán)節(jié)賦值public static final Integer INTEGER_CONSTANT1 = Integer.valueOf(100); // 在初始化階段<clinit>()中賦值public static Integer INTEGER_CONSTANT2 = Integer.valueOf(100); // 在初始化階段<clinit>()中概值public static final String se = "helloworlde"; // 在鏈接階段的準(zhǔn)備環(huán)節(jié)賦值public static final String s1 = new String("helloworld1"); // 在初始化階段<clinit>()中賦值public static final int NUM1 = new Random().nextInt(10);//在初始化階段clinit>()中賦值 }

所有非final的static都是在初始化<clini>()顯示賦值
不涉及符號(hào)引用(鏈接階段的解析環(huán)節(jié)),就直接在鏈接階段的準(zhǔn)備環(huán)節(jié)顯示賦值(沒(méi)驗(yàn)證)

<clinit>()的線(xiàn)程安全性

  • 對(duì)于<clinit>()方法的調(diào)用,也就是類(lèi)的初始化,虛擬機(jī)會(huì)在內(nèi)部確保其多線(xiàn)程環(huán)境中的安全性。
  • 虛擬機(jī)會(huì)保證一個(gè)類(lèi)的()方法在多線(xiàn)程環(huán)境中被正確地加鎖、同步,如果多個(gè)線(xiàn)程同時(shí)去初始化一個(gè)類(lèi),那么只會(huì)有一個(gè)線(xiàn)程去執(zhí)行這個(gè)類(lèi)的<clinit>()方法,其他線(xiàn)程都需要阻塞等待,直到活動(dòng)線(xiàn)程執(zhí)行<clinit>()方法完畢。
  • 正是因?yàn)楹瘮?shù)<clinit>()帶鎖線(xiàn)程安全的,因此,如果在一個(gè)類(lèi)的<clinit>()方法中有耗時(shí)很長(zhǎng)的操作,就可能造成多個(gè)線(xiàn)程阻塞,引發(fā)死鎖。并且這種死鎖是很難發(fā)現(xiàn)的,因?yàn)榭雌饋?lái)它們并沒(méi)有可用的鎖信息。
  • 如果之前的線(xiàn)程成功加載了類(lèi),則等在隊(duì)列中的線(xiàn)程就沒(méi)有機(jī)會(huì)再執(zhí)行<clinit>()方法了。那么,當(dāng)需要使用這個(gè)類(lèi)時(shí),虛擬機(jī)會(huì)直接返回給它已經(jīng)準(zhǔn)備好的信息。

代碼舉列

package T1;public class StaticDeadLockMain extends Thread {private char flag;public StaticDeadLockMain(char flag) {this.flag = flag;this.setName("Thread" + flag);}@Overridepublic void run() {try {Class.forName("T1.Static" + flag);} catch (ClassNotFoundException e) {e.printStackTrace();}System.out.println(getName() + "over");}public static void main(String[] args) throws ClassNotFoundException, InterruptedException {StaticDeadLockMain loadA = new StaticDeadLockMain('A');loadA.start();StaticDeadLockMain loadB = new StaticDeadLockMain('B');loadB.start();} }class StaticA {static {try {Thread.sleep(100);} catch (InterruptedException e) {}try {Class.forName("T1.StaticB");} catch (ClassNotFoundException e) {e.printStackTrace();}System.out.println("staticA init Ok");} }class StaticB {static {try {Thread.sleep(100);} catch (InterruptedException e) {}try {Class.forName("T1.StaticA");} catch (ClassNotFoundException e) {e.printStackTrace();}System.out.println("staticB init Ok");} }

類(lèi)的初始化情況:主動(dòng)使用vs被動(dòng)使用

引言

  • Java程序?qū)︻?lèi)的使用分為兩種:主動(dòng)使用和被動(dòng)使用。
  • 主動(dòng)使用才會(huì)調(diào)用<clinit>(初始化),被動(dòng)使用不會(huì)引起類(lèi)的初始化
  • 被動(dòng)使用不會(huì)引起類(lèi)的初始化,但有可能只是加載了沒(méi)進(jìn)行初始化,比如調(diào)用類(lèi)的final+static的字段,有加載能輸出字段,但沒(méi)經(jīng)歷初始化

主動(dòng)使用

Class只有在必須要首次使用的時(shí)候才會(huì)被裝載,Java虛擬機(jī)不會(huì)無(wú)條件地裝載Class類(lèi)型。Java虛擬機(jī)規(guī)定,一個(gè)類(lèi)或接口在初次使用前,必須要進(jìn)行初始化。這里指的“使用”,是指主動(dòng)使用,主動(dòng)使用只有下列幾種情況:(即:如果出現(xiàn)如下的情況,則會(huì)對(duì)類(lèi)進(jìn)行初始化操作。而初始化操作之前的加載、驗(yàn)證、準(zhǔn)備已經(jīng)完成。)

  • 當(dāng)創(chuàng)建一個(gè)類(lèi)的實(shí)例時(shí),比如使用new關(guān)鍵字,或者通過(guò)反射、克隆、反序列化。
  • 當(dāng)調(diào)用類(lèi)的靜態(tài)方法時(shí),即當(dāng)使用了字節(jié)碼invokestatic指令。
  • 當(dāng)使用類(lèi)、接口的靜態(tài)字段時(shí)(final修飾特殊考慮),比如,使用getstatic或者putstatic指令。(對(duì)應(yīng)訪(fǎng)問(wèn)變量、賦值變量操作)
  • 當(dāng)使用java.lang.reflect包中的方法反射類(lèi)的方法時(shí)。比如:Class.forName(“com.atguigu.java.Test”)
  • 當(dāng)初始化子類(lèi)時(shí),如果發(fā)現(xiàn)其父類(lèi)還沒(méi)有進(jìn)行過(guò)初始化,則需要先觸發(fā)其父類(lèi)的初始化。
  • 如果一個(gè)接口定義了default方法,那么直接實(shí)現(xiàn)或者間接實(shí)現(xiàn)該接口的類(lèi)的初始化,該接口要在其之前被初始化。
  • 當(dāng)虛擬機(jī)啟動(dòng)時(shí),用戶(hù)需要指定一個(gè)要執(zhí)行的主類(lèi)(包含main()方法的那個(gè)類(lèi)),虛擬機(jī)會(huì)先初始化這個(gè)主類(lèi)。
  • 當(dāng)初次調(diào)用MethodHandle 實(shí)例時(shí),初始化該 MethodHandle指向的方法所在的類(lèi)。(涉及解析REF getStatic、REF_putStatic、REF invokeStatic方法句柄對(duì)應(yīng)的類(lèi))
  • 針對(duì)5,補(bǔ)充說(shuō)明:

    當(dāng)Java虛擬機(jī)初始化一個(gè)類(lèi)時(shí),要求它的所有父類(lèi)都已經(jīng)被初始化,但是這條規(guī)則并不適用于接口。

    • 在初始化一個(gè)類(lèi)時(shí),并不會(huì)先初始化它所實(shí)現(xiàn)的接口
    • 在初始化一個(gè)接口時(shí),并不會(huì)先初始化它的父接口
    • 因此,一個(gè)父接口并不會(huì)因?yàn)樗淖咏涌诨蛘邔?shí)現(xiàn)類(lèi)的初始化而初始化。只有當(dāng)程序首次使用特定接口的靜態(tài)字段時(shí),才會(huì)導(dǎo)致該接口的初始化。

    針對(duì)7,說(shuō)明:

    VM啟動(dòng)的時(shí)候通過(guò)引導(dǎo)類(lèi)加載器加載一個(gè)初始類(lèi)。這個(gè)類(lèi)在調(diào)用public static void main(String[])方法之前被鏈接和初始化。這個(gè)方法的執(zhí)行將依次導(dǎo)致所需的類(lèi)的加載,鏈接和初始化。

    代碼舉列-主動(dòng)使用

    package T1;import java.util.Random;public class ActiveUse1 {// main()方法的那個(gè)類(lèi)),虛擬機(jī)會(huì)先初始化這個(gè)主類(lèi)static {System.out.println("ActiveUse1 的初始化(main觸發(fā))");}public static void main(String[] args) throws ClassNotFoundException {// 當(dāng)創(chuàng)建一個(gè)類(lèi)的實(shí)例時(shí),比如使用new關(guān)鍵字 // Order order = new Order();// 當(dāng)調(diào)用類(lèi)的靜態(tài)方法時(shí),invokestatic // Order.method1();// 當(dāng)使用類(lèi)、接口的靜態(tài)字段時(shí)(final修飾特殊考慮) // System.out.println(Order.num); // System.out.println(CompareA.num);// 當(dāng)使用java.lang.reflect包中的方法反射類(lèi)的方法時(shí) // Class.forName("T1.Order");// 當(dāng)初始化子類(lèi)時(shí),如果發(fā)現(xiàn)其父類(lèi)還沒(méi)有進(jìn)行過(guò)初始化,則需要先觸發(fā)其父類(lèi)的初始化(沒(méi)能觸發(fā)實(shí)現(xiàn)的接口)Class.forName("T1.Order");// 初始化一個(gè)接口時(shí),并不會(huì)先初始化它的父接口 // System.out.println(CompareC.cnum2);// 接口定義了default方法,那么直接實(shí)現(xiàn)或者間接實(shí)現(xiàn)該接口的類(lèi)的初始化Class.forName("T1.Order");} }class Order extends OrderFather implements CompareB, CompareD {public static int num = 10;static {System.out.println("Order類(lèi)的初始化過(guò)程。。。。。。。。。。。");}public static void method1() {System.out.println("靜態(tài)代碼 method1");} }class OrderFather {public static int num = 20;static {System.out.println("OrderFather類(lèi)的初始化過(guò)程。。。。。。。。。。。");} }interface CompareA {public static final Thread t = new Thread() {{System.out.println("CompareA初始化");}};public static int num = 10; // 為啥這個(gè)沒(méi)能觸發(fā)初始化public static final int cnum2 = new Random().nextInt(10); }interface CompareB {public static final Thread t = new Thread() {{System.out.println("CompareB初始化");}};}interface CompareC extends CompareB {public static final Thread t = new Thread() {{System.out.println("CompareC初始化");}};public static int num = 10; // 為啥這個(gè)沒(méi)能觸發(fā)初始化public static final int cnum2 = new Random().nextInt(10); }interface CompareD {public static final Thread t = new Thread() {{System.out.println("CompareD初始化");}};public default void abc() {System.out.println("default");} }

    -XX:+TraceClassLoading ,可以打印出類(lèi)的加載順序,可以用來(lái)排查 class 的沖突問(wèn)題。

    被動(dòng)使用

    除了以上的情況屬于主動(dòng)使用,其他的情況均屬于被動(dòng)使用。被動(dòng)使用不會(huì)引起類(lèi)的初始化。
    也就是說(shuō):并不是在代碼中出現(xiàn)的類(lèi),就一定會(huì)被加載或者初始化。如果不符合主動(dòng)使用的條件,類(lèi)就不會(huì)初始化。

  • 當(dāng)訪(fǎng)問(wèn)一個(gè)靜態(tài)字段時(shí),只有真正聲明區(qū)個(gè)字段的類(lèi)才會(huì)被初始化。

    當(dāng)通過(guò)子類(lèi)引用父類(lèi)的靜態(tài)變量,不會(huì)導(dǎo)致子類(lèi)初始化
  • 通過(guò)數(shù)組定義類(lèi)引用,不會(huì)觸發(fā)此類(lèi)的初始化

  • 引用常量不會(huì)觸發(fā)此類(lèi)或接口的初始化。因?yàn)槌A吭阪溄与A段就已經(jīng)被顯式賦值了。

  • 調(diào)用ClassLoader類(lèi)的LoadClass()方法加載一個(gè)類(lèi),并不是對(duì)類(lèi)的主動(dòng)使用,不會(huì)導(dǎo)致類(lèi)的初始化。

  • 代碼舉列-被動(dòng)使用

    package T1;import java.util.Random;public class PassiveUse1 {public static void main(String[] args) throws ClassNotFoundException {// 1. 當(dāng)訪(fǎng)問(wèn)一個(gè)靜態(tài)字段時(shí),只有真正聲明區(qū)個(gè)字段的類(lèi)才會(huì)被初始化。// 當(dāng)通過(guò)子類(lèi)引用父類(lèi)的靜態(tài)變量,不會(huì)導(dǎo)致子類(lèi)初始化 // System.out.println(Child.num);/// 2. 通過(guò)數(shù)組定義類(lèi)引用,不會(huì)觸發(fā)此類(lèi)的初始化 // Parent[] parents= new Parent[10]; // System.out.println(parents.getClass());// 但new的話(huà)還是會(huì)初始化 // parents[0] = new Parent();// 3. 引用常量不會(huì)觸發(fā)此類(lèi)或接口的初始化。因?yàn)槌A吭阪溄与A段就已經(jīng)被顯式賦值了。System.out.println(Parent.age);System.out.println(Serival1.num);// 但引用其他類(lèi)的話(huà)還是會(huì)初始化System.out.println(Serival1.cnum2);// 調(diào)用ClassLoader類(lèi)的LoadClass()方法加載一個(gè)類(lèi)System.out.println(ClassLoader.getSystemClassLoader().loadClass("T1.Parent"));} }class Parent {public static int num = 10;public static final int age = 20;static {System.out.println("Person 初始化");} }class Child extends Parent {static {System.out.println("Son 初始化");} }interface Serival1 {public static final Thread t = new Thread() {{System.out.println("Serival1 初始化");}};public static int num = 10; // 為啥這個(gè)沒(méi)能觸發(fā)初始化public static final int cnum2 = new Random().nextInt(10); }

    總結(jié)

    以上是生活随笔為你收集整理的尚硅谷2020最新版宋红康JVM教程-中篇-第3章类的加载过程(类的生命周期)详解-4-过程三:Initialization(初始化)阶段的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。