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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

java class加载_Java 类加载

發布時間:2024/3/7 java 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 java class加载_Java 类加载 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

從一個詭異的問題說起

測試案例一:

packageecut.classloader;public classSun {protected static int a = 100;protected static intb ;protected static Sun instance = newSun() ;publicSun() {

a++;

b++;

}

}

packageecut.classloader;public classSunTest {

@SuppressWarnings("unused")public static voidmain(String[] args) {

Sun s=Sun.instance;

System.out.println( Sun.a );

System.out.println( Sun.b );

}

}

運行結果如下:

101

1

測試案例二:

packageecut.classloader;public classMoon {protected static Moon instance = newMoon() ;protected static int a = 100;protected static intb ;publicMoon() {

a++;

b++;

}

}

packageecut.classloader;public classMoonTest {

@SuppressWarnings("unused")public static voidmain(String[] args) {

Moon s=Moon.instance;

System.out.println( Moon.a );

System.out.println( Moon.b );

}

}

運行結果如下:

100

1

類的生命周期

1、?JVM 的生命周期 ( 在線程部分 ) :當一個 Java 程序執行時,將啟動一個 JVM 進程 ,當程序執行結束或拋出異常時 JVM 退出。

2、對象的聲明周期:?當使用 new 關鍵字 創建一個類的實例時,一個對象(實例)的生命周期即宣告開始

Student s = new Student();? // 將導致創建一個Student實例并對該實例中的實例屬性進行初始化

實例屬性的初始化:

private int? id = 0 ;

private String studentNo ;

{

studentNo = "ECUT-00000000" ;

}

private String name ;

public Student( String name ){

this.name = name ;

}

使用對象 ( 使用對象的 屬性 、方法 等 )

當某個對象不再被任何一個引用變量所引用時,它可能會被GC回收,如果被回收,它的生命周期將宣告結束

3、類的生命周期

java.lang.Object 是整個 Java 類繼承體系的根類

java.lang.Class 類 也繼承了 Object 類

java.lang.Class 類的實例表示正在運行的 Java 應用程序中的類和接口

java.lang.Object.class 表示正在運行的 Java 程序中的 那個 Object 類 對應的 Class 類型的對象

Java語言中萬事萬物都可以當作對象來對待,即使是一個類,也可以當對對象對待。

類的加載

將 字節碼文件( .class ) 讀入到 JVM 所管理的內存中

將 字節碼文件對應的類的數據結構 保存在方法區

最后生成一個與該類對應的 java.lang.Class 類型的對象 ( 在堆區 )

類的鏈接

連接是把已讀入到內存的類的二進制數據合并到Java運行時環境(JRE)中去。

連接又分為三個階段:驗證、準備、解析。

驗證:驗保證類有正確的內部結構,并且與其它類協調一致如果JVM 檢查到錯誤,就會拋出Error 對象。

類文件的結構檢查: 確保文件遵循Java 文件的固定格式

語義檢查: 確保類本身符合Java 語言的語法規定

字節碼驗證: 確保字節碼流可以被JVM 安全地執行

? 字節碼流代表Java 方法(含靜態和非靜態),它是被稱作操作碼的單字節指令組成的序列,每個操作碼后都跟著一個或多個操作數

? 字節碼驗證會檢查每個操作碼是否合法,即是否有合法的操作數二進制兼容的驗證: 確保相互引用的類之間協調一致

? 比如A 類中調用B 類的b() 方法,檢查B 中是否有b() 方法存在

準備:? 在準備階段,JVM 為類的靜態變量分配內存,并設置默認值(byte 、short 、int 默認值都是 0,long 默認值是 0L,float 默認值是 0.0F,double 默認值是 0.0,boolean 默認值是 false,char 默認值是 \u0000,? 引用類型的默認值是 null)。

解析: 將符號引用解析為直接引用

類的初始化

初始化階段,JVM執行類的初始化語句,為靜態變量賦予初始值

靜態變量的初始化途徑:在靜態變量的聲明處進行初始化,在靜態代碼塊中進行初始化

初始化代碼可能是(聲明變量時的賦值語句): protected static int a = 100 ;也可以是(靜態代碼塊):

static {

a = 10000 ;

}

類初始化的一般步驟

如果該類還沒有被加載和連接,那么先加載和連接該類

如果該類存在直接父類,但該父類還未初始化,則先初始化其直接父類

如果該類中存在初始化語句,則依次執行這些初始化語句

類的初始化時機

JVM 只有在首次主動使用某個類或接口時才會初始化它

被動使用不會導致本類的初始化

詭異的問題 解析測試案例:

packageecut.classloader;public classSun {protected static int a = 100 ;//鏈接(準備):0//初始化: a:100

protected static int b ;//鏈接(準備):0//初始化: b:0

protected static Sun instance = new Sun() ;//鏈接(準備):null//初始化: a:101 b:1

publicSun() {

a++;

b++;

}

}

packageecut.classloader;public classMoon {protected static Moon instance = new Moon() ;//鏈接(準備):null//初始化: a:1 b:1

protected static int a = 100 ;//鏈接(準備):0//初始化: a:100

protected static int b ;//鏈接(準備):0//初始化: b:1

publicMoon() {

a++;

b++;

}

}

類的使用

主動使用 會導致 類被初始化

a>、創建類的實例 ( new 、反射、反序列化 、克隆 )

b>、調用類的靜態方法

c>、訪問類 或 接口的 靜態屬性 ( 非常量屬性 )? ( 取值 或 賦值 都算 )

訪問類 或 接口 的 非編譯時常量,也將導致類被初始化:

public static final long time = System.currentTimeMillis();

d>、調用反射中的某個些方法,比如 Class.forName( "edu.ecut.Student" );

e>、初始化某個類時,如果該類有父類,那么父類將也被初始化

f>、被標記為啟動類的那些類(main)

被動使用 不會導致類被初始化

a>、程序中對編譯時常量的使用視作對類的被動使用

對于final 修飾的變量,如果編譯時就能確定其取值,即被看作編譯時常量

? 編譯時常量如: public static final int a = 2 * 3 ;

? JVM 的加載和連接階段,不會在方法區內為某個類的編譯時常量分配內存

對于final 修飾的變量,如果編譯時就不能確定其取值,則不被看作編譯時常量

? 非編譯時常量如: public static final long time = System.currentTimeMillis() ;

? 使用該類型的靜態變量將導致當前類被初始化( 主動使用)

b>、JVM初始化某個類時,要求其所有父類都已經被初始化,但是 該規則不適用 于 接口 類型

一個接口不會因為其子接口或實現類的初始化而初始化,除非使用了該接口的靜態屬性

c>、只有當程序訪問的靜態變量或靜態方法的確在當前類或接口定義時,

才能看作是對類或接口的主動使用:

比如使用了 Sub.method() ,而 method() 是繼承自 Base ,則只初始化 Base 類

d>、調用 ClassLoader 的 loadClass( ) 加載一個類,不屬于對類的主動使用

主動使用和被動使用測試案例一:

packageecut.classloader;public classPanda {//編譯時常量(對于final 修飾的變量,如果編譯時就能確定其取值,即被看作編譯時常量)

public static final String HOMETOWN = "中國";//非編譯時常量(對于final 修飾的變量,如果編譯時就不能確定其取值,則不被看作編譯時常量)

public static final long time =System.currentTimeMillis();public static inta ;static{

System.out.println("static code , a = " +a );

a= 100;

System.out.println("static code , a = " +a );

}

}

packageecut.classloader;public classPandaTest {public static voidmain(String[] args) {

System.out.println( Panda.HOMETOWN );//編譯時常量被動使用//System.out.println( Panda.a );//訪問靜態變量(不是常量) 主動使用//使用該類型的靜態變量將導致當前類被初始化( 主動使用)

System.out.println( Panda.time );//非編譯時常量主動使用,靜態代碼塊只執行一次因為初始化操作只執行一次

System.out.println( Panda.time );//只有第一次使用才完成初始化操作,所以值是固定的不變的

}

}

運行結果如下:

中國static code , a = 0

static code , a = 100

1522485492485

1522485492485

主動使用和被動使用測試案例二:

packageecut.classloader;public classInitTest {public static voidmain(String[] args) {//比如使用了 Child.hometown ,而 hometown是繼承自 Father ,則只初始化 Father 類//System.out.println(Child.hometown);//初始化某個類時,如果該類有父類,那么父類將也被初始化//System.out.println(Child.name);//new Father();//new Father();

newChild();newChild();

}

}classFather {protected staticString hometown ;static{

System.out.println("Father : static code block.");

hometown= "Sinaean";

}//new Fater()時靜態代碼塊最先執行,只執行一次

{ System.out.println( "Father : non-static code block." );}//每一次new Fater()都執行,僅此靜態代碼塊執行

publicFather(){

System.out.println("Father construction.");

}//每一次new Fater()都執行,最后執行

}class Child extendsFather {protected staticString name ;static{

System.out.println("Child : static code block.");

name= "Child";

}

{ System.out.println("Child : non-static code block.");}publicChild(){

System.out.println("Child construction.");

}

}

運行結果如下:

Father : staticcode block.

Child :staticcode block.

Father : non-staticcode block.

Father construction.

Child : non-staticcode block.

Child construction.

Father : non-staticcode block.

Father construction.

Child : non-staticcode block.

Child construction.

類的卸載:當一個類不再被任何對象所使用時,JVM會卸載該類。

類加載器

1、類加載器用來把類加載到JVM 中

從JDK 1.2 版本開始,類的加載過程采用父親委托機制

設loader 要加載A 類,則loader 首先委托自己的父加載器去加載A 類,如果父加載器能加載A 類則由父加載器加載,否則才由loader 本身來加載A類。

這種機制能更好地保證Java 平臺的安全性

父親委托機制中,每個類加載器都有且只有一個父加載器,除了JVM 自帶的根類加載器( Bootstrap Loader )

2、JVM 的三種主要類加載機制

全盤負責

當一個類加載器負責加載某個類時,該類所依賴和引用的其它類也將由當前的類加載器負責載入,除非顯式使用了另外一個類加載器來載入

父類委托

先讓父加載器加載某個類,只有父加載器無法加載該類時子加載器才加載

當需要加載某個類時,加載這個類的類加載器會將加載操作委托給父加載器

JVM 提供了 根 加載器 : Bootstrap Loader ,它 是 JVM 的一個組成部分 ( 由JVM的實現著實現 )

緩存機制

使用緩存把所有的被加載過的類緩存起來,當程序中需要用到某個類時,類加載器先從緩存中搜尋該類,如果緩存中不存在該類,系統將讀取該類對應的二進制數據并轉換成Class 對象并存入cache 中

這正是修改源文件后只有重啟一個JVM 才能看到修改后的執行效果的原因

3、JVM 自帶的類加載器

根類加載器(BootstrapLoader)

負責加載虛擬機的核心類庫,比如java.lang.* 等

從系統屬性sun.boot.class.path 所指定的目錄中加載類庫

該加載器沒有父加載器,它屬于JVM 的實現的一部分(用C++實現)

擴展類加載器(ExtClassLoader)

其父加載器為BootstrapLoader 類的一個實例

該加載器負責從java.ext.dirs 系統屬性所指定的目錄中加載類庫或者從JDK_HOME/jre/lib/ext 目錄中加載類庫

該加載器對應的類是純Java 類,其父類是java.lang.ClassLoader

系統類加載器(AppClassLoader)

也稱作應用類加載器,其父加載器默認為ExtClassLoader 類的一個實例

負責從CLASSPATH 或系統屬性java.class.path 所指定的目錄中加載類庫

它是用戶自定義類加載器的默認父加載器

其父類也是java.lang.ClassLoader

ClassLoader測試案例一:

packageecut.classloader;importjava.util.ArrayList;public classClassLoaderTest1 {public static voidmain(String[] args) {

Class> c = String.class; //java.lang.String

ClassLoader loader =c.getClassLoader();

System.out.println(loader);//null ( Bootstrap Loader )

Object o= new ArrayList<>(); //java.util.ArrayList

c=o.getClass();

loader=c.getClassLoader();

System.out.println(loader);//null ( Bootstrap Loader )

c= ClassLoaderTest1.class;

loader= c.getClassLoader(); //獲得 ClassLoaderTest1 這個類的類加載器

System.out.println(loader); //AppClassLoader//獲得 loader 這個 "類加載器" 的 父加載器

ClassLoader parent =loader.getParent();

System.out.println(parent);//ExtClassLoader

ClassLoader root=parent.getParent();

System.out.println( root );//null ( Bootstrap Loader )

}

}

運行結果如下:

null

nullsun.misc.Launcher$AppClassLoader@73d16e93

sun.misc.Launcher$ExtClassLoader@15db9742nul

4、類加載器的層次

注意這里的層次關系不是類與類的繼承關系

各層次的類加載器加載的類

ClassLoader測試案例二:

packageecut.classloader;importjava.util.Iterator;importjava.util.Properties;importjava.util.Set;public classClassLoaderTest2 {public static voidmain(String[] args) {

Properties props=System.getProperties();

System.out.println(props);

System.out.println("~~~~~~~~~~~~~~~~~~~~~~~");

Set keys =props.keySet();for(Object key : keys) {

Object value=props.get(key);

System.out.println(key+ " : " +value);

}

System.out.println("~~~~~~~~~~~~~~~~~~~~~~~");

Iterator it =keys.iterator();while(it.hasNext()) {

Object key=it.next();

Object value=props.get(key);

System.out.println(key+ " : " +value);

}

System.out.println("~~~~~~~~~~~~~~~~~~~~~~~");

System.out.println(System.getProperty("java.ext.dirs"));

System.out.println(System.getProperty("java.class.path"));

}

}

運行結果如下:

..........~~~~~~~~~~~~~~~~~~~~~~~C:\Program Files\Java\jdk1.8.0_121\jre\lib\ext;C:\Windows\Sun\Java\lib\ext

D:\java_workspace\Java\JavaAdvanced\bin

5、自定義類加載器

JVM 允許開發者開發自己的類加載器

擴展java.lang.ClassLoader 類即可

重寫其中的方法

ClassLoader 中的關鍵方法

Class loadClass(String name )該方法為ClassLoader 的入口點,根據指定二進制名稱來加載類

Class findClass( String name )根據二進制名稱來查找類(一般重寫該方法即可)

Class defineClass(String name, byte[] b, int off, int len)根據加載到的二進制數據返回一個Class 對象。

部分源碼:

public Class> loadClass(String name) throwsClassNotFoundException {return loadClass(name, false);

}protected Class> loadClass(String name, booleanresolve)throwsClassNotFoundException

{synchronized(getClassLoadingLock(name)) {//首先,檢查類是否已經加載。

Class> c =findLoadedClass(name);if (c == null) {long t0 =System.nanoTime();try{if (parent != null) {

c= parent.loadClass(name, false);//看父類加載器有沒有加載該類(父委托機制)

} else{

c= findBootstrapClassOrNull(name);//父類加載器為空,看根加載器(Bootstrap Loader)有沒有加載

}

}catch(ClassNotFoundException e) {//如果類沒有發現拋出ClassNotFoundException

}if (c == null) {//如果仍然沒有找到,然后調用findClass為了找到類。

long t1 =System.nanoTime();

c=findClass(name);//這是定義類裝入器;記錄統計數據

sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 -t0);

sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);

sun.misc.PerfCounter.getFindClasses().increment();

}

}if(resolve) {

resolveClass(c);

}returnc;

}

}protected Class> findClass(String name) throwsClassNotFoundException {throw newClassNotFoundException(name);

}

除了 Bootstrap Loader 之外,其它的所有的類加載器對應的類的父類都是 java.lang.ClassLoader,loadClass方法最終調用的是findClass方法,因此自定義加載器時應該繼承java.lang.ClassLoader并重寫findClass方法。

自定義加載器測試案例:

用記事本新建一個Student.java,再使用命令行生成Student.class文件

Student.java有包名,直接運行Java命令會無法加載主類,因為用Javac 雖然可以編譯但是沒有生成正確的目錄結構,包結構不對,main方法無法執行,應該帶著包一起編譯。并且運行java 命令需要在包名的上級目錄下運行,且帶上完整類名(包名.類名)

錯誤的編譯方式:

正確的編譯方式:

\

packageecut.classloader.entity;public classStudent{privateString name;private intid;public voidsetName(String name){this.name =name;

}publicString getName(){returnname;

}public void setId (intid){this.id =id;

}public intgetId(){returnid;

}public static voidmain(String[] args) {

System.out.println("Hello World");

}

}

packageecut.classloader;importjava.io.ByteArrayOutputStream;importjava.io.IOException;importjava.io.InputStream;importjava.nio.file.Files;importjava.nio.file.Path;importjava.nio.file.Paths;/*** 除了 Bootstrap Loader 之外,

* 其它的所有的類加載器對應的類的父類都是 java.lang.ClassLoader*/

public class EcutClassLoader extendsClassLoader {privateString path ;publicEcutClassLoader(String path) {super();this.path =path;

}

@Overrideprotected Class> findClass(final String name) throwsClassNotFoundException {

Class> c = null;

System.out.println("將要加載的類: " +name );

String s= name.replace( '.', '/' ) + ".class";

Path p=Paths.get( path , s );if( Files.exists( p ) ){try{

ByteArrayOutputStream baos= newByteArrayOutputStream();

InputStream in=Files.newInputStream( p );intn ;byte[] bytes = new byte[1024];while( ( n = in.read( bytes ) ) != -1){

baos.write( bytes ,0, n );

}final byte[] byteCode = baos.toByteArray(); //獲得 ByteArrayOutputStream 內部的數據

c= this.defineClass( name , byteCode , 0, byteCode.length );

}catch(IOException e) {

e.printStackTrace();

}

}else{throw new ClassNotFoundException( "類: " + name + " 未找到.");

}returnc ;

}

}

packageecut.classloader;importjava.lang.reflect.Field;public classEcutClassLoaderTest {public static void main(String[] args) throwsException {final String path = "D:/Amy";//創建一個自定義的類加載器 ( 實例 )

EcutClassLoader loader = newEcutClassLoader( path );final String className = "ecut.classloader.entity.Student";

Class> c =loader.loadClass( className );

System.out.println( c );

System.out.println( c.getName() );

System.out.println( c.getSimpleName() );

System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");

Object o=c.newInstance();

System.out.println( o );

Field idField= c.getDeclaredField( "id");

idField.setAccessible(true);

Object value= idField.get( o ); //o.id

System.out.println( value );

idField.set( o ,250 ); //o.id = 250 ;

value= idField.get( o ); //o.id

System.out.println( value );

}

}

運行結果如下:

將要加載的類: ecut.classloader.entity.Studentclassecut.classloader.entity.Student

ecut.classloader.entity.Student

Student~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ecut.classloader.entity.Student@4e25154f

0

250

java.net.URLClassLoader 類

是ClassLoader 的URL 版實現

該類也是系統類加載器類和擴展類加載器類的父類,這兩個類繼承了該類,該類又繼承了java.security.SecureClassLoader,而java.security.SecureClassLoader 則繼承了ClassLoader

URLClassLoader 功能比較強大,可以從本地文件系統中獲取二進制文件來加載類,也可以從遠程主機獲取二進制文件來加載類

常用構造

URLClassLoader( URL[] urls )

URLClassLoader( URL[] urls , ClassLoader parent )

獲得實例的靜態方法

static URLClassLoader newInstance(URL[] urls)

static URLClassLoader newInstance(URL[] urls, ClassLoader parent)

待解決問題

URLClassLoader

轉載請于明顯處標明出處

總結

以上是生活随笔為你收集整理的java class加载_Java 类加载的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。