寫本篇的動因只是一段看起來很詭異的代碼,讓我感受有必要認識一下ClassLoaderhtml
----[Counter.java]-------------------------
public class Counter {
private static Counter sCounter = new Counter();//<---- tag1
public static int count = 10;//<---- tag2
private Counter() {
count++;
}
public static Counter getInstance() {
return sCounter;
}
}
----[Client.java]-------------------------
public class Client {
public static void main(String[] args) {
Counter counter = Counter.getInstance();
System.out.println(counter.count);//10
}
}
|-- 當tag1和tag2換一下位置,獲得的是11
複製代碼
上一篇講了Java虛擬機,關於類加載器一筆帶過,本篇詳細講一下
java文件經過javac能夠編譯成.class文件,類加載器就是將.calss加載到內存裏java
關於Class實例在堆中仍是方法區中?這裏找了一篇文章,講得挺深git
將字節碼(二進制流)載入方法區
堆內存中生成java.lang.Class對象,做爲方法區中該類各類數據的操做入口
|-- .class文件主要來源--------------------
-– 磁盤中直接加載
-– 網絡加載.class文件
-– 從zip ,jar 等文件中加載.class 文件
-– 從專有數據庫中提取.class文件
-– 將Java源文件動態編譯爲.class文件
複製代碼
驗證加載進來的字節流信息是否符合虛擬機規範github
[1].文件格式驗證: 字節流是否符合class文件格式規範
[2].元數據驗證: 是否符合java的語言語法的規範
[3].字節碼驗證:方法體進行校驗分析,保證運行時沒危害出現
[4].符號引用驗證 :常量池中的各類符號引用信息進行匹配性校驗
複製代碼
爲類靜態變量分配內存並設置爲[對應類型的初始值]數據庫
----[Counter.java]-------------------------
public class Counter {
private static Counter sCounter = new Counter();
public static int count = 1;
private Counter() {
count++;
}
public static Counter getInstance() {
return sCounter;
}
}
如上:在準備階段 count 的值爲int的默認值 = 0
複製代碼
常量池內的符號引用替換爲直接引用的過程,也就是字面量轉化爲指針。
主要解析:類,接口,字段,類方法,接口方法,方法類型,方法句柄和調用點限定符引用
編程
按順序查找
靜態變量
以及靜態代碼塊
對用戶自定義類變量的賦值
,數組
//如今count=0,調用後new Counter()時count++,變爲1
private static Counter sCounter = new Counter();
public static int count = 10;// 此時count賦值爲10
複製代碼
1.建立實例
2.訪問靜態變量或者對該靜態變量賦值
3.調用靜態方法
4.反射
5.初始化一個類的子類
6.JVM啓動時被標明爲啓動類(main)
---->[Shape類]------------------
public class Shape {
public static String color = "白色";
static {
System.out.println("-----初始化於Shape-----");
}
public static void draw() {
}
}
---->[Shape子類:Rect]------------------
public class Rect extends Shape {
public static int radius = 20;
static {
System.out.println("-----初始化於Rect-----");
}
}
new Shape(); //1.建立實例
String color = Shape.color;//2.訪問靜態變量
Shape.color = "黑色";//2.對該靜態變量賦值
Shape.draw();//3.調用靜態方法
Class.forName("classloader.Shape");//4.反射
Rect.radius = 10;//5.初始化一個類的子類
複製代碼
|-- 訪問編譯期靜態常量[不會]觸發初始化
|-- 訪問運行期靜態常量[會]觸發初始化
public class Shape {
...
public static final int weight = 1;
public static final int height = new Random(10).nextInt();
...
}
int w = Shape.weight;//編譯期靜態常量不會觸發初始化
int h = Shape.height;//運行期靜態常量會觸發初始化
|-- 其中height在運行時才能夠肯定值,訪問會觸發初始化
複製代碼
|-- 類初始化時並不會初始化它的接口
|-- 子接口初始化不會初始化父接口
|-- 聲明類變量時不會初始化
|-- 子類再調用父類的靜態方法或屬性時,子類不會被初始化
Shape shape;//聲明類變量,不會初始化
String color = Rect.color;//只初始化Shape
Rect.draw();//只初始化Shape
複製代碼
經過
ClassLoader.getSystemClassLoader()
能夠獲取系統類類加載器
debug一下,能夠看到系統類加載器:類名爲AppClassLoader
,因此也稱應用類加載器bash
ClassLoader loader = ClassLoader.getSystemClassLoader();
System.out.println(loader);
Shape shape = new Shape();
////sun.misc.Launcher$AppClassLoader@18b4aac2
ClassLoader loader = shape.getClass().getClassLoader();
String name = "toly";
ClassLoader loaderSting = name.getClass().getClassLoader();
System.out.println(loaderSting);//null
//可見String的類加載器爲null,先說一下,爲null時由Bootstrap類加載器加載
|-- 還有一點想強調一下,類加載器加載類後,不會觸發類的初始化
ClassLoader loader = ClassLoader.getSystemClassLoader();
Class<?> shapeClazz = loader.loadClass("classloader.Shape");//此時不初始化
Shape shape = (Shape) shapeClazz.newInstance();//建立實例時纔會初始化
複製代碼
這裏的父並非指繼承,而是ClassLoader類中有一個parent屬性是ClassLoader類型
因此是認乾爹,而不是親生的。就像Android中的ViewGroup和View的父子View關係
認了乾爹以後,有事先讓乾爹來擺平,乾爹擺不平,再本身來,都擺不平,就崩了唄。服務器
---->[ClassLoader#成員變量]----------------
private final ClassLoader parent;
---->[ClassLoader#構造函數一參]----------------
|-- 能夠在一參構造函數中傳入parent,認個乾爹,瞟了一下源碼,貌似是parent初始化的惟一途徑
protected ClassLoader(ClassLoader parent) {
this(checkCreateClassLoader(), parent);
}
|--關於父委託機制loadClass方法完美詮釋:
---->[ClassLoader#loadClass]------------------
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
---->[ClassLoader#loadClass(String,boolean)]------------------------------
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded---檢測類是否已加載
Class<?> c = findLoadedClass(name);
if (c == null) {//未被加載
long t0 = System.nanoTime();
try {
if (parent != null) {//有乾爹,讓乾爹來加載
c = parent.loadClass(name, false);
} else {//沒有乾爹,讓大佬Bootstrap類加載器加載
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
}
if (c == null) {//乾爹和大佬都加載不了
long t1 = System.nanoTime();
c = findClass(name);//我來親自操刀加載
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
複製代碼
Bootstrap ClassLoader : 引導類加載器(啓動類加載器/根類加載器)
|-- C++語言實現, 負責加載jre/lib路徑下的核心類庫
System.out.println(System.getProperty("sun.boot.class.path"));
//D:\M\JDK1.8\jre\lib\resources.jar;
// D:\M\JDK1.8\jre\lib\rt.jar;
// D:\M\JDK1.8\jre\lib\sunrsasign.jar;
// D:\M\JDK1.8\jre\lib\jsse.jar;
// D:\M\JDK1.8\jre\lib\jce.jar;
// D:\M\JDK1.8\jre\lib\charsets.jar;
// D:\M\JDK1.8\jre\lib\jfr.jar;
// D:\M\JDK1.8\jre\classes
Launcher$ExtClassLoader : 拓展類加載器
|-- Java語言實現,負責加載jre/lib/ext
System.out.println(System.getProperty("java.ext.dirs"));
//D:\M\JDK1.8\jre\lib\ext;C:\Windows\Sun\Java\lib\ext
Launcher$AppClassLoader : 系統類加載器
|-- Java語言實現,加載環境變量路徑classpath或java.class.path 指定路徑下的類庫
String property = System.getProperty("java.class.path");
//D:\M\JDK1.8\jre\lib\charsets.jar;
// D:\M\JDK1.8\jre\lib\deploy.jar;
...略若干jre的jar路徑...
// J:\FileUnit\file_java\base\out\production\classes; <--- 當前項目的輸出路徑
// C:\Program Files\JetBrains\IntelliJ IDEA 2018.1.3\lib\idea_rt.jar
複製代碼
---->[ClassLoader#構造函數]------------------------------------------
protected ClassLoader(ClassLoader parent) {
this(checkCreateClassLoader(), parent);
}
protected ClassLoader() {
this(checkCreateClassLoader(), getSystemClassLoader());
}
這裏能夠看出無參構造是默認乾爹是:getSystemClassLoader,也就是系統類加載器加載器
固然也可使用一參構造認乾爹
|-- 上面分析:在ClassLoader#loadClass方法中,當三個JVM的類加載器都找不到時
|-- 會調用findClass方法來初始化c ,那咱們來看一下findClass:
---->[在ClassLoader#findClass]------------------------
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
就問你一句:人家直接拋異常,你敢不覆寫嗎?
複製代碼
/**
* 做者:張風捷特烈
* 時間:2019/3/7/007:14:05
* 郵箱:1981462002@qq.com
* 說明:本地磁盤類加載器
*/
public class LocalClassLoader extends ClassLoader {
private String path;
public LocalClassLoader(String path) {
this.path = path;
}
@Override
protected Class<?> findClass(String name) {
byte[] data = getBinaryData(name);
if (data == null) {
return null;
}
return defineClass(name, data, 0, data.length);
}
/**
* 讀取字節流
*
* @param name 全類名
* @return 字節碼數組
*/
private byte[] getBinaryData(String name) {
InputStream is = null;
byte[] result = null;
ByteArrayOutputStream baos = null;
try {
if (name.contains(".")) {
String[] split = name.split("\\.");
name = split[split.length - 1];
}
String path = this.path + "\\" + name + ".class";
File file = new File(path);
if (!file.exists()) {
return null;
}
is = new FileInputStream(file);
baos = new ByteArrayOutputStream();
byte[] buff = new byte[1024];
int len = 0;
while ((len = is.read(buff)) != -1) {
baos.write(buff, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (is != null) {
is.close();
}
if (baos != null) {
result = baos.toByteArray();
baos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return result;
}
}
複製代碼
新建一個類HelloWorld,有一個公共方法say,注意包名和文件夾名微信
package com.toly1994.classloader;
public class HelloWorld {
public void say() {
System.out.println("HelloWorld");
}
}
複製代碼
使用LocalClassLoader加載剛纔的字節碼文件,經過反射調用say方法,執行無誤
這裏要提醒一下:使用javac編譯時的jdk版本,要和工程的jdk版本一致,否則會報錯
LocalClassLoader loader = new LocalClassLoader("G:\\Out\\java\\com\\toly1994\\classloader");
try {
Class<?> clazz = loader.loadClass("com.toly1994.classloader.HelloWorld");;
Constructor<?> constructor = clazz.getConstructor();
Object obj = constructor.newInstance();
Method say = clazz.getMethod("say");
say.invoke(obj);//HelloWorld
} catch (NoSuchMethodException | InvocationTargetException e) {
e.printStackTrace();
}
|-- 這裏能夠測試一下obj的類加載器
System.out.println(obj.getClass().getClassLoader());
//classloader.LocalClassLoader@6b71769e
複製代碼
這樣不管.java文件移到磁盤的哪一個位置,均可以的經過指定路徑加載
將剛纔的class文件放到服務器上:www.toly1994.com:8089/imgs/HelloW…
而後訪問路徑來讀取字節流,進行類的加載
核心也就是獲取到流,而後findClass中經過defineClass生成Class對象
/**
* 做者:張風捷特烈
* 時間:2019/3/7/007:14:05
* 郵箱:1981462002@qq.com
* 說明:網絡類加載器
*/
public class NetClassLoader extends ClassLoader {
private String urlPath;
public NetClassLoader(String urlPath) {
this.urlPath = urlPath;
}
@Override
protected Class<?> findClass(String name) {
byte[] data = getDataFromNet(urlPath);
if (data == null) {
return null;
}
return defineClass(name, data, 0, data.length);
}
private byte[] getDataFromNet(String urlPath) {
byte[] result = null;
InputStream is = null;
ByteArrayOutputStream baos = null;
try {
URL url = new URL(urlPath);
is = url.openStream();
baos = new ByteArrayOutputStream();
byte[] buff = new byte[1024];
int len = 0;
while ((len = is.read(buff)) != -1) {
baos.write(buff, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (is != null) {
is.close();
}
if (baos != null) {
result = baos.toByteArray();
baos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return result;
}
}
複製代碼
使用上基本一致
NetClassLoader loader = new NetClassLoader("http://www.toly1994.com:8089/imgs/HelloWorld.class");
try {
Class<?> clazz = loader.loadClass("com.toly1994.classloader.HelloWorld");
Constructor<?> constructor = clazz.getConstructor();
Object obj = constructor.newInstance();
Method say = clazz.getMethod("say");
say.invoke(obj);//HelloWorld
} catch (NoSuchMethodException | InvocationTargetException e) {
e.printStackTrace();
}
|-- 這裏能夠測試一下obj的類加載器
System.out.println(obj.getClass().getClassLoader());
//classloader.NetClassLoader@66d2e7d9
複製代碼
如今網絡和本地均可以,咱們讓本地的loader當作網絡加載的父親
---->[NetClassLoader#添加構造]------------------------
public NetClassLoader(ClassLoader parent, String urlPath) {
super(parent);
this.urlPath = urlPath;
}
---->[測試類]-----------------------------
LocalClassLoader localLoader = new LocalClassLoader("G:\\Out\\java\\com\\toly1994\\classloader");
//這裏講NetClassLoader的乾爹設置爲localLoader
NetClassLoader netLoader = new NetClassLoader(localLoader, "http://www.toly1994.com:8089/imgs/HelloWorld.class");
try {
Class<?> clazz = netLoader.loadClass("com.toly1994.classloader.HelloWorld");
Constructor<?> constructor = clazz.getConstructor();
Object obj = constructor.newInstance();
System.out.println(obj.getClass().getClassLoader());
//這裏打印classloader.LocalClassLoader@591f989e
Method say = clazz.getMethod("say");
say.invoke(obj);//HelloWorld
} catch (NoSuchMethodException | InvocationTargetException e) {
e.printStackTrace();
}
|-- 能夠看到,老爹LocalClassLoader能加載,做爲孩子的NetClassLoader就沒加載
|--- 如今將本地的[刪了],老爹LocalClassLoader加載不了,NetClassLoader本身搞
System.out.println(obj.getClass().getClassLoader());
classloader.NetClassLoader@4de8b406
複製代碼
如今應該很明白父委派機制是怎麼玩的了吧,若是NetClassLoader也加載不了,就崩了
[1].該類全部的實例都已經被GC。
[2].加載該類的ClassLoader實例已經被GC。
[3].該類的java.lang.Class對象沒有在任何地方被引用。
複製代碼
LocalClassLoader localLoader = new LocalClassLoader("G:\\Out\\java\\com\\toly1994\\classloader");
Class<?> clazz = localLoader.loadClass("com.toly1994.classloader.HelloWorld");
Constructor<?> constructor = clazz.getConstructor();
Object obj = constructor.newInstance();
System.out.println(obj.getClass().getClassLoader());
Method say = clazz.getMethod("say");
say.invoke(obj);//HelloWorld
|-- 使用上面的類加載器再加載一次com.toly1994.classloader.HelloWorld可見兩個class對象一致
System.out.println(clazz.hashCode());//1265210847
Class<?> clazz2 = localLoader.loadClass("com.toly1994.classloader.HelloWorld");
System.out.println(clazz2.hashCode());//1265210847
複製代碼
LocalClassLoader localLoader = new LocalClassLoader("G:\\Out\\java\\com\\toly1994\\classloader");
Class<?> clazz = localLoader.loadClass("com.toly1994.classloader.HelloWorld");
Constructor<?> constructor = clazz.getConstructor();
Object obj = constructor.newInstance();
Method say = clazz.getMethod("say");
say.invoke(obj);//HelloWorld
// 清除引用
obj = null; //清除該類的實例
localLoader = null; //清楚該類的ClassLoader引用
clazz = null; //清除該class對象的引用
複製代碼
深刻理解Java類加載器(ClassLoader)
Java --ClassLoader建立、加載class、卸載class
關於Class實例在堆中仍是方法區中?
項目源碼 | 日期 | 附錄 |
---|---|---|
V0.1--無 | 2018-3-7 | 無 |
發佈名:
JVM之類加載器ClassLoader
捷文連接:juejin.im/post/5c7a95…
筆名 | 微信 | |
---|---|---|
張風捷特烈 | 1981462002 | zdl1994328 |
個人github:github.com/toly1994328
個人簡書:www.jianshu.com/u/e4e52c116…
個人簡書:www.jianshu.com/u/e4e52c116…
我的網站:www.toly1994.com
1----本文由張風捷特烈原創,轉載請註明
2----歡迎廣大編程愛好者共同交流
3----我的能力有限,若有不正之處歡迎你們批評指證,一定虛心改正
4----看到這裏,我在此感謝你的喜歡與支持