这个问题的核心是classloader
上图中 启动类加载器,扩展类加载器,应用程序类加载器是 jvm 自带的类加载器.
comm catalina shared webapp 是tomcat 扩展的加载器,他们分别加载 /common/*
、/server/*
、/shared/*
WebAppClassLoader 负载加载 webapp 目录的jar 和 class .
WebAppClassLoader 没有遵循双亲委托模式,他的具体加载逻辑如下:
第1步:检查当前class loader 的缓存resourceEntries,是否已经加载,如果有直接返回。
第2步:检查 jvm 自带加载器是否加载过。
第3步:尝试用 javaseClassLoader 去加载,为什么要做这个尝试呢,防止代码重写了java.lang.String 等jdk核心类,从而导致异常。
第4步:如果设置了双亲委托模式 delegateLoad=true ,尝试用父加载器加载。
第5步: 尝试从当前classloader 制定的目录中加载,同时会将加载信息放到缓存中 resourceEntries
这样就实现了资源隔离。
扩展信息:
1.通过gc 回收class loader ,从而可以实现回收class,重新创建classloader,然后重新load class ,从而达到不重启jvm更新class的目的(热更新),jsp 的热更新就是这么干的。
2. jvm instrumentation 机制可以通过agent,去修改正在运行中的已加载的class 对象,该方法很有用,比如生产环境出了问题,重启后问题无法复现,那么就可以在线增加日志排错.
阿里巴巴基于该原理开发了一个强大在线调试工具: https://github.com/alibaba/arthas
3. ClassFileTransformer 利用tranformer 和 class loader 可以实现很多很神奇的功能,比如所有controller 方法第一行打印出请求参数,实现这个目的可以用spring AOP,同时也可以用ClassFileTransformer。
groovy 通过大量的 ClassFileTransformer 修改了很多jdk 核心class 的方法.
相关资源: