JVM 类加载机制
类加载机制
类的生命周期
JVM 类加载机制分为五个部分:加载,验证,准备,解析,初始化。
- 加载
将 .class 文件内容读入内存(通过全限定名找到 .class 文件)。
类加载过程中的第一个阶段,会在内存中生成一个代表这个类的 java.lang.Class 对象。这里不一定非得要从一个 .class 文件获取,可以从 ZIP 包中读取(比如从 jar 包和 war 包中读取),也可以在运行时计算生成(动态代理)等。 -
链接
- 验证
验证字节码文件的正确性
这一阶段的主要目的是为了确保 .class 文件的字节流中包含的信息是否符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。 -
准备
给类的静态变量分配内存,并赋予默认值。static int a = 10; // 准备阶段会赋值为 0 // 将 a 赋值为 10 的指令存放于类构造方法中 static final int a = 10; // 准备阶段会赋值为 10 // 编译阶段生成 ConstantValue 属性,准备阶段会根据 ConstantValue 直接赋值
- 解析
类装载器装入类所引用的其他所有类,将常量池中的符号引用替换为直接引用的过程。
符号引用就是 class 文件中的:CONSTANT_Class_info、CONSTANT_Field_info、CONSTANT_Method_info 等类型的常量。符号引用
符号引用与虚拟机实现的布局无关,引用的目标并不一定要已经加载到内存中。各种虚拟机实现的内存布局可以各不相同,但是它们能接受的符号引用必须是一致的,因为符号引用的字面量形式明确定义在 Java 虚拟机规范的 Class 文件格式中。
直接引用
直接引用可以是指向目标的指针,相对偏移量或是一个能间接定位到目标的句柄。如果有了直接引用,那引用的目标必定已经在内存中存在。
- 验证
-
初始化
为类的静态变量赋予正确的初始值,执行静态代码块。
前面的类加载阶段之后,除了在加载阶段可以自定义类加载器以外,其它操作都由 JVM 主导。到了初始阶段,才开始真正执行类中定义的 Java 程序代码。
类加载器
启动类加载器(Bootstrap Class Loader)
用于加载 JRE 的核心类库(c/c++代码实现),如:rt.jar、charsets.jar。加载
扩展类加载器(Extension Class Loader)
加载 JRE 扩展类。加载
系统类加载器(Application Class Loader)
加载 classpath 路径下的类。
用户自定义加载器(User Defined Class Loader)
自定义类加载器,加载自定义路径下的类
类加载机制
全盘负责委托机制
当一个 ClassLoader 加载一个类的时候,除非显示的使用另一个 ClassLoader,该类锁依赖和引用的类也由这个 ClassLoader 加载。
双亲委派机制(父类加载机制)
当一个类加载器收到了类加载请求,会首先将该请求委托给父类加载器(非继承关系,逻辑上的父类)去完成,并且每一个层次类加载器都是如此,只有父类加载器无法完成加载请求时,子类加载器才尝试自己去加载。好处:
- 避免用户自定义的包名覆盖系统的包功能
- 避免类的重复加载
- JVM 通过 ClassLoader 和类本身共同判断两个 Class 是否相同。即使两个类属于同一个 Class 文件,如果加载他们的 ClassLoader 不同,那么他们将被视为不同的类。
破坏双亲委派机制的场景
Tomcat 类加载器
Tomcat 是 web 容器,一个 web 容器可能要部署两个或者多个应用程序,不同的应用程序,可能会依赖同一个第三方类库的不同版本,因此要保证每一个应用程序的类库都是独立、相互隔离的;部署在同一个 web 容器中的相同类库的相同版本可以共享,否则,会有重复的类库被加载进 JVM;web 容器也有自己的类库,不能和应用程序的类库混淆,需要相互隔离;web 容器支持 jsp 文件修改后不用重启(热部署);
Tomcat 使用 Java 默认类加载器的问题:
1. 默认的类加载器无法加载两个相同类库的不同版本,类的全限定类名是一样的,所以无法解决相互隔离的问题
2. 修改 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">
开启正统的“双亲委派”加载机制。
加载类的时机有哪些?
- 创建新对象
- 使用 java.lang.reflect 反射调用
- 当初始化一个类时,父类没有初始化,则先初始化父类
- 当虚拟机启动,需要执行main()的主类,JVM首先初始化该类