JVM 类加载机制
类的生命周期
JVM 类加载机制分为五个部分:加载,验证,准备,解析,初始化。
加载
将 .class 文件内容读入内存(通过全限定名找到 .class 文件)。
类加载过程中的第一个阶段,会在内存中生成一个代表这个类的 java.lang.Class 对象。这里不一定非得要从一个 .class 文件获取,可以从 ZIP 包中读取(比如从 jar 包和 war 包中读取),也可以在运行时计算生成(动态代理)等。
链接
验证
验证字节码文件的正确性
这一阶段的主要目的是为了确保 .class 文件的字节流中包含的信息是否符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
准备
给类的静态变量分配内存,并赋予默认值。
解析
类装载器装入类所引用的其他所有类,将常量池中的符号引用替换为直接引用的过程。
符号引用就是 class 文件中的:CONSTANT_Class_info、CONSTANT_Field_info、CONSTANT_Method_info 等类型的常量。
符号引用
符号引用与虚拟机实现的布局无关,引用的目标并不一定要已经加载到内存中。各种虚拟机实现的内存布局可以各不相同,但是它们能接受的符号引用必须是一致的,因为符号引用的字面量形式明确定义在 Java 虚拟机规范的 Class 文件格式中。
直接引用
直接引用可以是指向目标的指针,相对偏移量或是一个能间接定位到目标的句柄。如果有了直接引用,那引用的目标必定已经在内存中存在。
初始化
为类的静态变量赋予正确的初始值,执行静态代码块。
前面的类加载阶段之后,除了在加载阶段可以自定义类加载器以外,其它操作都由 JVM 主导。到了初始阶段,才开始真正执行类中定义的 Java 程序代码。
类加载器
启动类加载器(Bootstrap Class Loader)
用于加载 JRE 的核心类库(c/c++代码实现),如:rt.jar、charsets.jar。加载 <JAVA_HOME>/lib 目录下的类。
扩展类加载器(Extension Class Loader)
加载 JRE 扩展类。加载 <JAVA_HOME>/lib/ext 目录下的类。
系统类加载器(Application Class Loader)
加载 classpath 路径下的类。
用户自定义加载器(User Defined Class Loader)
自定义类加载器,加载自定义路径下的类
类加载机制
全盘负责委托机制
当一个 ClassLoader 加载一个类的时候,除非显示的使用另一个 ClassLoader,该类锁依赖和引用的类也由这个 ClassLoader 加载。
双亲委派机制(父类加载机制)
当一个类加载器收到了类加载请求,会首先将该请求委托给父类加载器(非继承关系,逻辑上的父类)去完成,并且每一个层次类加载器都是如此,只有父类加载器无法完成加载请求时,子类加载器才尝试自己去加载。好处:
避免用户自定义的包名覆盖系统的包功能
避免类的重复加载
破坏双亲委派机制的场景
Tomcat 类加载器
Tomcat 是 web 容器,一个 web 容器可能要部署两个或者多个应用程序,不同的应用程序,可能会依赖同一个第三方类库的不同版本,因此要保证每一个应用程序的类库都是独立、相互隔离的;部署在同一个 web 容器中的相同类库的相同版本可以共享,否则,会有重复的类库被加载进 JVM;web 容器也有自己的类库,不能和应用程序的类库混淆,需要相互隔离;web 容器支持 jsp 文件修改后不用重启(热部署);
Tomcat 使用 Java 默认类加载器的问题
默认的类加载器无法加载两个相同类库的不同版本,类的全限定类名是一样的,所以无法解决相互隔离的问题
修改 jsp 文件后,因为类名一样,默认的类加载器不会重新加载,而是使用方法区中已经存在的类;所以需要每个 jsp 对应一个唯一的类加载器,当修改 jsp 的时候,直接卸载唯一的类加载器,然后重新创建类加载器,并加载 jsp 文件
Tomcat 的类加载器
CommonClassLoader:tomcat 最基本的类加载器,加载路径(/common/*
)中的 class 可以被 tomcat 和各个 webapp 访问
CatalinaClassLoader:tomcat 私有的类加载器,webapp 不能访问其加载路径(/server/*
)下的 class,即对 webapp 不可见
SharedClassLoader:各个 webapp 共享的类加载器,对 tomcat 不可见,加载路径:/shared/*
WebappClassLoader:webapp 私有的类加载器,只对当前 webapp 可见,加载路径:/WEB-INF/*
JspClassLoader:每一个 jsp 文件对应一个 JspClassLoader
默认情况下,conf 目录下的 catalina.properties 文件,没有指定 server.loader 以及 shared.loader,所以 tomcat 没有建立 CatalinaClassLoader 和 SharedClassLoader 的实例,这两个都会使用 CommonClassLoader 来代替。Tomcat6 之后,把 common、shared、server 目录合成了一个 lib 目录。
Tomcat 的类加载机制
CommonClassLoader 能加载的类都可以被 Catalina ClassLoader 和 SharedClassLoader 使用,从而实现了公有类库的共用;
CatalinaClassLoader 和 Shared ClassLoader 加载的类则相互隔离;
WebAppClassLoader 可以使用 SharedClassLoader 加载到的类,但各个 WebAppClassLoader 加载的类则相互隔离;
JasperLoader 的加载范围仅仅是这个 Jsp 文件所编译出来的 .class 文件,目的就是为了被丢弃:当 Web 容器检测到 JSP 文件被修改时,会替换掉目前的 JasperLoader 的实例,并通过再建立一个新的 Jsp 类加载器来实现 Jsp 文件的 HotSwap 功能;
WebappClassLoader 和 jasperClassLoader 没有传给父类加载器去加载,先自己加载。加载不到时再走双亲委托。
可以通过在 Context.xml 文件中加上 <Loader delegate = "true">
开启正统的“双亲委派”加载机制。
版权声明: 本文为 InfoQ 作者【Alex🐒】的原创文章。
原文链接:【http://xie.infoq.cn/article/e77b06901e7c2a59853f9a179】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
评论