名词解释
*.class文件的结构
查看指令: javap -verbose hello.class
包含信息:
结构信息(版本号,大小信息);
元数据(类,继承,接口,字段声明,方法声明);
方法信息(语句,表达式,异常,堆栈大小与类型,调试信息)
符号引用
符号引用符号引用就是 class 文件中的
- CONSTANT_Class_info
- CONSTANT_Field_info
- CONSTANT_Method_info等类型的常量
符号引用以 一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能够无歧义的定位到目标即可,使用符号引用时,被引用的目标不一定已经加载到内存中。
直接引用
直接引用可以是指向目标的指针,相对偏移量或是一个能间接 定位到目标的句柄。
如果有 了直接引用,那引用的目标必定已经在内存中存在。
参考:
个人理解,如果使用符号引用,虚拟机其实也不知道具体引用的类的内存地址,那么也就无法真正的调用到该类,所以要把符号引用转为直接引用,这样就能够真正定位到类在内存中的地址,如果符号引用转直接引用失败,就说明类还没有被加载到内存中,就会报错。
https://blog.csdn.net/weixin_38106322/article/details/109239971
类加载机制概述
1、类加载器把编译好的那些".class"字节码文件给加载到JVM中,供后续代码运行来使用。
2、JVM基于自己的 字节码执行引擎,来执行加载到内存里的类。
3、类加载的时机
一个类被使用到到时候
eg:比如你的代码中有一个"main()"方法,那么JVM就会从这个"main()"方法开始执行里面的代码。他需要哪个类的时候,就会使用类加载器来加载对应的类,反正对应的类就在".class"文件中。
4、通过Java命令执行代码的大体流程如下:
java xxx 指令 在Windows下对应就是一个exe文件。dll 相当于java jar包。
C++启动程序 -》创建java虚拟机 -》java类加载器 -》加载class文件
类加载机制详解
类是如何加载到jvm中
JVM 类加载机制分为五个部分:加载,验证,准备,解析,初始化,下面我们就分别来看一下这五个过程。
加载过程分析
1. 加载
通过类全名来获取定义此类的二进制字节流,
将字节流所代表的 静态存储结构转换为方法区中运行时数据结构。
在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
扩展:
- 静态存储结构:数据存储于class文件的结构。
- 运行时数据结构:数据存储于JVM的数据结构。
- 二进制流是从.class文件中获取的。这个class文件并不一定只来源于本地,也可以来源于网络等。
2. 连接
将java二进制代码合并到java运行状态中的过程。
- 验证:校验字节码文件的正确性 目的保证class流的格式正确。
(简单来说,这一步就是根据Java虚拟机规范,来校验你加载进来的".class"文件中的内容,是否符合指定的规范。)
- 准备:Java虚拟机为类变量(static修饰的变量)、加载到内存中的类分配内存空间,设置默认初始值。
- 示例代码:
public static int v=1; 在准备阶段中,v会被设置为0 在初始化的<clinit>中才会被设置为1
对于static final类型,在准备阶段就会被赋上正确的值: public static final int v=1;
- 解析::将符号引用替换为直接引用,
- 该阶段会把一些静态方法(符号引用,比如 main()方法)替换为指向数据所存内存的指针或句柄等(直接引用),这是所谓的静态链接过 程(类加载期间完成),
- 动态链接:是在程序运行期间完成的将符号引用替换为直接引用
3 初始化 (核心阶段)
初始化阶段是类加载最后一个阶段,前面的类加载阶段之后,除了在加载阶段可以自定义类加载器以外,其它操作都由 JVM 主导。到了初始阶段,才开始真正执行类中定义的 Java 程序代码。如: 在初始化阶段,JVM执行类的初始化语句, 为类的静态变量赋予用户给定的初始值,执行静态代码块 。
什么时候会初始化一个类?
一般来说有以下一些时机:比如"new ReplicaManager()"来实例化类的对象了,此时就会触发类的加载到初始化的全过程,把这个类准备好,然后再实例化一个对象出来;(这里也说明了为什么 构造方法是在 静态代码块之后执行(先准备代码在实例化 ))
或者是包含"main()"方法的主类,必须是立马初始化的。
此外,这里还有一个非常重要的规则,就是如果初始化一个类的时候,发现他的父类还没初始化,那么必须先初始化他的父类
4 使用
5 卸载
http://www.cnblogs.com/ITtangtang/p/3978102.html
加载器的类型
类加载器类型总体来说可以分为以下几种:启动类加载器、其他类加载器。类的加载过程遵循双亲委派模型。
1、Bootstrap ClassLoader(启动类加载器)
JVM启动,启动类加载器 加载 Java安装目录下jre 的 "lib"目录中的核心类库
/opt/apps/jdk1.8.0_251/jre/lib
--> rt.jar、charsets.jar等> rt.jar、charsets.jar等
2、Extension ClassLoader(扩展类加载器)
JVM启动,通过扩展类加载器加载"lib/ext"目录
[root@iZ2zei4wx78lgr80odfj6eZ ext]# pwd# pwd
/opt/apps/jdk1.8.0_251/jre/lib/ext
3、Application ClassLoader(应用程序类加载器)
加载ClassPath 环境变量所指定的路径中的类
4、自定义类加载器
根据自己需求进行类加载
类加载器初始化过程
参见类运行加载全过程图可知其中会创建JVM启动器实例sun.misc.Launcher。
sun.misc.Launcher初始化使用了单例模式设计,保证一个JVM虚拟机内只有一个 sun.misc.Launcher实例。
在Launcher构造方法内部,其创建了两个类加载器,分别是 sun.misc.Launcher.ExtClassLoader(扩展类加载器)和sun.misc.Launcher.AppClassLoader(应 用类加载器)。
JVM默认使用Launcher的getClassLoader()方法返回的类加载器AppClassLoader的实例加载我们 的应用程序。
双亲委派模型
概述(向上寻找、向下委托)
类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是 把这个请求委派给父类加载器完成。
每个类加载器都是如此,只有当父加载器在自己的搜索范围内找不到指定的类时(即ClassNotFoundException),
子加载器才会尝试自己去加载,这个过程为称为双亲委派模型。
AppClassLoader加载类的双亲委派机制源码
AppClassLoader 的loadClass方法最终会调用其父类ClassLoader的loadClass方法,
-
- 首先,检查一下指定名称的类是否已经加载过,如果加载过了,就不需要再加载,直接 返回。
-
- 如果此类没有加载过,那么,再判断一下是否有父加载器;如果有父加载器,则由父加 载器加载(即调用parent.loadClass(name, false);).或者是调用bootstrap类加载器来加 载。
-
- 如果父加载器及bootstrap类加载器都没有找到指定的类,那么调用当前类加载器的 findClass方法来完成类加载。
- 对应代码位置://ClassLoader的loadClass方法,里面实现了双亲委派机制
为什么需要双亲委派模型
- 保证类加载机制的安全性。
- 防止病毒代码植入,自己写的java.lang.String.class类不会被加载,这样便可以防止核心 API库被随意篡改
- 避免类的重复加载:
- 当父亲已经加载了该类时,就没有必要子ClassLoader再加载一 次,保证被加载类的唯一性
- 参考 : https://www.cnblogs.com/wxd0108/p/6681618.html
全盘负责委托机制
- "全盘负责"是指当一个ClassLoder装载一个类时,除非显示的使用另外一个ClassLoder,该类 所依赖及引用的类也由这个ClassLoder载入。
如何打破双亲委派模式
- 覆写loadClass方法
- protected Class<?> loadClass(String name, boolean resolve)
- 执行findClass()
- 首先会使用自定义类加载器加载类, 不在向上委托, 直接由自己执行
- 参考:https://www.cnblogs.com/ITPower/p/13211490.html
打破双亲委派模式的案例
- tomcat类加载器
- Common类加载器,加载Tomcat和Web应用都复用的类。
- Catalina类加载器,加载Tomcat专有类(这些类在web应用中不可见)
- Shared类加载器,加载所有Web应用都复用都类。(这些类tomcat中不可见)
- WebappClassLoader:webapp私有的类加载器,只对当前webapp可见
- Jsp类加载器,不同的jsp页面有不同的类加载器,方便实现jsp页面的热插拔。
- 类加载机制原理
-
a. CommonClassLoader能加载的类都可以被Catalina ClassLoader和SharedClassLoader使用,从而实现了公有类库的共用
b. CatalinaClassLoader和Shared ClassLoader自己能加载的类则与对方相互隔离
c. WebAppClassLoader可以使用SharedClassLoader加载到的类,但各个WebAppClassLoader实例之间相互隔离,多个WebAppClassLoader是同级关系
-
d. 而JasperLoader的加载范围仅仅是这个JSP文件所编译出来的那一个.Class文件,它出现的目的就是为了被丢弃:当Web容器检测到JSP文件被修改时,会替换掉目前的JasperLoader的实例,并通过再建立一个新的Jsp类加载器来实现JSP文件的HotSwap功能
- 为什么打破双亲委派模式?
- 隔离性
- 一个web应用可以部署多个应用程序,不同的应用程序可能会依赖同一个第三方类库的不同版本。
- 因此需要保证每个应用程序的类库是独立的、相互隔离的。
- web容器也有自己的类库,不能和应用程序的类库混淆,需要相互隔离
- 一个web应用可以部署多个应用程序,不同的应用程序可能会依赖同一个第三方类库的不同版本。
- 共享性
- 部署在同一个web容器中的相同类库的相同版本可以共享,否则,会有重复的类库被加载进JVM
- 独立性
- web容器支持jsp文件修改后不用重启,jsp文件也是要编译成.class文件的,支持HotSwap功能
- 隔离性
- tomcat如何打破双亲委派模式的?
- 参考tomcat类加载机制