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

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

生活随笔

當(dāng)前位置: 首頁(yè) > 运维知识 > windows >内容正文

windows

JVM学习笔记之-类加载子系统,类的加载与类的加载过程,双亲委派机制

發(fā)布時(shí)間:2024/4/15 windows 47 豆豆
生活随笔 收集整理的這篇文章主要介紹了 JVM学习笔记之-类加载子系统,类的加载与类的加载过程,双亲委派机制 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

一 類加載器與類加載過(guò)程

類加載子系統(tǒng)作用

類加載器子系統(tǒng)負(fù)責(zé)從文件系統(tǒng)或者網(wǎng)絡(luò)中加載class文件,class文件在文件開(kāi)頭有特定的文件標(biāo)識(shí)。
ClassLoader只負(fù)責(zé)class文件的加載,至于它是否可以運(yùn)行,則由ExecutionEngine決定。
加載的類信息存放于一塊稱為方法區(qū)的內(nèi)存空間。除了類的信息外,方法區(qū)中還會(huì)存放運(yùn)行時(shí)常量池信息,可能還包括字符串字面量和數(shù)字常量(這部分常量信息是Class文件中常量池部分的內(nèi)存映射)

類加載器ClassLoader角色

  • class file存在于本地硬盤(pán)上,可以理解為設(shè)計(jì)師畫(huà)在紙上的模板,而最終這個(gè)模板在執(zhí)行的時(shí)候是要加載到JVM當(dāng)中來(lái)根據(jù)這個(gè)文件實(shí)例化出n個(gè)一模一樣的實(shí)例。
  • class file 加載到JVM中,被稱為DNA元數(shù)據(jù)模板,放在方法區(qū)。
  • 在.class文件->JVM->最終成為元數(shù)據(jù)模板,此過(guò)程就要一個(gè)運(yùn)輸工具(類裝載器class Loader),扮演一個(gè)快遞員的角色。
  • 類的加載過(guò)程


    類的加載過(guò)程的幾個(gè)階段

    類的加載過(guò)程:一 Loading階段

    加載:

    1.通過(guò)一個(gè)類的全限定名獲取定義此類的二進(jìn)制字節(jié)流
    2.將這個(gè)字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)
    3.在內(nèi)存中生成一個(gè)代表這個(gè)類的java.lang.class對(duì)象,作為方法區(qū)這個(gè)類的各種數(shù)據(jù)的訪問(wèn)入口

    補(bǔ)充:加載.class文件的方式

    從本地系統(tǒng)中直接加載I
    通過(guò)網(wǎng)絡(luò)獲取,典型場(chǎng)景: web Applet
    從zip壓縮包中讀取,成為日后jar、war格式的基礎(chǔ)
    運(yùn)行時(shí)計(jì)算生成,使用最多的是:動(dòng)態(tài)代理技術(shù)
    由其他文件生成,典型場(chǎng)景:JSP應(yīng)用
    從專有數(shù)據(jù)庫(kù)中提取.class文件,比較少見(jiàn)
    從加密文件中獲取,典型的防class文件被反編譯的保護(hù)措施

    類加載過(guò)程:二 Linking階段


    驗(yàn)證(Verify):
    目的在于確保class文件的字節(jié)流中包含信息符合當(dāng)前虛擬機(jī)要求,保證被加載類的正確性,不會(huì)危害虛擬機(jī)自身安全。

    主要包括四種驗(yàn)證,文件格式驗(yàn)證,元數(shù)據(jù)驗(yàn)證,字節(jié)碼驗(yàn)證,符號(hào)引用驗(yàn)證。

    準(zhǔn)備(Prepare):
    為類變量分配內(nèi)存并且設(shè)置該類變量的默認(rèn)初始值,即零值。

    這里不包含用final修飾的static,因?yàn)閒inal在編譯的時(shí)候就會(huì)分配了,準(zhǔn)備階段會(huì)顯式初始化;被final修飾就不是變量了,是常量,就在編譯的時(shí)候就會(huì)賦值

    ==這里不會(huì)為實(shí)例變量分配初始化,==類變量會(huì)分配在方法區(qū)中,而實(shí)例變量是會(huì)隨著對(duì)象一起分配到Java堆中。

    解析(Resolve) :
    將常量池內(nèi)的符號(hào)引用轉(zhuǎn)換為直接引用的過(guò)程。

    事實(shí)上,解析操作往往會(huì)伴隨著JVM在執(zhí)行完初始化之后再執(zhí)行。

    符號(hào)引用就是一組符號(hào)來(lái)描述所引用的目標(biāo)。符號(hào)引用的字面量形式明確定義在《java虛擬機(jī)規(guī)范》的class文件格式中。直接引用就是直接指向目標(biāo)的指針、相對(duì)偏移量或一個(gè)間接定位到目標(biāo)的句柄。

    解析動(dòng)作主要針對(duì)類或接口、字段、類方法、接口方法、方法類型等。對(duì)應(yīng)常量池中的CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT _Methodref_info等。

    類加載過(guò)程:三 Initialization階段

    初始化:

    初始化階段就是執(zhí)行類構(gòu)造器方法<clinit>()的過(guò)程.

    此方法不需定義,是javac編譯器自動(dòng)收集類中的所有類變量的賦值動(dòng)作和靜態(tài)代碼塊中的語(yǔ)句合并而來(lái)。

    構(gòu)造器方法中指令按語(yǔ)句在源文件中出現(xiàn)的順序執(zhí)行。

    <clinit> ()不同于類的構(gòu)造器。(關(guān)聯(lián):構(gòu)造器是虛擬機(jī)視角下的<init>( ) 任何一個(gè)類,都會(huì)顯示或者隱式(無(wú)參,有參構(gòu)造) 對(duì)應(yīng)的就是<init>方法 )

    若該類具有父類,JVM會(huì)保證子類的<clinit>()執(zhí)行前,父類的<clinit>()已經(jīng)執(zhí)行完畢。

    虛擬機(jī)必須保證一個(gè)類的<clinit> ()方法在多線程下被同步加鎖。

    使用jclasslib工具軟件查看字節(jié)碼文件,idea有插件


    下面代碼案列解釋:
    由于第二個(gè)過(guò)程linking中prepare環(huán)節(jié)中:number被賦值為0了,當(dāng)在initialization環(huán)節(jié)中:先賦值20 ,后面又賦值10,
    因?yàn)樵趐repare準(zhǔn)備階段number的初始值為0,由于靜態(tài)代碼塊優(yōu)先執(zhí)行,則賦值20,后面在賦值10


    package com.fs.classtest;public class ClassInitTest {private static int num = 1;static {num = 2 ;sum = 20;System.out.println(num);//2 // System.out.println(sum);//報(bào)錯(cuò)Error:(9, 28) java: 非法前向引用}private static int sum = 10;public static void main(String[] args) {System.out.println(ClassInitTest.num);//2System.out.println(ClassInitTest.sum);//10} }

    若該類具有父類,JVM會(huì)保證子類的<clinit>()執(zhí)行前,父類的<clinit>()已經(jīng)執(zhí)行完畢。

    虛擬機(jī)必須保證一個(gè)類的<clinit> ()方法在多線程下被同步加鎖。
    一個(gè)類只會(huì)加載一次

    package com.fs.classtest;public class ClassTest {public static void main(String[] args) {Runnable r = new Runnable() {public void run() {System.out.println(Thread.currentThread().getName()+"開(kāi)始");//創(chuàng)建我們測(cè)試的類DeadThread deadThread = new DeadThread();System.out.println(Thread.currentThread().getName()+"結(jié)束");}};new Thread(r,"線程1").start();new Thread(r,"線程2").start();/* 線程1開(kāi)始 線程2開(kāi)始 線程1線程來(lái)初始化當(dāng)前類*/} }class DeadThread{static {if (true){System.out.println(Thread.currentThread().getName()+"線程來(lái)初始化當(dāng)前類");while (true);}} }

    類加載器分類

    JVM支持兩種類型的類加載器,分別為引導(dǎo)類加載器(BootstrapClassLoader)和自定義類加載器(User-Defined classLoader) 。

    從概念上來(lái)講,自定義類加載器一般指的是程序中由開(kāi)發(fā)人員自定義的一類類加載器,但是Java虛擬機(jī)規(guī)范卻沒(méi)有這么定義,而是將所有派生于抽象類ClassLoader的類加載器都劃分為自定義類加載器。

    無(wú)論類加載器的類型如何劃分,在程序中我們最常見(jiàn)的類加載器始終只有3個(gè),如下所示:

    代碼獲取一下類的加載器

    package com.fs.classtest;public class ClassLoaderTest {public static void main(String[] args) {//獲取系統(tǒng)類加載器ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();System.out.println(systemClassLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2//獲取其上層:擴(kuò)展類加載器ClassLoader extClassLoader = systemClassLoader.getParent();System.out.println(extClassLoader);//sun.misc.Launcher$ExtClassLoader@1b6d3586//嘗試獲取其上層類加載器:獲取不到引導(dǎo)類加載器ClassLoader parent = extClassLoader.getParent();System.out.println(parent);//null//對(duì)于用戶自定義類來(lái)說(shuō): 由打印出的地址值得:默認(rèn)是使用系統(tǒng)類加載器來(lái)加載ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();System.out.println(classLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2//來(lái)看看String是由誰(shuí)來(lái)加載的:由于也是null,間接的認(rèn)為是由引導(dǎo)類加載器加載的,可得java的核心類庫(kù)都是由引導(dǎo)類加載器加載的ClassLoader stringClassLoader = String.class.getClassLoader();System.out.println(stringClassLoader);//null} }

    虛擬機(jī)自帶的加載器

    啟動(dòng)類加載器(引導(dǎo)類加載器,BootstrapclassLoader)

    這個(gè)類加載使用C/C++語(yǔ)言實(shí)現(xiàn)的,嵌套在JVM內(nèi)部。

    它用來(lái)加載Java的核心庫(kù)(JAVA_HOME/jre/lib/rt.jar、resources.jar或sun.boot.class.path路徑下的內(nèi)容),用于提供JVM自身需要的類

    并不繼承自java.lang.classLoader,沒(méi)有父加載器

    加載擴(kuò)展類和應(yīng)用程序類加載器,并指定為他們的父類加載器。

    出于安全考慮,Bootstrap啟動(dòng)類加載器只加載包名為java、javax、sun等開(kāi)頭的類

    擴(kuò)展類加載器(Extension classLoader)

    Java語(yǔ)言編寫(xiě),由sun.misc.Launcher$ExtClassLoader實(shí)現(xiàn)。

    派生于classLoader類

    父類加載器為啟動(dòng)類加載器

    從java.ext.dirs系統(tǒng)屬性所指定的目錄中加載類庫(kù),或從JDK的安裝目錄的jre/lib/ext子目錄(擴(kuò)展目錄)下加載類庫(kù)。如果用戶創(chuàng)建的JAR放在此目錄下,也會(huì)自動(dòng)由擴(kuò)展類加載器加載。

    應(yīng)用程序類加載器(系統(tǒng)類加載器,AppclassLoader)

    java語(yǔ)言編寫(xiě),由sun.misc.Launcher$AppClassLoader實(shí)現(xiàn)派生于classLoader類

    父類加載器為擴(kuò)展類加載器

    它負(fù)責(zé)加載環(huán)境變量classpath或系統(tǒng)屬性java.class.path指定路徑下的類庫(kù)

    該類加載是程序中默認(rèn)的類加載器,一般來(lái)說(shuō),Java應(yīng)用的類都是由它來(lái)完成加載

    通過(guò)classLoader#getsystemclassLoader ()方法可以獲取到該類加載器

    代碼獲取下

    package com.fs.classtest;import sun.misc.Launcher; import sun.security.ec.CurveDB;import java.net.URL; import java.security.Provider;public class ClassLoaderTest02 {public static void main(String[] args) {System.out.println("---------啟動(dòng)類加載器-------");//獲取BootStrapClassLoader能夠加載的api的路徑URL[] urLs = Launcher.getBootstrapClassPath().getURLs();for (URL urL : urLs) {System.out.println(urL.toExternalForm());}/* file:/D:/Java/jdk-8-241/jre/lib/resources.jar file:/D:/Java/jdk-8-241/jre/lib/rt.jar file:/D:/Java/jdk-8-241/jre/lib/sunrsasign.jar file:/D:/Java/jdk-8-241/jre/lib/jsse.jar file:/D:/Java/jdk-8-241/jre/lib/jce.jar file:/D:/Java/jdk-8-241/jre/lib/charsets.jar file:/D:/Java/jdk-8-241/jre/lib/jfr.jar file:/D:/Java/jdk-8-241/jre/classes*///從上面啟動(dòng)類加載器輸出的一個(gè)路徑中任意選擇一個(gè)類,來(lái)看他加載器是什么ClassLoader classLoader = Provider.class.getClassLoader();System.out.println(classLoader);//null 輸出是null.說(shuō)明是引導(dǎo)類加載器System.out.println("-------------擴(kuò)展類加載器-------------");String property = System.getProperty("java.ext.dirs");String[] split = property.split(";");for (String s : split) {System.out.println(s);}/* D:\Java\jdk-8-241\jre\lib\ext C:\WINDOWS\Sun\Java\lib\ext*///從上面擴(kuò)展類加載器輸出的一個(gè)路徑中任意選擇一個(gè)類,來(lái)看他加載器是什么ClassLoader loader = CurveDB.class.getClassLoader();System.out.println(loader);//sun.misc.Launcher$ExtClassLoader@12a3a380 說(shuō)明是擴(kuò)展類加載器} }

    用戶自定義類加載器

    在Java的日常應(yīng)用程序開(kāi)發(fā)中,類的加載幾乎是由上述3種類加載器相互配合執(zhí)行的,在必要時(shí),我們還可以自定義類加載器,來(lái)定制類的加載方式。

    為什么要自定義類加載器?
    隔離加載類
    修改類加載的方式
    擴(kuò)展加載源
    防止源碼泄漏

    用戶自定義類加載器實(shí)現(xiàn)步驟:

    1.開(kāi)發(fā)人員可以通過(guò)繼承抽象類java.lang.classLoader類的方式,實(shí)現(xiàn)自己的類加載器,以滿足一些特殊的需求

    2.在JDK1.2之前,在自定義類加載器時(shí),總會(huì)去繼承classLoader類并重寫(xiě)loadclass ()方法,從而實(shí)現(xiàn)自定義的類加載類,但是在JDK1.2之后已不再建議用戶去覆蓋loadclass ()方法,而是建議把自定義的類加載邏輯寫(xiě)在findclass ()方法中

    3.在編寫(xiě)自定義類加載器時(shí),如果沒(méi)有太過(guò)于復(fù)雜的需求,可以直接繼承URLClassLoader類,這樣就可以避免自己去編寫(xiě)findclass()方法及其獲取字節(jié)碼流的方式,使自定義類加載器編寫(xiě)更加簡(jiǎn)潔

    代碼解釋下

    package com.fs.classtest;import java.io.FileNotFoundException;/*** 自定義類的加載器* 1.開(kāi)發(fā)人員可以通過(guò)繼承抽象類java.lang.classLoader類的方式,實(shí)現(xiàn)自己的類加載器,以滿足一些特殊的需求** 2.在JDK1.2之前,在自定義類加載器時(shí),總會(huì)去繼承classLoader類并重寫(xiě)loadclass ()方法,從而實(shí)現(xiàn)自定義的類加載類,但是在JDK1.2之后已不再建議用戶去覆蓋loadclass ()方法,而是建議把自定義的類加載邏輯寫(xiě)在findclass ()方法中* * 3.在編寫(xiě)自定義類加載器時(shí),如果沒(méi)有太過(guò)于復(fù)雜的需求,可以直接繼承URLClassLoader類,這樣就可以避免自己去編寫(xiě)findclass()方法及其獲取字節(jié)碼流的方式,使自定義類加載器編寫(xiě)更加簡(jiǎn)潔*/ public class CustomClassLoader extends ClassLoader{@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {try {byte[] classFromCustomPath = getClassFromCustomPath(name);if (classFromCustomPath == null){throw new FileNotFoundException();}else {return defineClass(name,classFromCustomPath,0,classFromCustomPath.length);}}catch (Exception e){e.printStackTrace();}throw new ClassNotFoundException();}//自定義方法private byte[] getClassFromCustomPath(String name){//從自定義路徑加載指定類:細(xì)節(jié)省略//如果指定路徑的字節(jié)碼文件進(jìn)行了加密,者需要在此方法中進(jìn)行解密操作return null;}//測(cè)試一下public static void main(String[] args) {CustomClassLoader customClassLoader = new CustomClassLoader();try{Class<?> clazz = Class.forName("One", true, customClassLoader);Object obj = clazz.newInstance();System.out.println(obj.getClass().getClassLoader());}catch (Exception e){e.printStackTrace();}} }

    關(guān)于ClassLoader

    ClassLoader類,它是一個(gè)抽象類,其后所有的類加載器都繼承自ClassLoader(不包括啟動(dòng)類加載器)


    獲取ClassLoader的途徑

    方式一:獲取當(dāng)前類的ClassLoader
    clazz.getClassLoader ()
    方式二:獲取當(dāng)前線程上下文的classLoader
    Thread.currentThread ().getContextClassLoader ()
    方式三:獲取系統(tǒng)的classLoader
    ClassLoader.getSystemClassLoader ()
    方式四:獲取調(diào)用者的classLoader
    DriverManager.getCallerClassLoader ()

    二 雙親委派機(jī)制

    什么事雙親委派機(jī)制

    概念:

    Java虛擬機(jī)對(duì)class文件采用的是按需加載的方式,也就是說(shuō)當(dāng)需要使用該類時(shí)才會(huì)將它的class文件加載到內(nèi)存生成class對(duì)象。而且加載某個(gè)類的class文件時(shí),Java虛擬機(jī)采用的是雙親委派模式,即把請(qǐng)求交由父類處理,它是一種任務(wù)委派模式。

    工作原理:

    1)如果一個(gè)類加載器收到了類加載請(qǐng)求,它并不會(huì)自己先去加載,而是把這個(gè)請(qǐng)求委托給父類的加載器去執(zhí)行;

    2)如果父類加載器還存在其父類加載器,則進(jìn)一步向上委托,依次遞歸,請(qǐng)求最終將到達(dá)頂層的啟動(dòng)類加載器;

    3)如果父類加載器可以完成類加載任務(wù),就成功返回,倘若父類加載器無(wú)法完成此加載任務(wù),子加載器才會(huì)嘗試自己去加載,這就是雙親委派模式。

    代碼測(cè)試

    很有意思的代碼 - 沙箱安全機(jī)制

    為什么在我們自定義的java.lang.String中寫(xiě)main方法執(zhí)行報(bào)錯(cuò)?

    package java.lang;/*** 我自定義了一一個(gè)和java.lang.String* 那么我創(chuàng)建這個(gè)對(duì)象,到底是java的String 還是我們自己定義的String呢?*/ public class String {static {System.out.println("---自定義的java.lang.String被創(chuàng)建了--");}public static void main(String[] args) {System.out.println("我在自定義java.lang.String中運(yùn)行main方法");}/*因?yàn)橛捎陔p親委派機(jī)制,我們自定義的java.lang.String不會(huì)被加載,而且由BootStrapClassLoader引導(dǎo)類加載器加載java的String,而java的String中沒(méi)有main方法,所以就會(huì)提示找不到main方法*/}

    雙親委派機(jī)制

    再次舉列子

    優(yōu)勢(shì)

    避免類的重復(fù)加載
    保護(hù)程序安全,防止核心API被隨意篡改
    √自定義類: java. lang. string
    √自定義類: java . lang . shkstart

    體現(xiàn)保護(hù)優(yōu)勢(shì)的有意思的代碼測(cè)試

    報(bào)錯(cuò)就是因?yàn)殡p親委派機(jī)制的防止核心API被隨意篡改的保護(hù)機(jī)制

    沙箱安全機(jī)制

    自定義string類,但是在加載自定義string類的時(shí)候會(huì)率先使用引導(dǎo)類加載器加載,而引導(dǎo)類加載器在加載的過(guò)程中會(huì)先加載jdk自帶的文件(rt.jar包中java \lang \string.class),報(bào)錯(cuò)信息說(shuō)沒(méi)有main方法,就是因?yàn)榧虞d的是rt.jar包中的string類。這樣可以保證對(duì)java核心源代碼的保護(hù),這就是沙箱安全機(jī)制。

    三 其他

    在JVM中表示兩個(gè)class對(duì)象是否為同一個(gè)類存在兩個(gè)必要條件:

    類的完整類名必須一致,包括包名。加載這個(gè)類的classLoader(指classLoader實(shí)例對(duì)象)必須相同。

    換句話說(shuō),在JVM中,即使這兩個(gè)類對(duì)象(class對(duì)象)來(lái)源同一個(gè)class文
    件,被同一個(gè)虛擬機(jī)所加載,但只要加載它們的ClassLoader實(shí)例對(duì)象不同,那么這兩個(gè)類對(duì)象也是不相等的。

    對(duì)類加載器的引用

    JVM必須知道一個(gè)類型是由啟動(dòng)加載器加載的還是由用戶類加載器加載的。如果一個(gè)類型是由用戶類加載器加載的,那么JVM會(huì)將這個(gè)類加載器的一個(gè)引用作為類型信息的一部分保存在方法區(qū)中。當(dāng)解析一個(gè)類型到另一個(gè)類型的引用的時(shí)候,JVM需要保證這兩個(gè)類型的類加載器是相同的。

    類的主動(dòng)使用和被動(dòng)使用

    Java程序?qū)︻惖氖褂梅绞椒譃?主動(dòng)使用和被動(dòng)使用。

    主動(dòng)使用,又分為七種情況:

    創(chuàng)建類的實(shí)例訪問(wèn)某個(gè)類或接口的靜態(tài)變量,或者對(duì)該靜態(tài)變量賦值調(diào)用類的靜態(tài)方法反射(比如: Class.forName ( "com.fs. Test") )初始化一個(gè)類的子類Java虛擬機(jī)啟動(dòng)時(shí)被標(biāo)明為啟動(dòng)類的類JDK 7開(kāi)始提供的動(dòng)態(tài)語(yǔ)言支持:java .lang.invoke.MethodHandle實(shí)例的解析結(jié)果REF_getstatic、REF_putstatic、REF_invokestatic句柄對(duì)應(yīng)的類沒(méi)有初始化,則初始化

    除了以上七種情況,其他使用Java類的方式都被看作是對(duì)類的被動(dòng)使用,都不會(huì)導(dǎo)致類的初始化。

    總結(jié)

    以上是生活随笔為你收集整理的JVM学习笔记之-类加载子系统,类的加载与类的加载过程,双亲委派机制的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

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