欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

JVM 类加载器原理和自定义类加载器

最编程 2024-03-09 12:13:52
...


类加载器原理

JVM将class文件字节码文件加载到内存中, 并将这些静态数据转换成方法区中的运行时数据结构,在堆中生成一个代表这个类的java.lang.Class

对象,作为方法区类数据的访问入口。

类缓存

标准的Java SE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过,JVM垃圾收集器可以回收这些Class过象。

类加载器数状结构

JVM类加载器原理与自定义类加载器_加载

引导类加载器(bootstrap class loader)

它用来加载Java的核心库(JAVA_HOME/jre/lib/rt.jar或sun.boot.class.Path路径下的内容),是用原生代码来实现的,并不继承自​​java.lang.classloader​​。

加载扩展类和应用程序类加载器,并指定他们的父类加载器

扩展类加载器(extensions class loader)

用来加载Java的扩展库(​​JAVA_HOME/jre/ext/*.jar​​或​​java.ext.dirs​​路径下的内容)。

Java虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载Java类。

由​​sun.misc.Launcher$ExtClassLoader​​实现。

应用程序类加载器(application class loader)

它根据Java应用的类路径(classpath,java.class.path类。

一般来说,Java应用的类都是由它来完成加载的。

由​​sun.misc.Launcher$AppClassLoader​​实现。

自定义类加载器

开发人员可以通过继承​​java.lang.ClassLoader​​类的方式实现自己的类加载器,以满足一些特殊的需求。

java.lang.ClassLoader

基本职责就是根据一个指定的类的名称,找到或者生成其对应的字节代码,然后从这些字节代码中定义出一个Java类,即java.lang.Class类的一个实例。

除此之外,ClassLoader还负责加载Java应用所需的资源,如图像文

件和配置文件等。

相关方法:

getParent()     返回委托的父类加载器。
loadClass(String name, boolean resolve) 使用指定的二进制名称来加载类。
findClass(String name) 使用指定的二进制名称查找类。
findLoadedClass(String name) 如果Java虚拟机已将此加载器记录为具有给定二进制名称的某个类的启动加载器,则返回该二进制名称的类。
defineClass(String name, byte[] b, int off, int len) 将一个 byte 数组转换为 Class 类的实例。
resolveClass(Class<?> c) 链接指定的类。

类加载器的代理模式–双亲委托机制

代理模式–交给其他加载器来加载指定的类。

双亲委托机制

就是某个特定的类加载器在接到加载器的请求时,首先将加载任务委托给父类加载器,依次追溯,直到最高的爷爷辈,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。

双亲委托机制是为了保证Java核心库的类型安全。这种机制就保证不会出现用户自己能定义java.lang.Object类的情况。

类加载器除了用于加载类,也是安全的最基本的屏障。

双亲委托机制是代理模式的一种。

**并不是所有的类加载器都采用双亲委派机制。**tomoat服务器类加载器也使用代理模式,所不同的是它是首先尝试去加载某个类,如果找不到再代理给父类加载器。这与一般类加载器的顺序是相反的。

双亲委派机制Demo:

自定义的java.lang.String

package java.lang;

public class String {
@Override
public String toString() {
return "defineString";
}
}
package JVMProcess;

public class LoadClass {
public static void main(String[] args) {

test();

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

String a = "LYN";
//类加载时,采用双亲委派机制,先加载父类,如果没有,再加载子类。
//实际上加载的是jdk自己提供的包,并没有加载自己定义的java.lang.String
System.out.println(a.getClass().getClassLoader());
System.out.println(a);

}


public static void test(){
System.out.println(ClassLoader.getSystemClassLoader());//应用类加载器
System.out.println(ClassLoader.getSystemClassLoader().getParent());//扩展类加载器
//引导类加载器 JAVA_HOME/jre/lib/rt.jar
System.out.println(ClassLoader.getSystemClassLoader().getParent().getParent());

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

}

运行结果:

sun.misc.Launcher$AppClassLoader@5b02a6
sun.misc.Launcher$ExtClassLoader@10aefdb
null
G:\program\javase\JVM\bin
#####################
null
LYN

自定义类加载器

继承:java.lang.ClassLoader

首先检查请求的类型是否已经被这个类装载器装载到命名空间中了

如果已经装载,直接返回;

如果没有装载,委派类加载请求给父类加载器,如果父类加载器能够完成,则返回父类加载器的Class实例。

如果父类没有装载,再调用本类加载器的findClass(…)方法,试图获取对应的字节码,如果获取得到,则调用defineClass(…)导入类型到方法区;

如果获取不到对应的字节码或者其他原因失败,返回异常给loadclass(…),loadclass(…)转抛异常,终止加载过程。

注意:被两个类加载器加载的同一个类,JVM不认为是相同的类。

文件系统类加载器Demo

package JVMProcess;

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

/**
* 自定义文件系统类加载器
*/
public class FileSystemClassLoader extends ClassLoader{
//com.lgd.User --> d:/myjava/ com/lgd/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(c!=null){
return c;
} else {
//获得他的父类,让父类去加载去加载
ClassLoader parent = this.getParent(); //获得appclassloader

//采用的是双亲委派机制
try {
c = parent.loadClass(name);//委派给父类加载
} catch (Exception e) {
}

//如果不为空,返回父类加载。
if(c!=null)
{
return c;
}else{

byte[] classData = getClassData(name);

if(classData == null)
{
throw new ClassNotFoundException();
}else {
c = defineClass(name, classData, 0,classData.length);
}
}

}
return c;
}

//com.lgd.User --> d:/myjava/ com/lgd/User.class
private byte[] getClassData(String classname)
{

String path = rootDir+"/"+classname.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 (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally
{
try {
if(is!=null)
{
is.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if(baos!=null)
{
baos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
}
package JVMProcess;
/**
* 测试自定义文件系统
* @author liguodong
*/

public class TestFSClassLoader {

public static void main(String[] args) {
//加载器
FileSystemClassLoader loader= new FileSystemClassLoader("G:/program/java/hello/bin");

//另一个加载器
FileSystemClassLoader loader2= new FileSystemClassLoader("G:/program/java/hello/bin");

try {
Class<?> c = loader.loadClass("hello.hello");
System.out.println(c);//class hello.hello

//c与c2即是同一个类,也是同一个类加载器,所以他们是同一个对象。
Class<?> c2 = loader.loadClass("hello.hello");//可以加载多次
System.out.println(c+"-->"+c.hashCode());
System.out.println(c2+"-->"+c2.hashCode());

//同一个类被不同的类加载器加载,JVM认为也是不同的机制
Class<?> c3 = loader2.loadClass("hello.hello");
System.out.println(c3+"-->"+c3.hashCode());
//自定义类加载器
System.out.println("-->"+c3.getClassLoader());


Class<?> c4 = loader2.loadClass("java.lang.String");
System.out.println(c4+"-->"+c4.hashCode());
//引导类加载器
System.out.println("-->"+c4.getClassLoader());


Class<?> c5 = loader2.loadClass("JVMProcess.Demo");
System.out.println(c5+"-->"+c5.hashCode());
//应用类加载器
System.out.println(c5.getClassLoader());//系统默认的类加载器

} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}

运行结果:

class hello.hello

class hello.hello-->13078969
class hello.hello-->13078969

class hello.hello-->27959193
-->JVMProcess.FileSystemClassLoader@3020ad

class java.lang.String-->25675463
-->null

class JVMProcess.Demo-->6164599
sun.misc.Launcher$AppClassLoader@cb6009

网络类加载器Demo

package JVMProcess;

/**
* 自定义网络类加载器
* @author liguodong
*/

import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;

public class NetClassLoader extends ClassLoader{
//com.lgd.User --> www.google.com/myjava com/lgd/User.class
private String rootUrl;
public NetClassLoader(String rootUrl){
this.rootUrl = rootUrl;
}

//重写方法
protected Class<?> findClass(String name) throws ClassNotFoundException
{
Class<?> c = findLoadedClass(name);//查找已加载的类
//应该要先查询有没有加载过这个类。如果已经加载,不为空,则直接返回加载好的类。
//如果没有,则加载新的类。
if(c!=null)
{
return c;
}
else {
ClassLoader parent = this.getParent(); //获得appclassloader
try {
c = parent.loadClass(name);//委派给父类加载
} catch (Exception e) {
}

if(c!=null)
{
return c;
}
else {

byte[] classData = getClassData(name);
if(classData == null)
{
throw new ClassNotFoundException();
}else {
c = defineClass(name, classData, 0,classData.length);
}
}

}
return c;
}

//com.lgd.User --> www.google.com/myjava com/lgd/User.class
private byte[] getClassData(String classname)
{
String path = rootUrl+"/"+classname.replace('.', '/')+".class";
//IOUtils,可以使用它将流中的数据装换成字节数组
InputStream is = null;
//字节输出流
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
URL url = new URL(path);
is = url.openStream();//通过url打开一个输入流

byte[] buffer = new byte[1024];
int temp = 0;
while((temp = is.read(buffer))!=-1)
{
baos.write(buffer,0,temp);
}
return baos.toByteArray();

} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally
{
try {
if(is!=null)
{
is.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if(baos!=null)
{
baos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
}

加密解密类加载器Demo

首先加密类文件:

package JVMProcess;

import java.io.FileInputStream;
import java.io.FileOutputStream;

/**
* 加密工具类
* @author liguodong
*/

public class EncrptUtil {

public static void main(String[] args) {
//加密
encrypt("G:/program/java/hello/bin/hello/HelloWorld.class",
"G:/program/java/hello/bin/temp/hello/HelloWorld.class");
}

public static void encrypt(String src,String dest)
{
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream(src);
fos = new FileOutputStream(dest);

int temp = -1;

while((temp=fis.read()) != -1)
{
//System.out.println("--");
//System.out.println(temp^0xff);
fos.write(temp^0xff);//取反操作,相当于加密

}

//System.out.println("--");
} catch (Exception e) {
e.printStackTrace();
}

try {
if(fis != null)
{
fis.close();
}
} catch (Exception e) {
}
try {
if(fos != null)
{
fos.close();
}
} catch (Exception e) {
}
}
}

然后编写解密的类加载器:

package JVMProcess;

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

/**
* 加载文件系统中加密后的class字节码的类加载器
* @author liguodong
*/

public class DecrptClassLoader extends ClassLoader{
//com.lgd.User --> d:/myjava/ com/lgd/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(c!=null){
return c;
}else
{
ClassLoader parent = this.getParent(); //获得appclassloader
try {
c = parent.loadClass(name);//委派给父类加载
}catch (Exception e) {
}

if(c!=null){
return c;
}
else
{
byte[] classData = getClassData(name);
if(classData == null){

throw new ClassNotFoundException();
}else {
c = defineClass(name, classData, 0,classData.length);
}
}

}
return c;
}

//com.lgd.User --> d:/myjava/ com/lgd/User.class
private byte[] getClassData(String classname)
{

String path = rootDir+"/"+classname.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);//取反操作,解密操作
//System.out.println(temp^0xff);
}
/*byte[] buffer = new byte[1024];
int temp = 0;
while((temp = is.read(buffer))!=-1)
{
baos.write(buffer,0,temp);
}*/
return baos.toByteArray();

} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();

}finally
{
try {
if(is!=null)
{
is.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if(baos!=null){
baos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
}

测试:

package JVMProcess;

/**
* 测试简单的加密解密(取反)操作
* @author liguodong
*
*/
public class TestEDClassLoader {

public static void main(String[] args) throws ClassNotFoundException {
//int a = 3;//00000011
//System.out.println(Integer.toBinaryString(a^0xff));//11111100

//加密后的class文件,正常的类加载器无法加载,报classFormatError
/*
FileSystemClassLoader loader = new FileSystemClassLoader("G:/program/java/hello/bin");
Class<?> c = loader.loadClass("hello.HelloWorld");
System.out.println(c);*/


DecrptClassLoader loader = new DecrptClassLoader("G:/program/java/hello/bin/temp");
Class<?> c = loader.loadClass("hello.HelloWorld");
System.out.println(c);
}
}

运行结果:

class hello.HelloWorld



推荐阅读