白菜Java自习室 涵盖核心知识
1. 类加载机制
什么是 Java 的类加载机制?
.java 文件通过编译以后,读取到 Java 的方法区中,然后类加载器的加载、验证(class特定的格式)、准备(分配内存)、解析(将字节码的符号引用改成直接引用)、初始化(对象的初始化)的过程。
前置知识学习请参考作者的文章:Java工程师的进阶之路 JVM篇
2. 双亲委派机制
什么是 双亲委派机制?
当某个类加载器需要加载某个.class文件时,它首先把这个任务委托给他的上级类加载器,递归这个操作,如果上级的类加载器没有加载,自己才会去加载这个类。
BootstrapClassLoader(启动类加载器)
c++ 编写,加载 Java 核心库 java.*,构造 ExtClassLoader 和 AppClassLoader。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作。
ExtClassLoader (标准扩展类加载器)
Java 编写,加载扩展库,如 classpath 中的 jre,javax.* 或者 java.ext.dir 指定位置中的类,开发者可以直接使用标准扩展类加载器。
AppClassLoader(系统类加载器)
Java 编写,加载程序所在的目录。
CustomClassLoader(用户自定义类加载器)
Java 编写,用户自定义的类加载器,可加载指定路径的 class 文件。
双亲委派机制的作用
- 防止重复加载同一个 .class,加载过了,就不用再加载一遍,保证数据安全。
- 保证了 JVM 提供的核心类不被篡改,保证 class 执行安全。
3. Java 8 中 ClassLoad 源码
如果开发人员需要自定义的加载器需要重新注意重写下面的 loadClass()
和 findClass()
方法,从而可以打破双亲的委派机制。
ClassLoad 中的 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 {
// 加载 Java 核心类库的加载器
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;
}
}
复制代码
URLClassloader 中的 findClass() 方法
protected Class<?> findClass(final String name)
throws ClassNotFoundException
{
final Class<?> result;
try {
result = AccessController.doPrivileged(
new PrivilegedExceptionAction<Class<?>>() {
public Class<?> run() throws ClassNotFoundException {
// 拼接成指定前缀的.class文件
String path = name.replace('.', '/').concat(".class");
// 从 classpath 获取到.class文件
Resource res = ucp.getResource(path, false);
if (res != null) {
try {
// 从路径中读取class文件字节流,并定义成类;todo bytebuffer
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;
}
复制代码
4. Tomcat 如何打破双亲委派机制
下面的简图是 Tomcat的官方文档给出的类加载器的图:
Bootstrap
|
System
|
Common
/ \
Webapp1 Webapp2 ..
复制代码
- Bootstrap:是 Java 的最高的加载器,用 C 语言实现,主要用来加载 JVM 启动时所需要的核心类,例如 $JAVA_HOME/jre/lib/ext 路径下的类。
- System:会加载 CLASSPATH 系统变量所定义路径的所有的类。
- Common:会加载 Tomcat 路径下的 lib 文件下的所有类。
- Webapp1、Webapp2:会加载 webapp 路径下项目中的所有的类。一个项目对应一个WebappClassLoader,这样就实现了应用之间类的隔离了。
那么 Tomcat 为什么要自定义类加载器呢?
- 性能:如果在一个 Tomcat 部署多个应用,多个应用中都有相同的类库依赖。那么可以把这相同的类库让 Common 类加载器进行加载。
- 灵活性:Web 应用之间的类加载器相互独立,那么就可以根据修改不同的文件重建不同的类加载器替换原来的,从而不影响其他应用。
- 应用隔离:部署在同一个 Tomcat 中的不同应用 A 和 B,例如 A 用了 Spring 2.5,B 用了Spring 3.5,那么这两个应用如果使用的是同一个类加载器,那么 Web 应用就会因为 jar 包覆盖而无法启动。
Tomcat 打破双亲委派机制
Tomcat 自定义了 WebAppClassLoader 类加载器,打破了双亲委派的机制。即如果收到类加载的请求,会尝试自己去加载,如果找不到再交给父加载器去加载,目的就是为了优先加载 Web 应用自己定义的类。
Tomcat 重写下面的 loadClass()
和 findClass()
方法,从而可以打破双亲的委派机制。
ClassLoad 中的 loadClass() 方法
@Override
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
Class<?> clazz = null;
// 1. 从本地缓存中查找是否加载过此类
clazz = findLoadedClass0(name);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Returning class from cache");
if (resolve)
resolveClass(clazz);
return clazz;
}
// 2. 从AppClassLoader中查找是否加载过此类
clazz = findLoadedClass(name);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Returning class from cache");
if (resolve)
resolveClass(clazz);
return clazz;
}
String resourceName = binaryNameToPath(name, false);
// 3. 尝试用ExtClassLoader 类加载器加载类,防止Web应用覆盖JRE的核心类
ClassLoader javaseLoader = getJavaseClassLoader();
boolean tryLoadingFromJavaseLoader;
try {
URL url;
if (securityManager != null) {
PrivilegedAction<URL> dp = new PrivilegedJavaseGetResource(resourceName);
url = AccessController.doPrivileged(dp);
} else {
url = javaseLoader.getResource(resourceName);
}
tryLoadingFromJavaseLoader = (url != null);
} catch (Throwable t) {
tryLoadingFromJavaseLoader = true;
}
boolean delegateLoad = delegate || filter(name, true);
// 4. 判断是否设置了delegate属性,如果设置为true那么就按照双亲委派机制加载类
if (delegateLoad) {
if (log.isDebugEnabled())
log.debug(" Delegating to parent classloader1 " + parent);
try {
clazz = Class.forName(name, false, parent);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Loading class from parent");
if (resolve)
resolveClass(clazz);
return clazz;
}
} catch (ClassNotFoundException e) {
// Ignore
}
}
// 5. 默认是设置delegate是false的,那么就会先用WebAppClassLoader进行加载
if (log.isDebugEnabled())
log.debug(" Searching local repositories");
try {
clazz = findClass(name);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Loading class from local repository");
if (resolve)
resolveClass(clazz);
return clazz;
}
} catch (ClassNotFoundException e) {
// Ignore
}
// 6. 如果此时在WebAppClassLoader没找到类,那么就委托给AppClassLoader去加载
if (!delegateLoad) {
if (log.isDebugEnabled())
log.debug(" Delegating to parent classloader at end: " + parent);
try {
clazz = Class.forName(name, false, parent);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Loading class from parent");
if (resolve)
resolveClass(clazz);
return clazz;
}
} catch (ClassNotFoundException e) {
// Ignore
}
}
}
throw new ClassNotFoundException(name);
}
复制代码
URLClassloader 中的 findClass() 方法
@Override
public Class<?> findClass(String name) throws ClassNotFoundException {
if (log.isDebugEnabled())
log.debug(" findClass(" + name + ")");
checkStateForClassLoading(name);
// (1) Permission to define this class when using a SecurityManager
if (securityManager != null) {
int i = name.lastIndexOf('.');
if (i >= 0) {
try {
if (log.isTraceEnabled())
log.trace(" securityManager.checkPackageDefinition");
securityManager.checkPackageDefinition(name.substring(0,i));
} catch (Exception se) {
if (log.isTraceEnabled())
log.trace(" -->Exception-->ClassNotFoundException", se);
throw new ClassNotFoundException(name, se);
}
}
}
// Ask our superclass to locate this class, if possible
// (throws ClassNotFoundException if it is not found)
Class<?> clazz = null;
try {
if (log.isTraceEnabled())
log.trace(" findClassInternal(" + name + ")");
try {
if (securityManager != null) {
PrivilegedAction<Class<?>> dp =
new PrivilegedFindClassByName(name);
clazz = AccessController.doPrivileged(dp);
} else {
// 从本地的具体类名查找 内部方式
clazz = findClassInternal(name);
}
} catch(AccessControlException ace) {
log.warn("WebappClassLoader.findClassInternal(" + name
+ ") security exception: " + ace.getMessage(), ace);
throw new ClassNotFoundException(name, ace);
} catch (RuntimeException e) {
if (log.isTraceEnabled())
log.trace(" -->RuntimeException Rethrown", e);
throw e;
}
if ((clazz == null) && hasExternalRepositories) {
try {
// 调用父类的加载方式
clazz = super.findClass(name);
} catch(AccessControlException ace) {
log.warn("WebappClassLoader.findClassInternal(" + name
+ ") security exception: " + ace.getMessage(), ace);
throw new ClassNotFoundException(name, ace);
} catch (RuntimeException e) {
if (log.isTraceEnabled())
log.trace(" -->RuntimeException Rethrown", e);
throw e;
}
}
if (clazz == null) {
if (log.isDebugEnabled())
log.debug(" --> Returning ClassNotFoundException");
throw new ClassNotFoundException(name);
}
} catch (ClassNotFoundException e) {
if (log.isTraceEnabled())
log.trace(" --> Passing on ClassNotFoundException");
throw e;
}
// Return the class we have located
if (log.isTraceEnabled())
log.debug(" Returning class " + clazz);
if (log.isTraceEnabled()) {
ClassLoader cl;
if (Globals.IS_SECURITY_ENABLED){
cl = AccessController.doPrivileged(
new PrivilegedGetClassLoader(clazz));
} else {
// 如果父类再加载不到
cl = clazz.getClassLoader();
}
log.debug(" Loaded by " + cl.toString());
}
return clazz;
}
复制代码
Web应用默认的类加载顺序是(打破了双亲委派规则):
- 先从 JVM 的 BootStrapClassLoader 中加载。
- 加载 Web 应用下 /WEB-INF/classes 中的类。
- 加载 Web 应用下 /WEB-INF/lib/*.jap 中的 jar 包中的类。
- 加载上面定义的 System 路径下面的类。
- 加载上面定义的 Common 路径下面的类。
如果在配置文件中配置了“,那么就是遵循双亲委派规则,加载顺序如下:
- 先从 JVM 的 BootStrapClassLoader 中加载。
- 加载上面定义的 System 路径下面的类。
- 加载上面定义的 Common 路径下面的类。
- 加载 Web 应用下 /WEB-INF/classes 中的类。
- 加载 Web 应用下 /WEB-INF/lib/*.jap 中的 jar 包中的类。