本文的目的,是通过解剖和修改JVM的类加载器,来详细分析JVM的类加载机制。
其实任何一个JVM的类加载器不过是做了如下的工作:
1. 确定JAVA类文件的位置。
2. 读取类文件内容,将类文件内容读取成二进制字节流。
3. 解析并加载类内容。
4. 最后,将类的“标识”返回给要使用这个类的代码中。
那下面我们就来做一个比较“另类”的试验:
在JAVA规范中,public类名必须与类所在的文件名相同。
但本文将尝试在一个名为“T1.bin”的文件中,加载一个名为“Test1”的类,以此来创造一种与JVM标准类加载器不同的类加载器。
先讲讲我的实现思路:
1. 先编写一个类,此类将被其它类来调用,这个类的类名和文件名在测试时将不会相同。这个类的类名为“Test1”,而文件名为“T1.bin”。
2. 编写自己的类加载器,此类加载器不是从寻常的".class"文件中来加载一个类,而是从代码中写死的“D:/Users/T1.bin”文件中来加载一个类。
3. 编写业务逻辑类,此类将利用反射原理,调用类加载器,读取T1.bin文件,构造第一步中建立的类的对象,并调用这个对象的方法。
首先在Eclipse中新建一个Java工程,名为“Test1”,在我的硬盘上,此工程的存储地址为“D:\Users\haojian.XINAO\workspace\Test1”。
在此工程的src目录下,新建一个Java类,名为“Test1”,内容如下:
/**
* @author haojian
*
*/
public class Test1 {
// 此类的此方法,将被调用来测试
public void testMethod() {
System.out.print("---test---"); // 标识1
}
}
则打开"D:\Users\haojian.XINAO\workspace\Test1"目录后,在src目录下将有Test1.java文件,在bin目录下将有“Test1.class”文件。
将Test1.class文件复制到D:\Users目录下,修改文件名为“T1.bin”。此时此T1.bin文件在逻辑上已与Eclipse中的Test1工程没有任何关系。
编写读取此T1.bin文件的类加载器:
在Test1目录下新建包,名为"cld",在此包下新建类,内容如下:
/**
*
*/
package cld;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.WritableByteChannel;
/**
* @author haojian
*
*/
public class MyClassLoader1 extends ClassLoader {
public MyClassLoader1(ClassLoader parent, String baseDir) {
super(parent);
}
protected Class findClass(String name) throws ClassNotFoundException {
byte[] bytes = loadClassBytes(name);
Class theClass = defineClass(name, bytes, 0, bytes.length);
if (theClass == null)
throw new ClassFormatError();
return theClass;
}
private byte[] loadClassBytes(String className)
throws ClassNotFoundException {
try {
String classFile = getClassFile();
FileInputStream fis = new FileInputStream(classFile);
FileChannel fileC = fis.getChannel();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
WritableByteChannel outC = Channels.newChannel(baos);
ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
while (true) {
int i = fileC.read(buffer);
if (i == 0 || i == -1) {
break;
}
buffer.flip();
outC.write(buffer);
buffer.clear();
}
fis.close();
return baos.toByteArray();
} catch (IOException fnfe) {
throw new ClassNotFoundException(className);
}
}
private String getClassFile() {
return "D:/Users/T1.bin";
}
}
MyClassLoader1类实现了自定义的类加载器, getClassFile() 方法的作用,是确定要读取的文件名,loadClassBytes()方法则将文件中的内容读取成二进制数组。findClass()则可以看做是MyClassLoader1类与它的父类ClassLoader类之间的接口,后面将要讲到。
此类将从D:/Users/T1.bin文件中读取Java类内容,并加载到JVM中去。
下面将编写业务流程代码,去“T1.bin”文件中读取并加载“Test1”类。并调用Test1类的“testMethod”方法。
/**
*
*/
package cld;
import java.lang.reflect.Method;
/**
* @author haojian
*
*/
public class TestClass {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
// 创建自己写的类加载器的对象
MyClassLoader1 myClassLoader1 = new MyClassLoader1(TestClass.class.getClassLoader());
// 设定要加载的类名称
String className = "Test1";
Class loadedClassTest1 = null;
try {
// 实际干活,去“T1.bin”文件中加载"Test1"类
loadedClassTest1 = myClassLoader1.loadClass(className); // 标识2
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 下面是利用反射来实际调用Test1类的“testMethod”方法
Object oTest1 = null;
try {
oTest1 = loadedClassTest1.newInstance();
Method testMethodCall = null;
testMethodCall = loadedClassTest1.getMethod("testMethod");
testMethodCall.invoke(oTest1);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
以下为控制台输出结果:
---test---
可以看到,程序中确实实际调用了“标识1”位置的语句。
也许有人会问,MyClassLoader1这个类为什么会到D:/Users/T1.bin文件中去加载"Test1"类呢?在TestClass的main方法里,并没有调用getClassFile()方法啊?
让我们解析一下MyClassLoader1这个类的执行过程,大家就清楚了。。。
让我们先看标识2的代码,这里调用了MyClassLoader1这个类的loadClass(className)方法,这个方法做了什么工作呢?
我们查看MyClassLoader1类,发现没有loadClass方法,那么这个方法就应该是由它的父类来实现的。则我们继续打开ClassLoader这个类的loadClass方法:
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
protected synchronized Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// First, check if the class has already been loaded
Class c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClass0(name);
}
} catch (ClassNotFoundException e) {
// If still not found, then invoke findClass in order
// to find the class.
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
此方法的执行过程如下:
1. 首先查看JVM中是否已经加载过这个类。
2. 如没有加载过这个类,则首先用此类的父类来加载参数类名中指向的类。
3. 如仍然不能加载类,则调用findClass方法。
因为我们已经确定这个类是新加载过的,以前没有加载,所以1、2两步必然不能正确加载此类,程序必然会执行到第3步。
则我们继续追踪findClass()此类的代码。。。
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
其实在ClassLoader类里,对findClass()是没有任何实现的,需要它的子类来实现。这样,执行权就顺利的交给了MyClassLoader1的findClass()方法,而通过findClass()->loadClassBytes()->getClassFile()就能顺利执行到getClassFile()方法了。
其实我这篇文章里面讲到的,都只是对类文件的读取,并没有涉及到最核心的“将二进制字节流解析成内存中的JAVA类结构”,将二进制解析成内存结构是由“defineClass()”方法来完成的,以后会慢慢讲到的。
其实按照我文章里的这种思路,大家可以尝试在数据库中、网站文件中、FTP中等地方来加载一个JAVA类。。。
分享到:
相关推荐
在Java编程语言中,类加载器(ClassLoader)是运行时环境...理解类加载机制并正确实现自定义加载器是提升Java应用开发能力的重要一步。在实际项目中,合理利用类加载器可以解决很多复杂问题,比如模块化、动态更新等。
在Java编程语言中,类加载器(ClassLoader)是运行时环境的重要组成部分,它负责查找和加载类的字节码文件到Java虚拟机(JVM)中。本篇将深入探讨自定义类加载器的实现、双亲委派模型以及如何指定父类加载器。 首先...
自定义类加载器可以继承`java.lang.ClassLoader`来实现自己的加载逻辑,比如从网络、数据库或其他来源加载类。当需要加载非标准路径或实现特定加载策略时,自定义类加载器就显得尤为重要。 总之,Java类加载机制是...
类加载器是JVM实现动态加载的核心组件,它的主要任务是根据类名找到对应的.class文件,并将其转换为内存中的Class对象。Java中的类加载器主要有以下几种: 1. Bootstrap ClassLoader:引导类加载器,负责加载JRE...
1. **加载 (Loading):** 在这个阶段,类加载器读取类的二进制流并将其转换为 `java.lang.Class` 对象。 2. **连接 (Linking):** 包括验证 (Verification)、准备 (Preparation) 和解析 (Resolution) 三个子步骤。验证...
在Java中,类加载器(ClassLoader)是负责加载类到JVM中的核心组件。默认情况下,系统类加载器会搜索`CLASSPATH`环境变量或 `-cp`/`-classpath`命令行选项指定的路径下的`.class`文件。但是,我们可以通过自定义类...
- 在现有加载器不能满足需求时,例如加载非标准路径的类、加密/解密字节码或打破双亲委派模型,可自定义加载器。 12. **字节码对象的唯一性** - 同一类的不同字节码对象由不同的类加载器加载,因此它们在内存中是...
加载阶段,Java虚拟机通过类加载器将类的.class文件读取到内存中,并创建对应的java.lang.Class对象。类加载器是这个过程的关键组件。 Java中主要有三种类加载器: 1. 引导类加载器(Bootstrap ClassLoader):这...
通过理解类加载原理和自定义加载器的工作方式,我们可以更灵活地控制类的生命周期,提高软件的可扩展性和安全性。在实际项目中,务必谨慎使用,遵循良好的设计原则,以确保代码的可维护性和稳定性。
《深入Java虚拟机(七)深入源码看java类加载器ClassLoader》 Java类加载器(ClassLoader)在Java运行环境中扮演着至关重要的角色。它负责将类的字节码加载到Java虚拟机(JVM)中,使得程序能够运行。ClassLoader是...
Java 类加载器(ClassLoader)是Java虚拟机(JVM)的重要组成部分,它负责在运行时查找并加载类到JVM中。这个教程将深入探讨ClassLoader的工作原理、类型以及如何自定义类加载器。 一、Java ClassLoader 基础 1. 类...
Java加载器(Loader)是Java虚拟机(JVM)的核心组成部分,主要负责将类的字节码文件加载到JVM中并转换为运行时的数据结构。在深入理解这个概念之前,我们首先要明白Java的类加载机制。Java的类加载过程包括加载、...
Java虚拟机类装载机制是Java运行环境中的核心组成部分,它负责将类的字节码从磁盘、网络等不同来源加载到JVM中,并进行一系列处理以使类能够被正确地使用。类装载机制的目的是为了实现代码的动态加载和运行时的灵活...
类装载器在Java中的主要职责是动态加载类到JVM中。Java的类装载器分为三个基本层次:启动类装载器(Bootstrap ClassLoader)、扩展类装载器(Extension ClassLoader)和应用程序类装载器(Application ClassLoader)...
1. **加载**:类加载器根据全限定名(包括包名和类名)找到对应的.class文件,然后读取字节流,并创建一个二进制表示的Class对象。 2. **验证**:确保加载的类符合Java语言规范,没有安全风险。这个阶段会检查字节...
本文详细介绍了 Java 类加载机制的基本概念,包括类加载的过程、初始化时机、类加载器的工作原理及其分类,并提供了一个自定义类加载器的示例。通过这些内容的学习,可以帮助开发者更好地理解 Java 类加载机制,为...
当一个类加载器接收到加载类的请求时,它首先会委托父加载器去尝试加载,只有当父加载器无法完成时,子加载器才会尝试自己加载。这种模型避免了类的重复加载,也保证了核心类库的安全性。 **自定义类加载器**是Java...
以上内容详细阐述了JVM中类的加载机制、类加载器及加载模型、内存区域划分、类的初始化时机以及运行时内存的具体划分等内容,这些知识点对于深入理解Java虚拟机的工作原理至关重要,也是Java开发人员面试中经常涉及...
Java 类加载器(ClassLoader)是Java虚拟机(JVM)中的一个重要组成部分,它负责加载类的字节码文件,使得程序能够运行。深入理解ClassLoader对于优化应用性能、处理类加载问题以及实现自定义加载策略至关重要。 一...