深入JVM類載入器
01、類載入器原理
02、類載入器樹狀結構、雙親委託(代理)機制
03、自定義類載入器(檔案、網路、加密)
04、執行緒上下文類載入器
05、伺服器類載入原理
1、類載入器的作用
將class檔案位元組碼內容載入到記憶體中,並將這些靜態資料轉換成方法區中的執行時資料結構,在堆中生成一個代表這個類的java.lang.Class物件,作為方法區類資料的訪問入口。
類快取
標準的Java SE類載入器可以按要求查詢類,但一旦某個類載入到類載入器中,它將維持載入(快取)一段時間,不過JVM垃圾收集器可以回收這些Class物件。
2、類載入器的層次結構(樹狀結構)
引導類載入器(bootstarap class loader)(C)
- 它用來載入java的核心庫(JAVA_HOME/jre/lib/rt.jar或sun.boot.class.path路徑下的內容),是用原生程式碼來實現的,並不繼承自java.lang.ClassLoader。
- 載入擴充套件類和應用程式類載入器,並指定他們的父類載入器。
擴充套件類載入器(extensions class loader)(java)
- 用來載入java的擴充套件庫(JAVA_HOME/jre/ext/*.jar,或java.ext.dirs路徑下的內容)。Java虛擬機器的實現會提供一個擴充套件庫目錄,該類載入器在此目錄裡面查詢並載入java類。
- 由sun.misc.Launcher$ExtClassLoader實現
應用程式類載入器(application class loader)(java)
- 它根據java應用的類路徑(classpath ,java.class.path路徑類)
- 一般來說java應用的類都是由它來完成載入的
- 由sun.misc.Launcher$AppClassLoader實現
自定義類載入器(java寫的)
- 開發人員可以通過繼承java.lang.ClassLoader類的方法實現自己的類載入器,以滿足一些特殊的需求
Java.class.ClassLoader類介紹
作用:
-Java.lang.ClassLoader類的基本職責就是根據一個指定的類的名稱,找到或者生成期對應的位元組程式碼,然後從這些位元組程式碼中定義一個java類,既java.lang.Class類的一個例項。
-除此之外,ClassLoader還負責載入java應用的所需資源,如影象檔案和配置檔案等。
相關方法
- getparent() 返回該類載入器的父類載入器
- loadClass(String name) 載入名稱為name的類,返回結果是java.lang.Class類的例項
- findClass(String name)查詢名稱為name的類,返回結果是java.lang.Class類的例項
- findLoadedClass(String name) 查詢名稱為name的已經被載入過的類,返回結果是java.lang.Class類的例項
- defineClass(String name,byte[] b,int off,int len)把位元組陣列b中的內容轉換成java類,返回的結果是java.lang.Class類的例項,這個方法被宣告final的。
- resolveClass(Class
package com.lyy.test;
public class Demo2 {
public static void main(String[] args) {
System.out.println(ClassLoader.getSystemClassLoader());
System.out.println(ClassLoader.getSystemClassLoader().getParent());
System.out.println(ClassLoader.getSystemClassLoader().getParent().getParent()); //JAVA_HOME/jre/lib/rt.jar
System.out.println(System.getProperty("java.class.path"));
System.out.println("=====================================");
String a = "gaogao";
System.out.println(a.getClass().getClassLoader());
System.out.println(a);
}
}
類載入器的代理模式
代理模式
— 交給其他載入器來載入指定的類
雙親委託機制
— 就是某個特定的類載入器在接到載入類的請求時,首先將載入任務委託給父類載入器,一次追溯,知道最高的爺爺輩的,如果父類載入器可以完成類載入任務,就成功返回;只有父類載入器無法完成此載入任務時,才自己去載入。
— 雙親委託機制是為了保證java核心庫的型別安全。
這種機制就保證不會出現使用者自己能定義java.lang.Object類的情況。
類載入器除了使用者載入類,也是安全的最基本的屏障。
雙親委託機制是代理模式的一種
— 並不是所有的類載入器都採用雙親委託機制。
— Tomcat伺服器類載入器也使用代理模式,所不同的是它是首先嚐試去載入某個類,如果找不到再代理給父類載入器,這與一般類載入器的順序是相反的。
自定義類載入器
自定義類載入器的流程
— 首先檢查請求的型別是否已經被這個類載入器裝載到名稱空間中了,
如果已經裝載,直接返回;
— 委派類載入請求給父類載入器能夠完成,則返回父類載入器載入的Class例項;
— 呼叫本類載入器的findClass(…)方法,檢視獲取對應的位元組碼,如果獲取到,則呼叫defineClass(…)匯入型別到方法區;如果獲取不到對應的位元組碼或者其他原因失敗,返回異常loadClass(…)loadClass(…)轉拋異常,終止載入過程。
— 注意:被兩個類載入器載入的同一個類,JVM不認為是相同的類
package com.lyy.temp;
public class HelloWrold {
public static void main(String[] args) {
System.out.println("aaa");
}
}
package com.lyy.test;
/**
* 測試自定義類載入器(FileSystemClassLoader)
* @author 01
*
*/
public class Demo3 {
public static void main(String[] args) throws ClassNotFoundException {
FileSystemClassLoader load = new FileSystemClassLoader("E:/VIP");
FileSystemClassLoader load2 = new FileSystemClassLoader("E:/VIP");
Class<?> c = load.loadClass("com.lyy.temp.HelloWrold");
Class<?> c2 = load.loadClass("com.lyy.temp.HelloWrold");
Class<?> c3 = load2.loadClass("com.lyy.temp.HelloWrold");
Class<?> c4 = load2.loadClass("java.lang.String");
Class<?> c5 = load2.loadClass("com.lyy.test.Demo1");
System.out.println(c.hashCode());
System.out.println(c2.hashCode());
System.out.println(c3.hashCode());//同一個類,被不同的載入器載入,JVM認為也是不相同的類
System.out.println(c4.hashCode());
System.out.println(c3.getClassLoader());//自定義的類載入器
System.out.println(c4.getClassLoader());//引導類載入器
System.out.println(c5.getClassLoader());//系統預設的類載入器
}
}
檔案類載入器
package com.lyy.test;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* 自定義檔案系統載入器
* @author 01
*
*/
public class FileSystemClassLoader extends ClassLoader{
//com.lyy.test.User --> d:/myjava/com/lyy/test/User.class
private String rootDir;
public FileSystemClassLoader(String rootDir){
this.rootDir=rootDir;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class<?> c = findLoadedClass(name);
//首先查詢是否載入過該類,如果已經載入過,直接返回已經載入好的類,否則載入新的類
if(null != c){
return c;
}else{
ClassLoader parent = this.getParent();
c = parent.loadClass(name); //委派給父類載入
if(null != c){
return c;
}else{
byte[] classData = getClassData(name);
if(classData==null){
throw new ClassNotFoundException();
}else{
c = defineClass(name, classData,0, classData.length);
}
}
}
return c;
}
private byte[] getClassData(String name) { //com.lyy.test.User d:/myjava/com/lyy/test/User.class
String path = rootDir+"/"+name.replace('.', '/')+"class";
//IOUtils,可以使用它將流中的資料轉成位元組資料
InputStream is = null;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
is = new FileInputStream(path);
byte[] buffer = new byte[1024];
int temp = 0;
while((temp=is.read(buffer)) != -1){
baos.write(buffer,0,temp);
}
return baos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
return null;
}finally{
try {
if(is != null){
is.close();
}
if(baos != null){
baos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
網路類載入器
package com.lyy.test;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
/**
* 網路類載入器
* @author 01
*
*/
public class NetClassLoader extends ClassLoader{
//com.lyy.test.User --> www.baidu.com
private String rootUrl;
public NetClassLoader(String rootUrl){
this.rootUrl=rootUrl;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class<?> c = findLoadedClass(name);
//首先查詢是否載入過該類,如果已經載入過,直接返回已經載入好的類,否則載入新的類
if(null != c){
return c;
}else{
ClassLoader parent = this.getParent();
c = parent.loadClass(name); //委派給父類載入
if(null != c){
return c;
}else{
byte[] classData = getClassData(name);
if(classData==null){
throw new ClassNotFoundException();
}else{
c = defineClass(name, classData,0, classData.length);
}
}
}
return c;
}
private byte[] getClassData(String name) { //com.lyy.test.User d:/myjava/com/lyy/test/User.class
String path = rootUrl+"/"+name.replace('.', '/')+"class";
//IOUtils,可以使用它將流中的資料轉成位元組資料
InputStream is = null;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
URL url = new URL(path);
is = url.openStream();
byte[] buffer = new byte[1024];
int temp = 0;
while((temp=is.read(buffer)) != -1){
baos.write(buffer,0,temp);
}
return baos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
return null;
}finally{
try {
if(is != null){
is.close();
}
if(baos != null){
baos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
加密解密載入器(取反操作,DES對稱加密解密)
package com.lyy.test;
/**
* 測試簡單加密解密(取反)操作
* @author 01
*
*/
public class Demo4 {
public static void main(String[] args) throws Exception {
//測試取反操作
// int a = 3;//0000011
// System.out.println(Integer.toBinaryString(a^0xff));
//加密後的class檔案,正常的類載入器無法載入,報clasformatError
// FileSystemClassLoader load = new FileSystemClassLoader("E:/VIP/temp");
// Class<?> c = load.loadClass("HelloWrold");
// System.out.println(c);
DecrptClassLoader loader = new DecrptClassLoader("E:/VIP/temp");
Class<?> c = loader.loadClass("com.lyy.temp.HelloWrold");
System.out.println(c);
}
}
package com.lyy.test;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* 自定義檔案系統載入器
* @author 01
*
*/
public class FileSystemClassLoader extends ClassLoader{
//com.lyy.test.User --> d:/myjava/com/lyy/test/User.class
private String rootDir;
public FileSystemClassLoader(String rootDir){
this.rootDir=rootDir;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class<?> c = findLoadedClass(name);
//首先查詢是否載入過該類,如果已經載入過,直接返回已經載入好的類,否則載入新的類
if(null != c){
return c;
}else{
ClassLoader parent = this.getParent();
c = parent.loadClass(name); //委派給父類載入
if(null != c){
return c;
}else{
byte[] classData = getClassData(name);
if(classData==null){
throw new ClassNotFoundException();
}else{
c = defineClass(name, classData,0, classData.length);
}
}
}
return c;
}
private byte[] getClassData(String name) { //com.lyy.test.User d:/myjava/com/lyy/test/User.class
String path = rootDir+"/"+name.replace('.', '/')+"class";
//IOUtils,可以使用它將流中的資料轉成位元組資料
InputStream is = null;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
is = new FileInputStream(path);
byte[] buffer = new byte[1024];
int temp = 0;
while((temp=is.read(buffer)) != -1){
baos.write(buffer,0,temp);
}
return baos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
return null;
}finally{
try {
if(is != null){
is.close();
}
if(baos != null){
baos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
package com.lyy.test;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* 載入檔案系統加密後的class位元組碼的類載入器
* @author 01
*
*/
public class DecrptClassLoader extends ClassLoader{
//com.lyy.test.User --> d:/myjava/com/lyy/test/User.class
private String rootDir;
public DecrptClassLoader(String rootDir){
this.rootDir=rootDir;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class<?> c = findLoadedClass(name);
//首先查詢是否載入過該類,如果已經載入過,直接返回已經載入好的類,否則載入新的類
if(null != c){
return c;
}else{
ClassLoader parent = this.getParent();
c = parent.loadClass(name); //委派給父類載入
if(null != c){
return c;
}else{
byte[] classData = getClassData(name);
if(classData==null){
throw new ClassNotFoundException();
}else{
c = defineClass(name, classData,0, classData.length);
}
}
}
return c;
}
private byte[] getClassData(String name) { //com.lyy.test.User d:/myjava/com/lyy/test/User.class
String path = rootDir+"/"+name.replace('.', '/')+"class";
//IOUtils,可以使用它將流中的資料轉成位元組資料
InputStream is = null;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
is = new FileInputStream(path);
int temp = -1;
while((temp=is.read()) != -1){
baos.write(temp^0xff); //取反操作,相當於解密操作
}
return baos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
return null;
}finally{
try {
if(is != null){
is.close();
}
if(baos != null){
baos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
執行緒上下文類載入器
雙親委託機制以及類載入器的問題
一般情況下,保證同一個類中所關聯的其他類都是由當前類的類載入器所載入的。
比如,Class本身在Ext下找到,那麼他裡面new出來的一些類也就只能用ext去查找了(不會低一個級別),所以有些明明app可以找到的,卻找不到了。
JDBC API 他有實習那的driven部門(mysql/sql server),我們的JDBC API都是由Boot或者Ext來載入的,但是Service Prover卻是由EXT或者App來載入,那麼就有可能找不到driver了,在java領域中,其實只要分成這種Api-SPI(Service Provide Interface,特定廠商提供)的,都會遇到此問題。
常見的SPi 有JDBC、JCE、JNXP和JBI等。
package com.lyy.test;
/**
* 執行緒上下文類載入器
* @author 01
*
*/
public class Demo5 {
public static void main(String[] args) throws Exception {
ClassLoader loader = Demo5.class.getClassLoader();
System.out.println(loader);
ClassLoader laoder2 = Thread.currentThread().getContextClassLoader();
System.out.println(laoder2);
Thread.currentThread().setContextClassLoader(new FileSystemClassLoader("E:/VIP/"));
System.out.println(Thread.currentThread().getContextClassLoader());
Class<Demo1> c = (Class<Demo1>)Thread.currentThread().getContextClassLoader().loadClass("com.lyy.test.Demo1");
System.out.println(c);
System.out.println(c.getClassLoader());
}
}
TOMCAT伺服器的類載入機制