一.类的加载流程
下面是一个简单的类
package com.company;
public class Test {
public void hello()
{
System.out.println("hello");
}
public static void main(String[] args) {
Test test=new Test();
test.hello();
}
}
通过java命令执行,流程如下
loadClass加载类,它的整个生命周期包括:加载、验证、准备、解析、初始化、使用和卸载七个阶段。它们的顺序如下图所示:
各个步骤主要的工作
加载:读取字节码文件,在内存中生成这个类的java.lang.Class对象,作为方法区的这个类的各种数据的访问入口
验证:验证字节码的正确性
准备:给类的静态变量分配内存,并赋默认值。如int类型,赋值为0
解析:将符号引用替换为直接引用,该阶段会把一些静态方法(符号引用)替换为执行数据内存的指针。这是静态链接过程。
初始化:将类的静态变量设为代码中指定的值,执行静态代码块static{代码}
二.类加载器
启动类记载器:负责加载jre目录下lib核心类库,rt.jar、resources.jar等
扩展类加载器:负责加载jre目录下ext目录的类包
应用程序类加载器:负责加载ClassPath路径下的类包,主要是自己写的类
自定义加载器:负责加载用户自定义路径下的类包
类加载器初始化过程:
jvm启动器创建实例sun.misc.Launcher。其构造方法中创建了扩展类加载器和应用类加载器。
jvm默认使用Launcher的getClassLoader()方法返回应用类加载器的的实例,加载我们的应用程序,并使用双亲委派的机制。
构造方法
public Launcher() {
Launcher.ExtClassLoader var1;
try {
//扩展类加载器
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader");
}
try {
//将load属性,设为应用类加载器
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader");
}
Thread.currentThread().setContextClassLoader(this.loader);
String var2 = System.getProperty("java.security.manager");
if (var2 != null) {
SecurityManager var3 = null;
if (!"".equals(var2) && !"default".equals(var2)) {
try {
var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();
} catch (IllegalAccessException var5) {
} catch (InstantiationException var6) {
} catch (ClassNotFoundException var7) {
} catch (ClassCastException var8) {
}
} else {
var3 = new SecurityManager();
}
if (var3 == null) {
throw new InternalError("Could not create SecurityManager: " + var2);
}
System.setSecurityManager(var3);
}
}
三.双亲委派机制
原理:当一个类加载器收到类加载任务,会先交给其父类加载器去完成,因此最终加载任务都会传递到顶层的启动类加载器,只有当父类加载器无法完成加载任务时,才会尝试执行加载任务
源码如下:
其核心代码块通过递归方式不断调用parent.loadClass(),直到调用到启动类加载器结束。若c为null,则调用本加载器。
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) {
//递归,调用其父级的loadClass方法
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;
}
}
双亲委派机制的好处
1.可以避免重复加载:父亲已经加载就不需要重复加载,确保唯一性。
2.沙箱安全机制:防止核心API库被修改。如下操作会报错
在项目中新建java.lang包,在包下建String类,与核心库的String类文件一致
调用main方法报错,找不到main方法。因为双亲委派的机制,java.lang.String的在启动类加载器得到加载,因为在核心jre库中有其相同名字的类文件,但该类中并没有main方法。这样就能防止黑客篡改核心API库。