类从编译到执行的过程
类从编译到执行的过程主要经历了以下几个步骤:
例如加载ClassLoadTest.java文件
1.编译器将ClassLoadTest.java源文件编译成ClassLoadTest.class字节码文件。
2.ClassLoader将字节码文件转换为JVM中的Class<ClassLoadTest>对象。
3.JVM利用Class<ClassLoadTest>对象实例化ClassLoadTest对象。
看一个加载的例子
Class aClass = ClassLoadTest.class;
ClassLoader classLoader = ClassLoadTest.class.getClassLoader();
Class class3 = classLoader.loadClass("com.reflect.ClassLoadTest");
如之上代码所示,其实本质上是等价的。在java虚拟机规范中讲明了这一点。
If D was defined by a user-defined class loader, then that same user-defined class loader initiates loading of C (§5.3.2).
其中涉及到两个名词。我们称触发一个类加载的过程的类加载器叫做initiating loader,而最终加载这个类的类加载器叫做defining loader。例子:一个类加载器X触发了java.util.Map类的加载,但是它最终被系统的类加载器加载了(因为双亲委派模型),我们称X为initiating loader,系统的类加载器为defining loader。
如果使用Idea中的ASM插件去查看字节码的话,会发现Class aClass = ClassLoadTest.class;
这段代码执行了LDC指令,会去常量池中拿东西。我们假设这段代码是运行在Test
类中的,这时候,ClassLoadTest.class的加载会被Test.class.classLoader作为initiating loader触发。
ClassLoader classLoader = A.class.getClassLoader();
其中的classLoader就是最终加载A.class的类加载器。
我们令classLoader.loadClass("com.ClassLoadTest");就是令该类加载器加载ClassLoadTest.class
。
什么是ClassLoader
ClassLoader是Java中的核心组件,它主要是从系统外部读取class文件字节码,加载进JVM中,交由JVM进行连接初始化等。
ClassLoader的种类
- BootStrapClassLoader:C++编写的,加载核心库
*.java
。 - ExtClassLoader:Java编写,加载扩展库 javax.*
- AppClassLoader:Java编写,加载程序所在的目录。
- 自定义ClassLoader:Java编写,定制化加载
所以自定义ClassLoader指的是自己定义编写类加载的规则,如何自己编写一个ClassLoader呢?
在java.lang.ClassLoader
这个抽象类中,有一个方法:
protected Class<? > findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
它的定义直接抛出异常,它的作用是提供给用户覆盖方法,编写自定义的ClassLoader。自定义编写ClassLoader的代码如下:
public class MyClassLoader extends ClassLoader {
private String fileName;
private String path;
public MyClassLoader(String fileName, String path) {
this.fileName = fileName;
this.path = path;
}
@Override
public Class findClass(String name) {
byte[] b = new byte[0];
try {
b = loadClassData(name, path);
} catch (IOException e) {
e.printStackTrace();
}
return defineClass(name, b, 0, b.length);
}
private byte[] loadClassData(String name, String path) throws IOException {
return Files.readAllBytes(Paths.get(path + name + ".class"));
}
}
这样就完成了一个类加载器的编写。我们用写一个检查类进行验证
class Check{
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException,
InstantiationException {
MyClassLoader myClassLoader = new MyClassLoader("LoadFile","\\mypath");
Class c = myClassLoader.loadClass("LoadFile");
System.out.println(c.getClassLoader());
c.newInstance();
}
}
打印的结果为
com.reflect.MyClassLoader@7d4991ad
load file ...
类加载器的双亲委派机制

如上图所示,在开始类加载之前,类加载器首先会检查这个类曾经有没有被加载过。加载过则直接使用加载的Class对象,没有加载则向上委托给它的父加载器,检查父加载器有没有加载过。
如果都没有加载过这个类,则会从上到下检查classpath中的jar,查找包中的jar是否有对应的类。
使用双亲委派机制来加载类,主要是避免多份同样字节码的加载。
loadClass和forName的区别
类的装载过程主要有以下三步:
- 加载
通过ClassLoader加载class文件字节码,生成Class对象。 - 链接
1.校验:检查加载class的正确性和安全性
2.准备:为类变量分配存储空间并设置类变量初始值
3.解析:JVM将常量池的符号引用转换为直接引用 - 初始化
执行类变量赋值和静态代码块
对于Class.forName和ClassLoad.loadClass而言,主要的区别是:
Class.forName得到的class是完成初始化的。 ClassLoad.loadClass得到的class是没有链接的,也就是没有初始化的。
这点可以在源代码中体现:
forName
的代码,可以看到它的返回方法中第二个参数置为true,则表明会被初始化。
@CallerSensitive
public static Class<?> forName(String className)
throws ClassNotFoundException {
Class<?> caller = Reflection.getCallerClass();
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}
对于loadClass
而言,它最后执行的方法如下,它的初始化默认是为false的。
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;
}
}
由于它的方法是protected修饰的,所以子类无法继承。但是我们有反射呀...在Java8中,有了反射就可以随时随地的扒下JVM的内裤了。。。
网友评论