JVM 类加载器原理和自定义类加载器
类加载器原理
JVM将class文件字节码文件加载到内存中, 并将这些静态数据转换成方法区中的运行时数据结构,在堆中生成一个代表这个类的java.lang.Class
对象,作为方法区类数据的访问入口。
类缓存
标准的Java SE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过,JVM垃圾收集器可以回收这些Class过象。
类加载器数状结构
引导类加载器(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
上一篇: JAVA 什么是类加载器
下一篇: Java 类加载器机制和应用
推荐阅读
-
01-JVM 学习记录-类加载器
-
Android 开发中 nodpi、xhdpi、hdpi、mdpi、ldpi 的概念 - 术语和概念 屏幕尺寸 屏幕的物理尺寸,基于屏幕的对角线长度(如 2.8 英寸、3.5 英寸)。 简而言之,安卓系统将所有屏幕尺寸简化为三大类:大、普通和小。 程序可以为这三种屏幕尺寸提供三种不同的布局选项,然后系统会以合适的方式将布局选项呈现到相应的屏幕上,这个过程不需要程序员用代码进行干预。 屏幕纵横比 屏幕的物理长度与物理宽度之比。程序只需使用系统提供的资源分类器 long(长)和 notlong(不长),就能为具有特定长宽比的屏幕提供配制材料。 分辨率 屏幕的像素总数。请注意,分辨率并不意味着长宽比,尽管在大多数情况下,分辨率表示为 "宽度 x 长度"。在安卓系统中,程序一般不直接处理分辨率。 密度 根据屏幕分辨率,沿屏幕宽度和长度排列的像素数量。 密度较低的屏幕在长度和宽度方向上的像素都相对较少,而密度较高的屏幕通常会在同一区域内排列很多甚至非常非常多的像素。屏幕的密度非常重要;例如,一个界面元素(如按钮)的长度和宽度以像素为单位,在低密度屏幕上会显得很大,但在高密度屏幕上就会显得很小。 独立于密度的像素(DIP)是指程序用来定义界面元素的抽象意义上的像素。它作为一个与实际密度无关的单位,帮助程序员构建布局方案(界面元素的宽度、高度和位置)。 与密度无关的像素在逻辑上与像素密度为 160 DPI 的屏幕上的像素大小相同,而 160 DPI 是安卓平台默认的显示设备。在运行时,平台会以目标屏幕的密度为基准,"透明 "地处理所有所需的 DIP 缩放操作。要将与密度无关的像素转换为屏幕像素,可以使用一个简单的公式:像素 = DIP * (密度 / 160)。例如,在 240 DPI 的屏幕上,1 个 DIP 等于 1.5 个物理像素。强烈建议使用 DIP 来定义程序界面的布局,因为这样可以确保用户界面在所有分辨率的屏幕上都能正常显示。 为了简化程序员在面对各种分辨率时的麻烦,也为了让各种分辨率的平台都能直接运行这些程序,Android 平台将所有屏幕以密度和分辨率作为分类方式,分别分为三类:- 三大尺寸:大、普通、小;- 三种不同密度:高(hdpi)、中(mdpi)和低(ldpi)。DPI 表示 "每英寸点数",即每英寸的像素数。如果需要,程序可以为不同的屏幕尺寸提供不同的资源(主要是布局),为不同的屏幕密度提供不同的资源(主要是位图)。除此之外,程序无需对屏幕尺寸或密度进行任何额外处理。执行时,平台会根据屏幕本身的尺寸和密度特性自动加载相应的资源,并将其从逻辑像素(DIP,用于定义界面布局)转换为屏幕上的物理像素。
-
jvm 的 java 类加载机制和类加载器(ClassLoader)的详细信息
-
Java 类加载]自定义类加载器
-
JVM 注意事项 (I) Java 类加载过程?类加载器?
-
4.进一步了解类加载器
-
19.类加载器说明
-
深入了解 Java 类加载器(ClassLoader)
-
JAVA 类加载器包括几种类型?它们之间有什么关系?双亲委托机制是什么意思?有哪些好处?
-
深入了解 Java 类加载器