Jvm的类加载机制
java应用程序在运行的过程中,会将使用到的类加载到jvm中。
完成的加载过程有如下几步:
- 加载 :
从磁盘读入字节码文件(磁盘io),类被使用时才会加载。例如:new Xialu(),new 对象时。
- 验证 :
校验字节码文件的正确性。例如:文件格式验证、元数据验证、字节码验证、符号引用验证等。
- 准备:
给类的静态变量分配内存,并赋予默认值。
- 解析 :
将符号引用替换为直接引用,此时会把一些静态方法(符号引用,比如
main()方法)替换为指向数据所在内存的指针或句柄等(直接引用),这是所谓的静态链接过程(类加载期间完成)
- 初始化:
将类的静态变量初始化为指定的值,并执行静态代码块(执行顺序为代码顺序)。
- 使用:
使用加载完成的对象
- 卸载
gc回收对象(非实时,由jvm控制)
Jvm类加载器
JVM 的类加载机制中有一个非常重要的⻆色叫做类加载器(ClassLoader),上面的加载流程就是由类加载器完成的。
java中主要有以下几种类加载器:
- 引导类加载器 BootstrapClassLoader:
负责加载JRE的lib目录下的核心类库,例如rt.jar等
- 扩展类加载器 ExtClassLoader:
负责加载扩展库JAVA_HOME/lib/ext目录下的jar中的类,如classpath中的jre,javax.*或者java.ext.dir指定位置中的类
- 系统类加载器 AppClassLoader:
负责加载ClassPath路径下的类包,主要就是加载自己定义的那些类
- 自定义加载器 :
负责加载自定义路径下的类
Jvm类加载器的父子(委托)关系
什么是双亲委派机制
当某个类被加载时,当前的类加载器会先把这个任务委托给父类加载器,递归这个操作,只有父类加载器找不到这类的时候,才会自己去加载这个类。
例如加载Xialu对象 AppClassLoader会先把这个任务交给ExtClassLoader,ExtClassLoader又会上交给BootstrapClassLoader,因为BootstrapClassLoader负责加载核心类库的类,ExtClassLoader负责加载扩展类,因此在它们负责的路径下是找不到这个类的,最后返回给AppClassLoader完成加载。
为什么要设计双亲委派机制
- 防止类被重复加载:
向上委托的时候会判断该类是否已经被加载过了,如果父类加载器已经加载了,就不用再重复加载了.
- 防止核心Api被随意篡改:
例如我们自己写的java.lang.String就不会被加载.
Jvm类加载器源码
直接来看类加载器的loadClass方法:java.lang.ClassLoader#loadClass(java.lang.String, boolean)
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;
}
}
- getClassLoadingLock;对目标类进行加锁,防止多个线程同时加载同一个类,造成重复加载。
- findLoadedClass(name); 判断目标类是否已经被加载,还未被加载则返回null。
- 双亲委派关键逻辑:
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
可以看到在这里获取当前类加载器的父类加载器,如果不等于null则由父类加载器加载目标类,并且父类加载器也同样实现了该逻辑。
如果为null则调用findBootstrapClassOrNull方法。
那么什么时候或者什么样的类加载器,父类加载器才会为空呢?
从图中可以看到BootstrapClassLoader为空,也就是扩展类加载器的上级加载器为null,这是因为启动类加载器不是由java编写的,所以在jvm中为null.
回到上面的源码,也就是当目标类委托到扩展类加载器之后继续向上委托时就会执行findBootstrapClassOrNull方法。
- c = findClass(name);当目标类c为null是,则会调用findClass查找该类再自己的加载路径范围之类。如果没有找到,则返回给下级类加载器。
简单总结就是先找父亲加载,不行就给儿子加载。#####
Tomcat的类加载机制
tomcat不是使用的双亲委派加载机制,这主要是因为tomcat是一个web服务器需要支持部署多个应用程序,不同的应用程序可能会依赖同一个第三方类 库的不同版本,不能要求同一个类库在同一个服务器只有一份,因此要保证每个应用程序的 类库都是独立的,保证相互隔离。
Tomcat类加载器
- commonClassLoader
通用类加载器加载Tomcat使用以及应用通用的一些类,位于CATALINA_HOME/lib下, 比如servlet-api.jar
- catalinaClassLoader
omcat容器私有的类加载器,加载路径中的class对于Webapp不可见
- sharedClassLoader
各个Webapp共享的类加载器,加载路径中的class对于所有Webapp可见,但是对于Tomcat容器不可见
- WebappClassLoader
各个Webapp私有的类加载器,加载路径中的class只对当前Webapp可见;
每个 webappClassLoader加载自己的目录下的class文件,不会传递给父类加载器,打破了双 亲委派机制。
Tomcat类加载器源码
直接来看WebappClassLoaderBase的loadClass方法:org.apache.catalina.loader.WebappClassLoaderBase#loadClass(java.lang.String, boolean)
@Override
public Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
if (log.isDebugEnabled()) {
log.debug("loadClass(" + name + ", " + resolve + ")");
}
Class clazz = null;
// Log access to stopped class loader
checkStateForClassLoading(name);
// (0) Check our previously loaded local class cache
/**
* 检查该类是否已经被webapp类加载器加载。
*/
clazz = findLoadedClass0(name);
if (clazz != null) {
if (log.isDebugEnabled()) {
log.debug(" Returning class from cache");
}
if (resolve) {
resolveClass(clazz);
}
return clazz;
}
// (0.1) Check our previously loaded class cache
/**
* 该方法直接调用findLoadedClasso本地方法,findLoadedClass0方法会检查jvm缓存中是否加载过此类(jvm内存)
*/
clazz = findLoadedClass(name);
if (clazz != null) {
if (log.isDebugEnabled()) {
log.debug(" Returning class from cache");
}
if (resolve) {
resolveClass(clazz);
}
return clazz;
}
// (0.2) Try loading the class with the system class loader, to prevent
// the webapp from overriding Java SE classes. This implements
// SRV.10.7.2
String resourceName = binaryNameToPath(name, false);
ClassLoader javaseLoader = getJavaseClassLoader();
boolean tryLoadingFromJavaseLoader;
try {
// Use getResource as it won't trigger an expensive
// ClassNotFoundException if the resource is not available from
// the Java SE class loader. However (see
// https://bz.apache.org/bugzilla/show_bug.cgi?id=58125 for
// details) when running under a security manager in rare cases
// this call may trigger a ClassCircularityError.
// See https://bz.apache.org/bugzilla/show_bug.cgi?id=61424 for
// details of how this may trigger a StackOverflowError
// Given these reported errors, catch Throwable to ensure any
// other edge cases are also caught
URL url;
if (securityManager != null) {
PrivilegedAction dp = new PrivilegedJavaseGetResource(resourceName);
url = AccessController.doPrivileged(dp);
} else {
url = javaseLoader.getResource(resourceName);
}
tryLoadingFromJavaseLoader = (url != null);
} catch (Throwable t) {
// Swallow all exceptions apart from those that must be re-thrown
ExceptionUtils.handleThrowable(t);
// The getResource() trick won't work for this class. We have to
// try loading it directly and accept that we might get a
// ClassNotFoundException.
tryLoadingFromJavaseLoader = true;
}
/**
* 尝试通过系统类加载器(AppClassLoader)加载类,防止webapp重写jdk的类。
* 例如:webapp加载一个java.lang.String类,是不被允许的。
*/
if (tryLoadingFromJavaseLoader) {
try {
clazz = javaseLoader.loadClass(name);
if (clazz != null) {
if (resolve) {
resolveClass(clazz);
}
return clazz;
}
} catch (ClassNotFoundException e) {
// Ignore
}
}
// (0.5) Permission to access this class when using a SecurityManager
if (securityManager != null) {
int i = name.lastIndexOf('.');
if (i >= 0) {
try {
securityManager.checkPackageAccess(name.substring(0,i));
} catch (SecurityException se) {
String error = sm.getString("webappClassLoader.restrictedPackage", name);
log.info(error, se);
throw new ClassNotFoundException(error, se);
}
}
}
boolean delegateLoad = delegate || filter(name, true);
// (1) Delegate to our parent if requested
/**
* 是否委派给父类加载.
*/
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
}
}
// (2) Search local repositories
/**
* 使用webapp加载
*/
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
}
// (3) Delegate to parent unconditionally
/**
* 如果webapp类加载器没有找到,则交给父加载器
*/
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);
}
从源码可以知道,WebappClassLoader再加载目标类时,会判断delegate是否启动父加载器(也就是双亲委派机制),默认是false,也就是不使用,而是直接使用WebappClassLoader加载目标类,如果WebappClassLoader找不到目标类,然后会尝试委托给父类加载器加载。