java 所有的代码都是要经过编辑成 class 文件,也就是class文件才能被虚拟机识别,在被虚拟机加载的过程 需要完成下面3步
下面可以看代码1验证一下,然后在分析代码
ClassLoader classsLoader = Main.class.getClassLoader();
while (classsLoader != null) {
System.out.println(classsLoader.toString());
classsLoader = classsLoader.getParent();
}
运行结果
sun.misc.Launcher$AppClassLoader@12a3a380
sun.misc.Launcher$ExtClassLoader@511d50c0
可以这个类是由Launcher的内部类AppClassLoader加载的,AppClassLoader的父类为同样的内部类ExtClassLoader,其实 还有一个加载类为Bootstrap ClassLoader ,这三个类加载器,加载不同目录和功能
JDK\jre\li
(JDK 代表 JDK 的安装目录,下同)下,或被-Xbootclasspath
参数指定的路径中的,并且能被虚拟机识别的类库(如 rt.jar,所有的java.*
开头的类均被 Bootstrap ClassLoader 加载)。启动类加载器是无法被 Java 程序直接引用的,这个加载器不是java写的。sun.misc.Launcher$ExtClassLoader
实现,它负责加载JDK\jre\lib\ext
目录中,或者由 java.ext.dirs 系统变量指定的路径中的所有类库(如javax.*
开头的类),开发者可以直接使用扩展类加载器。ExtClassLoader和AppClassLoader都是继承URLClassLoader 继承于 ClassLoader ,ClassLoader是类加载器的基类,除了启动类加载器是由java实现的,以上运行结果没有这个类,是由于他没有父类。
加载入口loadClass
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 {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
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;
}
}
这个是ClassLoader的类加载入口,其中类加载前首先判断该类有没有被加载过,如果被加载过直接返回,否则调用父类加载器,以此类推,如果没有加载最终调用自身加载。
下面是AppClassLoader的类加载方法
public Class> loadClass(String var1, boolean var2) throws ClassNotFoundException {
int var3 = var1.lastIndexOf(46);
if(var3 != -1) {
SecurityManager var4 = System.getSecurityManager();
if(var4 != null) {
var4.checkPackageAccess(var1.substring(0, var3));
}
}
if(this.ucp.knownToNotExist(var1)) {
Class var5 = this.findLoadedClass(var1);
if(var5 != null) {
if(var2) {
this.resolveClass(var5);
}
return var5;
} else {
throw new ClassNotFoundException(var1);
}
} else {
return super.loadClass(var1, var2);
}
}
其中this.ucp.knowToNotExist(var1)这个方法也是用来判断该类有没有被加载过。类似于父类的加载流程。
在看ClassLoader的加载方法当所有类没有完成类的字节码加载的时,会调用findClass方法,
protected Class> findClass(final String name)
throws ClassNotFoundException
{
final Class> result;
try {
result = AccessController.doPrivileged(
new PrivilegedExceptionAction>() {
public Class> run() throws ClassNotFoundException {
String path = name.replace('.', '/').concat(".class");
Resource res = ucp.getResource(path, false);
if (res != null) {
try {
return defineClass(name, res);
} catch (IOException e) {
throw new ClassNotFoundException(name, e);
}
} else {
return null;
}
}
}, acc);
} catch (java.security.PrivilegedActionException pae) {
throw (ClassNotFoundException) pae.getException();
}
if (result == null) {
throw new ClassNotFoundException(name);
}
return result;
}
调用
private Class> defineClass(String name, Resource res) throws IOException {
long t0 = System.nanoTime();
int i = name.lastIndexOf('.');
URL url = res.getCodeSourceURL();
if (i != -1) {
String pkgname = name.substring(0, i);
// Check if package already loaded.
Manifest man = res.getManifest();
definePackageInternal(pkgname, man, url);
}
// Now read the class bytes and define the class
java.nio.ByteBuffer bb = res.getByteBuffer();
if (bb != null) {
// Use (direct) ByteBuffer:
CodeSigner[] signers = res.getCodeSigners();
CodeSource cs = new CodeSource(url, signers);
sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
return defineClass(name, bb, cs);
} else {
byte[] b = res.getBytes();
// must read certificates AFTER reading bytes.
CodeSigner[] signers = res.getCodeSigners();
CodeSource cs = new CodeSource(url, signers);
sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
return defineClass(name, b, 0, b.length, cs);
}
}
最终调用ClassLoader的 defineClass 方法,这个方法被定义为final 是不能够被复写的,尽管参数不同但是都是通过加载byte数据,通过底层实现Class 的构建,其中里面的由于涉及涉及JVM内存区等这里先不讨论。
下图是系统加载器流程图
先验证一下上述流程图的问题,主要涉及到自定义加载的问题,细看ClassLoader可以发现,classloder的加载机制是在基类里面已经实现好的,如果需要自定义类加载器,没有必要重写loadClass方法,只需要重写findClass方法就可以
public class MyClassLoader extends ClassLoader {
private String rootPath;
public MyClassLoader(String rootPath) {
this.rootPath = rootPath;
}
@Override
protected Class> findClass(String name) throws ClassNotFoundException {
//check if the class have been loaded
Class> c = findLoadedClass(name);
if (c != null) {
return c;
}
//load the class
byte[] classData = getClassData(name);
if (classData == null) {
throw new ClassNotFoundException();
} else {
c = defineClass(name, classData, 0, classData.length);
return c;
}
}
private byte[] getClassData(String className) {
String path = rootPath + "/" + className.replace('.', '/') + ".class";
InputStream is = null;
ByteArrayOutputStream bos = null;
try {
is = new FileInputStream(path);
bos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int temp = 0;
while ((temp = is.read(buffer)) != -1) {
bos.write(buffer, 0, temp);
}
return bos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
is.close();
bos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
}
上面是自定义的类加载器
public class Test1 {
private Test1 instance;
public void setSample(Object instance) {
this.instance = (Test1) instance;
}
public String getName() {
return "test1";
}
}
上图是随便写的测试类.
验证流程问题
String root = "/Users/xinggenguo/Desktop";
String name = "Test1";
ClassLoader classLoader = new MyClassLoader(root);
Class classC = classLoader.loadClass(name);
while (classLoader != null) {
System.out.println(classLoader.toString());
classLoader = classLoader.getParent();
}
输出结果
MyClassLoader@60e53b93
sun.misc.Launcher$AppClassLoader@12a3a380
sun.misc.Launcher$ExtClassLoader@1d44bcfa
这就验证了上述问题。
关于类加载为什么这么设计,需要看一下一个类是否相同是怎么判断的,类是否相同不单于类的全名相关还和类加载器相关。
try {
String root = "/Users/xinggenguo/Desktop";
String name = "Test1";
ClassLoader classLoader = new MyClassLoader(root);
Class classC = classLoader.loadClass(name);
Object obj1 = classC.newInstance();
MyClassLoader classLoader1 = new MyClassLoader(root);
Class classC1 = classLoader1.loadClass(name);
Object obj2 = classC1.newInstance();
System.out.println(obj1.getClass().getClassLoader().toString());
System.out.println(obj2.getClass().getClassLoader().toString());
Method setSampleMethod = classC.getMethod("setSample", java.lang.Object.class);
setSampleMethod.invoke(obj1, obj2);
} catch (Exception e) {
e.printStackTrace();
}
运行这段代码会出现Caused by: java.lang.ClassCastException: Test1 cannot be cast to Test1 异常也就验证了上述问题。
在回过来分析上面为什么要这么设计类加载,java下所有的类都是继承于Object类的,这就可以保证java核心类库在同一个虚拟机下,只有同一个版本,这些都是可以兼容的,反过来,又可以通过不同的类加载器为开发者提供相对对立的空间。
同时类加载器不单单可以加载内部的class类,java包,也可以加载外部的class类。
还是使用上面的测试类,调用getName方法
try {
String root = "/Users/xinggenguo/Desktop";
String name = "Test1";
ClassLoader classLoader = new MyClassLoader(root);
Class classC = classLoader.loadClass(name);
Object object = classC.newInstance();
Method setSampleMethod = classC.getMethod("getName");
System.out.println(setSampleMethod.invoke(object));
} catch (Exception e) {
e.printStackTrace();
}
打印结果:test1
更改Test1类的getName方法重新调用。
public String getName() {
return "test2";
}
打成class文件后重新运行
打印结果:test2
也就是说明,直接替换是生效的,但是在java程序在运行期间来替换,要涉及到资源的动态释放和或者替换,这里不讨论。