類加載器ClassLoader-1

一,  類加載器深刻剖析

1,Java虛擬機與程序的生命週期

在以下幾種狀況下,Java虛擬機將結束生命週期:html

–執行了System.exit()方法java

–程序正常執行結束git

–程序在執行過程當中遇到了異常或錯誤而異常終止程序員

–因爲操做系統出現錯誤而致使Java虛擬機進程終止github

2,類的加載,連接,初始化

概念:

•加載:查找並加載類的二進制數據(java編譯後的.class文件)數據庫

•鏈接編程

–驗證:確保被加載的類的正確性數組

–準備:爲類的靜態變量分配內存,並將其初始化爲默認值安全

–解析:把類中的符號引用轉換爲直接引用網絡

•初始化:爲類的靜態變量賦予正確的初始值(=號後面的值)

圖示:

初始化的條件:

•Java程序對類的使用方式可分爲兩種

–主動使用

–被動使用

•全部的Java虛擬機實現必須在每一個類或接口被Java程序「首次主動使用」時才初始化他們

•主動使用(六種)

–建立類的實例

–訪問某個類或接口的靜態變量,或者對該靜態變量賦值

–調用類的靜態方法

–反射(如Class.forName(「com.shengsiyuan.Test」))

–初始化一個類的子類

–Java虛擬機啓動時被標明爲啓動類的類(Java Test),就是用java命令執行的那個帶有main方法入口的類。

被動使用

除了以上六種狀況,其餘使用Java類的方式都被看做是對類的被動使用,都不會致使類的初始化。

類的加載:

1,類加載作的事情:

•類的加載指的是將類的.class文件中的二進制數據讀入到內存中,將其放在運行時數據區的方法區內,而後在堆區建立一個該類的java.lang.Class對象,用來封裝類在方法區內的數據結構。

JVM運行時數據區的解釋:

運行時數據區能夠理解爲java虛擬機內存。

JVM就是一個特殊的進程, 咱們執行的java程序, 都運行在一個JVM進程中, 這個進程的做用就是加載class文件, 而且執行class文件中的代碼。 固然, 從一個class文件的加載, 到準備好可執行以前, 還有一段很長的路要走。 既然虛擬機做爲一個虛擬的計算機, 來執行咱們的程序:

那麼在執行的過程當中,必然要有地方存放咱們的代碼(class文件);

在執行的過程當中, 總會建立不少對象, 必須有地方存放這些對象;

在執行的過程當中, 還須要保存一些執行的狀態, 好比, 將要執行哪一個方法, 當前方法執行完成以後, 要返回到哪一個方法等信息;

因此, 必須有一個地方來保持執行的狀態。 上面的描述中, 「地方」指的固然就是內存區域, 程序運行起來以後, 就是一個動態的過程, 必須合理的劃份內存區域, 來存放各類數據(內存,用於存放程序運行時數據)。 

事實上,JVM在執行Java代碼時都會把內存分爲幾個部分,即數據區來使用,這些區域都擁有本身的用途,並隨着JVM進程的啓動或者用戶線程的啓動和結束創建和銷燬。接下去,經過下面的這幅圖,咱們一個一個細數一下JVM運行時的數據區結構。

線程私有的數據區

  程序計數器

·         做用 
記錄當前線程所執行到的字節碼的行號。字節碼解釋器工做的時候就是經過改變這個計數器的值來選取下一條須要執行的字節碼指令

·         意義 
JVM的多線程是經過線程輪流切換並分配處理器來實現的,對於咱們來講的並行事實上一個處理器也只會執行一條線程中的指令。因此,爲了保證各線程指令的安全順利執行,每條線程都有獨立的私有的程序計數器。

·         存儲內容 
當線程中執行的是一個Java方法時,程序計數器中記錄的是正在執行的線程的虛擬機字節碼指令的地址。 
當線程中執行的是一個本地方法時,程序計數器中的值爲空。

·         可能出現異常 
此內存區域是惟一一個在JVM上不會發生內存溢出異常(OutOfMemoryError)的區域。

  虛擬機棧

·         做用 
描述Java方法執行的內存模型。每一個方法在執行的同時都會開闢一段內存區域用於存放方法運行時所需的數據,成爲棧幀,一個棧幀包含如:局部變量表、操做數棧、動態連接、方法出口等信息。

·         意義 
JVM是基於棧的,因此每一個方法從調用到執行結束,就對應着一個棧幀在虛擬機棧中入棧和出棧的整個過程。

·         存儲內容 
局部變量表(編譯期可知的各類基本數據類型、引用類型和指向一條字節碼指令的returnAddress類型)、操做數棧、動態連接、方法出口等信息。 
值得注意的是:局部變量表所需的內存空間在編譯期間完成分配。在方法運行的階段是不會改變局部變量表的大小的。

·         可能出現的異常 
若是線程請求的棧深度大於虛擬機所容許的深度,將拋出StackOverflowError(棧溢出錯誤)異常。 
若是在動態擴展內存的時候沒法申請到足夠的內存,就會拋出OutOfMemoryError(內存溢出錯誤)異常。

本地方法棧

·         做用 
JVM所調用到的Nativa即本地方法服務。

·         可能出現的異常 
和虛擬機棧出現的異常很相像。

全部線程共有的數據區

  Java堆

·         做用 
全部線程共享一塊內存區域在虛擬機開啓的時候建立

·         意義 
一、存儲對象實例,更好地分配內存。 
二、垃圾回收(GC堆是垃圾收集器管理的主要區域。更好地回收內存。 
-存儲內容 
存放對象實例,幾乎全部的對象實例都在這裏進行分配。堆能夠處於物理上不連續的內存空間只要邏輯上是連續的就能夠。 
值得注意的是:在JIT編譯器[z1] [z2] 等技術的發展下,全部對象都在堆上進行分配已變得不那麼絕對。有些對象實例也能夠分配在棧中

·         可能出現的異常 
實現堆能夠是固定大小的,也能夠經過設置配置文件設置該爲可擴展的。 
若是堆上沒有內存進行分配並沒有法進行擴展時,將會拋出OutOfMemoryError異常。

  方法區

·         做用 
用於存儲運行時常量池已被虛擬機加載的類信息常量靜態變量即時編譯器編譯後的代碼等數據

·         意義 
對運行時常量池、常量、靜態變量等數據作出了規定。

·         存儲內容 
運行時常量池(具備動態性)已被虛擬機加載的類信息常量靜態變量即時編譯器編譯後的代碼等數據

·         可能出現的異常 
當方法區沒法知足內存分配需求時,將拋出OutOfMemoryError異常。

2,•加載.class文件的方式

–從本地系統中直接加載

–經過網絡下載.class文件

–從zipjar等歸檔文件中加載.class文件

–從專有數據庫中提取.class文件

–將Java源文件動態編譯爲.class文件

將Java源文件動態編譯爲.class文件的理解:

簡單理解:

就是在程序運行的時候,咱們只有.java文件,沒有已經編譯好的.class文件,這時,在程序運行的時候,咱們須要經過javax.tools. JavaCompiler接口得到系統編譯器,而且經過它的run方法讀取源代碼,編譯診斷,輸出class,這叫動態編譯。(系統運行以前就存在於磁盤等存儲介質中的一個個文件,叫作靜態的。系統運行的時候纔會有的,經過系統的運行在內存中產生的,就叫動態。系統已經運行產生並放到了存儲介質中的,叫靜態的。)

JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

int compilationResult = compiler.run(null, null, null, '/path/to/Test.java');

詳解:

Java動態編譯

程序產生過程

下圖展現了從源代碼到可運行程序的過程,正常狀況下先編譯(明文源碼到字節碼),後執行(JVM加載字節碼,得到類模板,實例化,方法使用)。本文來探索下當程序已經開始執行,但在.class甚至.java還未就緒的狀況下,程序如何得到指定的實現。這就是咱們下面的主題,動態編譯。 

相關類介紹

JavaCompiler: 負責讀取源代碼,編譯診斷,輸出class 
JavaFileObject: 文件抽象,表明源代碼或者編譯後的class 
JavaFileManager: 管理JavaFileObject,負責JavaFileObject的建立和保存位置 
ClassLoader: 根據字節碼,生成類模板

使用方式

因爲代碼在編譯的時候,類定義甚至類名稱還不存在,因此無法直接聲明使用的。只能定義一個接口代替之,具體實現留給後面的動態編譯。

public interface Printer {

    public void print();

}

源代碼的文件級動態編譯

java源碼以文件的形式存在本地,程序去指定路徑加載源文件。

String classPath = File2Class.class.getResource("/").getPath();

 

//在這裏咱們是動態生成定義,而後寫入文件。也能夠直接讀一個已經存在的文件

String str = "import classloader.Printer;"

    + "public class MyPrinter1 implements Printer {"

    + "public void print() {"

    + "System.out.println(\"test1\");"

+ "}}";

 

//將類的內容的字符串寫入MyPrinter1.java文件

FileWriter writer = new FileWriter(classPath + "MyPrinter1.java");

writer.write(str);;

writer.close();

 

//得到系統編譯器,獲取此平臺提供的 Java™ 編程語言編譯器工具。

JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

//獲取一個標準文件管理器實現的新實例

StandardJavaFileManager fileManager = compiler.getStandardFileManager(null,null, null);

//讀入源文件,獲取表示給定java文件的文件對象。

Iterable fileObject = fileManager.getJavaFileObjects(classPath + "MyPrinter1.java");

//編譯, 使用給定組件和參數建立編譯任務。該編譯可能沒有完成。

JavaCompiler.CompilationTask task = compiler.getTask[z3] (

                null, fileManager, null, null, null, fileObject);

task.call();//執行此編譯任務

fileManager.close();

 

//指定class路徑,默認和源代碼路徑一致,加載class,該類加載器用於從指向 jar文件和目錄的 URL 的搜索路徑加載類和資源。

//這裏假定任何以 '/' 結束的 URL 都是指向目錄的。

URLClassLoader classLoader = new URLClassLoader(new URL[]{new URL("file:" + classPath)});

Printer printer = (Printer)classLoader.loadClass("MyPrinter1").newInstance();//加載目標class文件,並建立類的實例

printer.print();

源代碼的內存級動態編譯

上一節是經過java源文件動態編譯加載的狀況,這節讓咱們看下源代碼和class全程都在內存中操做,如何實現動態編譯。

思路:

是生成源代碼對應的JavaFileObject時,從內存string讀取;

生成class對應的JavaFileObject時,以字節數組的形式存到內存。

JavaFileObject是一個interface, SimpleJavaFileObject是JavaFileObject的一個基本實現,當自定義JavaFileObject時,繼承SimpleJavaFileObject,而後改寫部分函數。 
1,自定義JavaSourceFromString,做爲源代碼的抽象文件(來自JDK API文檔)

/**

 *用於表示來自字符串的源的文件對象。

*/

public class JavaSourceFromString extends SimpleJavaFileObject {

/**

 *這個「文件」的源代碼。

 */

final String code;

/**

 *構造一個新的JavaSourceFromString。

 * @param name此文件對象表示的編譯單元的名稱

 * @param code該文件對象所表明的編譯單元的源代碼

 */

JavaSourceFromString(String name, String code) {

    super(URI.create("string:///" + name.replace('.','/') + Kind.SOURCE.extension), Kind.SOURCE);

    this.code = code;

}

 

@Override

public CharSequence getCharContent(boolean ignoreEncodingErrors) {

    return code;

}

}

2JavaClassFileObject,表明class的文件抽象

public class JavaClassFileObject extends SimpleJavaFileObject {

    //用於存儲class字節

    ByteArrayOutputStream outputStream;

 

    public JavaClassFileObject(String className, Kind kind) {

        super(URI.create("string:///" + className.replace('.', '/') + kind.extension), kind);

        outputStream = new ByteArrayOutputStream();

    }

 

    @Override

    public OutputStream openOutputStream() throws IOException {

        return outputStream;

    }

 

    public byte[] getClassBytes() {

        return outputStream.toByteArray();

    }

}

3ClassFileManager,修改JavaFileManager生成class的JavaFileObject的行爲,另外返回一個自定義ClassLoader用於返回內存中的字節碼對應的類模板

public class ClassFileManager extends ForwardingJavaFileManager {

 

    private JavaClassFileObject classFileObject;

    /**

     *建立ForwardingJavaFileManager的新實例。

     *

     * @param fileManager委託給這個文件管理器

     */

    protected ClassFileManager(JavaFileManager fileManager) {

        super(fileManager);

    }

 

    /**

     *獲取要輸出的JavaFileObject文件對象

     *表明給定位置中指定類名的指定類別。

     */

    @Override

    public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind,

    FileObject sibling) throws IOException {

        classFileObject = new JavaClassFileObject(className, kind);

        return classFileObject;

    }

 

    @Override

    //得到一個定製ClassLoader,返回咱們保存在內存的類

    public ClassLoader getClassLoader(Location location) {

        return new ClassLoader() {

            @Override

            protected Class<?> findClass(String name) throws ClassNotFoundException {

                byte[] classBytes = classFileObject.getClassBytes();//獲取class文件對象的字節數組

                return super.defineClass(name, classBytes, 0, classBytes.length);//定義Class對象

            }

        };

    }

}

4,下面來偷樑換柱,用自定義的JavaFileObject/JavaFileManager來動態編譯

String str = "import Printer;"

    + "public class MyPrinter2 implements Printer {"

    + "public void print() {"

    + "System.out.println(\"test2\");"

    + "}}";

//生成源代碼的JavaFileObject

SimpleJavaFileObject fileObject = new JavaSourceFromString("MyPrinter2", str);

JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

//被修改後的JavaFileManager

JavaFileManager fileManager = new ClassFileManager(compiler.getStandardFileManager(null, null, null));

//執行編譯

JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, null, null, null, Arrays.asList(fileObject));

task.call();

//得到ClassLoader,加載class文件

ClassLoader classLoader = fileManager.getClassLoader(null);

Class printerClass = classLoader.loadClass("MyPrinter2");

//得到實例

Printer printer = (Printer) printerClass.newInstance();

printer.print();

 

Java運行時動態生成class的方法(動態生成代理對象)

Java是一門靜態語言,一般,咱們須要的class在編譯的時候就已經生成了,爲何有時候咱們還想在運行時動態生成class呢?

由於在有些時候,咱們還真得在運行時爲一個類動態建立子類

應用場景(問題)的產生:

好比,編寫一個ORM框架,如何得知一個簡單的JavaBean是否被用戶修改過呢?

User爲例:

public class User {

    private String id;

    private String name;

 

    public String getId() {

        return id;

    }

 

    public void setId(String id) {

        this.id = id;

    }

 

    public String getName() {

        return name;

    }

 

    public void setName(String name) {

        this.name = name;

    }

}

其實UserProxy實現起來很簡單,就是建立一個User的子類,覆寫全部setXxx()方法,作個標記就能夠了:

public class UserProxy extends User {

    private boolean dirty;

 

    public boolean isDirty() {

        return this.dirty;

    }

 

    public void setDirty(boolean dirty) {

        this.dirty = dirty;

    }

 

    @Override

    public void setId(String id) {

        super.setId(id);

        setDirty(true);

    }

 

    @Override

    public void setName(String name) {

        super.setName(name);

        setDirty(true);

    }

}

可是這個UserProxy必須在運行時動態建立出來了,由於編譯時ORM框架根本不知道User類。

解決方式一:本身動態生成字節碼

如今問題來了,動態生成字節碼,難度有多大?

若是咱們要本身直接輸出二進制格式的字節碼,在完成這個任務前,必須先認真閱讀JVM規範第4章,詳細瞭解class文件結構。估計讀完規範後,兩個月過去了。

因此,第一種方法,本身動手,從零開始建立字節碼,理論上可行,實際上很難。

 

解決方式二:使用已有的一些能操做字節碼的庫,幫助咱們建立class

 

目前,可以操做字節碼的開源庫主要有CGLibJavassist兩種,它們都提供了比較高級的API來操做字節碼,最後輸出爲class文件。

好比CGLib,典型的用法以下:

Enhancer e = new Enhancer();

e.setSuperclass(...);

e.setStrategy(new DefaultGeneratorStrategy() {

    protected ClassGenerator transform(ClassGenerator cg) {

        return new TransformingGenerator(cg,

            new AddPropertyTransformer(new String[]{ "foo" },

                    new Class[] { Integer.TYPE }));

    }});

Object obj = e.create();

比本身生成class要簡單,可是,要學會它的API仍是得花大量的時間,而且,上面的代碼很難看懂對不對?

有木有更簡單的方法?

有!

解決方式三:動態生成.java文件,而後編譯該文件,在加載編譯後的.class文件

 

換一個思路,若是咱們能建立UserProxy.java這個源文件,再調用Java編譯器,直接把源碼編譯成class,再加載進虛擬機,任務完成!

畢竟,建立一個字符串格式的源碼是很簡單的事情,就是拼字符串嘛,高級點的作法能夠用一個模版引擎。

如何編譯?

Java的編譯器是javac,可是,在很早很早的時候,Java的編譯器就已經用純Java重寫了,本身能編譯本身,行業黑話叫「自舉」。從Java 1.6開始,編譯器接口正式放到JDK的公開API中,因而,咱們不須要建立新的進程來調用javac,而是直接使用編譯器API來編譯源碼。

使用起來也很簡單:

JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

int compilationResult = compiler.run[z4] (null, null, null, '/path/to/Test.java');

這麼寫編譯是沒啥問題,問題是咱們在內存中建立了Java代碼後,必須先寫到文件,再編譯,最後還要手動讀取class文件內容並用一個ClassLoader加載。

有木有更簡單的方法?

有!

其實Java編譯器根本不關心源碼的內容是從哪來的,你給它一個String看成源碼,它就能夠輸出byte[]做爲class的內容。

因此,咱們須要參考Java Compiler API的文檔,讓Compiler直接在內存中完成編譯,輸出的class內容就是byte[]

代碼改造以下:

Map<String, byte[]> results;

JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

StandardJavaFileManager stdManager = compiler.getStandardFileManager(null, null, null);

try (MemoryJavaFileManager manager = new MemoryJavaFileManager(stdManager))[z5]  {

    JavaFileObject javaFileObject = manager.makeStringSource(fileName, source);

    CompilationTask task = compiler.getTask(null, manager, null, null, null, Arrays.asList(javaFileObject));

    if (task.call()) {

        results = manager.getClassBytes();

    }

}

上述代碼的幾個關鍵在於:

1.    用自定義的MemoryJavaFileManager替換JDK默認的StandardJavaFileManager,以便在編譯器請求源碼內容時不是從文件讀取,而是直接返回String

2.    用自定義的MemoryOutputJavaFileObject替換JDK默認的SimpleJavaFileObject,以便在接收到編譯器生成的byte[]內容時不寫入class文件,而是直接保存在內存中

最後,編譯的結果放在Map<String, byte[]>中,Key是類名,對應的byte[]是class的二進制內容。

爲何編譯後不是一個byte[]呢?

由於一個.java的源文件編譯後可能有多個.class文件!只要包含了靜態類、匿名類等,編譯出的class確定多於一個。

如何加載編譯後的class呢?

加載class相對而言就容易多了,咱們只須要建立一個ClassLoader,覆寫findClass()方法:

class MemoryClassLoader extends URLClassLoader {

 

    Map<String, byte[]> classBytes = new HashMap<String, byte[]>();

 

    public MemoryClassLoader(Map<String, byte[]> classBytes) {

        super(new URL[0], MemoryClassLoader.class.getClassLoader());

        this.classBytes.putAll(classBytes);

    }

 

    @Override

    protected Class<?> findClass(String name) throws ClassNotFoundException {

        byte[] buf = classBytes.get(name);

        if (buf == null) {

            return super.findClass(name);

        }

        classBytes.remove(name);

        return defineClass(name, buf, 0, buf.length);

    }

}

除了寫ORM用以外,還能幹什麼?

能夠用它來作一個Java腳本引擎。實際上本文的代碼主要就是參考了Scripting項目的源碼。

完整的源碼呢?

在這裏:https://github.com/michaelliao/compiler,連Maven的包都給你準備好了!compiler-master(動態建立class的代碼).zip

也就200行代碼吧!動態建立class不是夢!

3,類加載示意圖:

•類的加載的最終產品是位於堆區中的Class對象

Class對象封裝了類在方法區內的數據結構,而且Java程序員提供了訪問方法區內的數據結構的接口

4,類加載器:Java虛擬機自帶的類加載器和用戶自定義的類加載器

 •有兩種類型的類加載器

Java虛擬機自帶的加載器

•根類加載器(Bootstrap)

•擴展類加載器(Extension)

•系統類加載器(System)

–用戶自定義的類加載器

java.lang.ClassLoader的子類

•用戶能夠定製類的加載方式,只要繼承ClassLoader類和重寫它的findClass方法就能夠了。固然,也能夠根據業務需求重寫其它的相關方法。

5,類的加載時機和錯誤報告時機

•類加載器並不須要等到某個類被「首次主動使用」時再加載它,首次主動使用只是初始化的條件,不是加載的條件。咱們能夠手動調用類加載器的loadClass方法,主動加載想加載的類。

•JVM規範容許類加載器在預料某個類將要被使用時就預先加載它,若是在預先加載的過程當中遇到了.class文件缺失或存在錯誤,類加載器必須在程序首次主動使用該類時才報告錯誤(LinkageError連接錯誤)

•若是這個類一直沒有被程序主動使用,那麼類加載器就不會報告錯誤

只有在首次主動使用被加載的類的時候,纔會報告類在加載過程當中發生的錯誤,LinkageError連接錯誤

類的連接:

•類被加載後,就進入鏈接階段。鏈接就是將已經讀入到內存的類的二進制數據合併到虛擬機的運行時環境中去。

1,類的驗證:

•類的驗證的內容

–類文件的結構檢查

–語義檢查

–字節碼驗證

–二進制兼容性的驗證

2,類的準備:

3,類的解析:

類的初始化:

•類的初始化步驟

類的初始化時機(首次主動使用)

主動使用(六種)

–建立類的實例

–訪問某個類或接口的靜態變量,或者對該靜態變量賦值

–調用類的靜態方法

–反射(如Class.forName(「com.shengsiyuan.Test」))

–初始化一個類的子類

–Java虛擬機啓動時被標明爲啓動類的類(Java Test)

除了上述六種情形,其餘使用Java類的方式都被看做是被動使用,不會致使類的初始化。

注意:

1,  只有當程序訪問的靜態變量或靜態方法確實在當前類或當前接口中定義時,才能夠認爲是對類或接口的主動使用,若是程序訪問的靜態變量或靜態方法是在該類的父類或其餘類中定義的,並不認爲是對該類的主動使用也不會初始化該類。而是對定義這個靜態變量或靜態方法的類的主動使用,會初始化這個定義了該靜態變量或方法的類。

2,  調用ClassLoader類的loadClass方法加載一個類,並非對類的主動使用,不會致使類的初始化。

接口的初始化

只有當程序首次使用接口的靜態變量時,纔會致使該接口的初始化;

類加載器:

類加載器的做用:

Java虛擬機自帶的3中類加載器:

根加載器默認加載rt.jar中的類,若是一個類是由根類加載器加載的,咱們去獲取這個類的類加載器對象,將返回一個空。由於根類加載器不對程序員暴露,java不容許程序員去獲取根類加載器。

自定義類加載器:

類加載器的父子關係示意圖:

類加載的父委託機制:

父委託機制:當某個類加載器要加載一個類時,他首先會去到本身的命名空間去查找這個類是否已經被加載,若是已經被加載,就返回這個類的Class對象的引用,若是沒有被它加載,它不會立刻去加載這個類,而是委託本身的父加載器去加載這個類,固然,父加載器也是先到本身的命名空間中去查找,看這個類是否已經加載,若是已經加載了,將這個類的Class對象的引用直接返回給到本身的子加載器,若是沒有加載,繼續向上委託,就這樣,直到根加載器,若是根加載器在本身的命名空間中找到了這個類的Class對象,則證實根加載器已經加載了這個類,根加載器會將這個類的Class對象的引用返回給到本身的子加載器,這樣一級一級返回,直到返回到要加載這個類的那個類加載器。若是根加載器仍是沒有加載過這個類,那麼它會嘗試去加載這個類,加載到了,就將這個類的Class對象的引用根據父子關係,依次接力向本身的子加載器返回,若是仍是沒有加載到,就會往下依次接力委託子加載器去加載,直到加載到這個類,而後返回這個類的Class對象的引用,依次傳遞給到要加載這個類的加載器。若是最終一直到要加載這個類的加載器都沒有加載到這個類,那麼程序拋出類找不到異常。

定義類加載器和初始類加載器的概念:

定義類加載器:成功加載了那個類的類加載器。那個類的Class對象是由它定義生成的,存放在它的命名空間中。

初始類加載器:定義類加載器以及它的全部子孫加載器都是初始類加載器。它們都能成功返回該類的Class對象的引用。

類加載器的父子關係的解釋:

包裝關係:類加載器是一個對象,而不是一個類。包裝關係只得是一個對象裏面持有另外一個對象得引用,在類加載器的父子關係中,就是子加載器持有了父加載器的引用。父加加載器對象的實例經過子加載器的構造方法傳入,若是不傳,默認的自定義加載器的父加載器就是系統類加載器。

父委託機制的優勢:

命名空間:

運行時包:

建立用戶自定義的類加載器:

package com.shengsiyuan.classloader;

 

import java.io.ByteArrayOutputStream;

import java.io.File;

import java.io.FileInputStream;

import java.io.InputStream;

 

public class MyClassLoader extends ClassLoader {

    private String name; // 類加載器的名字

 

    private String path = "d:\\"; // 加載類的路徑

 

    private final String fileType = ".class"; // class文件的擴展名

 

    public MyClassLoader(String name) {

        super(); // 讓系統類加載器成爲該類加載器的父加載器

 

        this.name = name;

    }

 

    public MyClassLoader(ClassLoader parent, String name) {

        super(parent); // 顯式指定該類加載器的父加載器

 

        this.name = name;

    }

 

    @Override

    public String toString() {

        return this.name;

    }

 

    public String getPath() {

        return path;

    }

 

    public void setPath(String path) {

        this.path = path;

    }

 

    @Override

    public Class<?> findClass(String name) throws ClassNotFoundException {

        byte[] data = this.loadClassData(name);

 

        return this.defineClass(name, data, 0, data.length);

    }

 

    private byte[] loadClassData(String name) {

        InputStream is = null;

        byte[] data = null;

        ByteArrayOutputStream baos = null;

 

        try {

            this.name = this.name.replace(".", "\\");

 

            is = new FileInputStream(new File(path + name + fileType));

 

            baos = new ByteArrayOutputStream();

 

            int ch = 0;

 

            while (-1 != (ch = is.read())) {

                baos.write(ch);

            }

 

            data = baos.toByteArray();

        } catch (Exception ex) {

            ex.printStackTrace();

        } finally {

            try {

                is.close();

                baos.close();

            } catch (Exception ex) {

                ex.printStackTrace();

            }

        }

 

        return data;

    }

 

    public static void main(String[] args) throws Exception {

        MyClassLoader loader1 = new MyClassLoader("loader1");

 

        loader1.setPath("d:\\myapp\\serverlib");

 

        MyClassLoader loader2 = new MyClassLoader(loader1, "loader2");

 

        loader2.setPath("d:\\myapp\\clientlib");

 

        MyClassLoader loader3 = new MyClassLoader(null, "loader3");

 

        loader3.setPath("d:\\myapp\\otherlib");

 

        test(loader2);

        test(loader3);

    }

 

    public static void test(ClassLoader loader) throws Exception {

        Class clazz = loader.loadClass("Sample");

 

        Object object = clazz.newInstance();

    }

 

}

 

package com.shengsiyuan.classloader;

 

public class Dog {

    public Dog() {

        System.out.println("Dog is loaded by : " + this.getClass().getClassLoader());

    }

}

 

package com.shengsiyuan.classloader;

 

public class Sample {

    public int v1 = 1;

 

    public Sample() {

        System.out.println("Sample is loaded by: " + this.getClass().getClassLoader());

 

        new Dog();

    }

}

 

 

不一樣類加載器的命名空間關係:

反射訪問:不建立該類型的引用,直接經過該類的Class對象去獲取該類的成員屬性,方法等的描述對象,而後操做。

3,類的卸載:

由用戶自定義的類加載器加載的類可卸載,由虛擬機自帶的類加載器加載的類不可卸載;

示例:

 [z1]JIT just in time 的縮寫, 也就是即時編譯編譯器。使用即時編譯器技術,可以加速 Java 程序的執行速度。下面,就對該編譯器技術作個簡單的講解。

首先,咱們你們都知道,一般經過 javac 將程序源代碼編譯,轉換成 java 字節碼,JVM 經過解釋字節碼將其翻譯成對應的機器指令,逐條讀入,逐條解釋翻譯。很顯然,通過解釋執行,其執行速度必然會比可執行的二進制字節碼程序慢不少。爲了提升執行速度,引入了 JIT 技術。

在運行時 JIT 會把翻譯過的機器碼保存起來,以備下次使用,所以從理論上來講,採用該 JIT 技術能夠接近之前純編譯技術。

 [z2]JIT程序有兩種運行方式:靜態編譯與動態解釋。靜態編譯的程序在執行前所有被翻譯爲機器碼,而解釋執行的則是一句一句邊運行邊翻譯。

Java字節碼(包括須要被解釋的指令的程序)轉換成能夠直接發送給處理器的指令的程序。當你寫好一個Java程序後,源語言的語句將由Java編譯器編譯成字節碼,而不是編譯成與某個特定的處理器硬件平臺對應的指令代碼(好比,IntelPentium微處理器或IBMSystem/390處理器)。字節碼是能夠發送給任何平臺而且能在那個平臺上運行的獨立於平臺的代碼。

 [z3]

參數:

Writer out - 用於來自編譯器的其餘輸出的 Writer;若是爲 null,則使用 System.err

JavaFileManager fileManager - 文件管理器;若是爲 null,則使用編譯器的標準文件管理器

DiagnosticListener<? super JavaFileObject> diagnosticListener - 診斷偵聽器;若是爲 null,則使用編譯器的默認方法報告診斷信息

Iterable<String> options - 編譯器選項;null 表示沒有選項

Iterable<String> classes - 類名稱(用於註釋處理),null 表示沒有類名稱

Iterable<? extends JavaFileObject> compilationUnits - 要編譯的編譯單元;null 表示沒有編譯單元

返回:

表示編譯的對象,編譯任務對象

 

 [z4]

int run(InputStream in,
        OutputStream out,
        OutputStream err,

        String... arguments)

使用給定 I/O 通道和參數運行工具。按照慣例,工具若是運行成功,則返回 0;若是出現錯誤,則返回非 0 值。任何生成的診斷都將以某種未指定的格式寫入 out 或 err。

參數:

in - 「標準」輸入;若是爲 null,則使用 System.in

out - 「標準」輸出;若是爲 null,則使用 System.out

err - 「標準」錯誤;若是爲 null,則使用 System.err

arguments - 要傳遞給工具的參數,要編譯的java文件。

返回:

若是成功,則返回 0;不然返回非 0 值

拋出:

NullPointerException - 若是參數數組包含任何 null 元素。

 [z5]

這是Try-with-resources結構。是java7中一個新的異常處理機制,它可以很容易地關閉在try-catch語句塊中使用的資源。

應用場景:用於關閉資源。是一個可以確保資源能被正確地關閉的強大方法。

特色:try-with-resources 語句會確保在try語句結束時關閉全部資源。實現了java.lang.AutoCloseablejava.io.Closeable的對象均可以作爲資源。

語法:將資源對象的建立寫在try後面的括號中,括號中能夠聲明多個資源對象,用;號隔開,最後一個不用加分號。資源被關閉的順序與它們被建立的順序相反。

注意:try-with-resources 也能夠有catch和finally語句塊,就像使用一個普通的try語句同樣。在try-with-resources 語句中,catch或者finally將在資源被關閉後執行。若是沒有catch語句塊,try中的異常將被拋出,可是從try-with-resources拋出的異常被禁止。在java7或更晚的版本中,咱們能夠獲取到這些被禁止的異常。

相關文章
相關標籤/搜索