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 类加载的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: PHS定位技术及业务应用研究(图)
- 下一篇: 美国交通事故分析(2017)(项目练习_