- 类加载的7个阶段(生命周期):
- 加载
- 生成二进制字节流,将静态结构转化成方法去的运行时数据结构
- 生.class对象,作为方法区这个类的各种数据的访问入口
- JVM ClassLoader 在这个阶段
- 验证
- 文件格式验证 :验证是否符合规范 ,文本头,主次版本验证
- 元数据验证:保证描述符合java语言的要求
- 类是否有父类
- 是否继承了不允许被继承的类(final修饰过的类)
- 如果这个类不是抽象类,是否实现其父类或接口中所有要求实现的方法
- 类中的字段、方法是否与父类产生矛盾(如:覆盖父类final类型的字段,或者不符合个则的方法)
- 字节码验证:确定程序语义是合法的
- 符号引用验证:
- 符号引用中通过字符串描述的全限定名是否能找到对应的类。
- 在指定类中是否存在符合方法的字段描述符以及简单名称所描述的方法和字段。
- 符号引用中的类、字段、方法的访问性(private、protected、public、default)是否可被当前类访问。
- 准备
- 是正式为类变量分配内存并设置初始值阶段。
- public static int value = 123 初始时 value = 0;(因为这时候尚未开始执行任何Java方法,而把value赋值为123的putstatic指令是程序被编译后,存放于类构造器<clinit>()方法之中,所以吧value赋值为123的动作将在初始化阶段才会执行。)
- static final int value = 123 初始时就是 value =123(常量)
- !注意!这里是类中的静态变量设置初始值(操作的是JVM的方法区),不包含实例变量(实例变量存放在堆内存与对象实例在一起),实例变量是在对象实例化的时候初始化分配值得。
- 是正式为类变量分配内存并设置初始值阶段。
- 解析
- 是虚拟机将常量池中的符号引用替换为直接引用的过程
- 符号引用:一个java类将会编译成一个class文件。在编译时,java类并不知道所引用的类的实际地址,因此只能使用符号引用来代替,符号引用就是一组没有歧义的字符串。
- 直接引用:指针或者是地址偏移量。直接引用的对象一定是被加载到内存中。
- 是虚拟机将常量池中的符号引用替换为直接引用的过程
- 初始化
- 执行类的构造器<clinit>:真正的开始执行类中定义的java程序代码
- 初始化静态变量,静态块中的数据(一个类加载器只会初始化一次)
- 在子类的<clinit>调用前要保证父类的<clinit>被调用
- <clinit> 是线程安全的,执行<clinit>的线程需要先获取锁才能进行初始化操作,保证只有一个线程执行。(线程安全的懒汉单例模式)
- public class SingTest {
- private SingTest() {
- }
- private static class SingTestDemo{
- private static SingTest getInstance = new SingTest(); }
- public static SingTest getOurInstance(){
- return SingTestDemo.getInstance; }
- }
- public class SingTest {
- 对应的字节码命令:new getstatic putstatic或invokestatic时
- 对应的场景:
- 使用new关键字实例对象
- 读取或者设置一个类的静态字段(final修饰的除外)
- 调用一个类的静态方法
- 对应的场景:
- 使用
- 卸载
- 加载
- ClassLoader 什么是类加载器
- ClassLoader 是一个抽象类
- classLoader 可以定制,可以进行并行加载,使用的是委派机制
- 可以定制
- JVM中的类加载
- 启动类加载器 BootStrap ClassLoader 引导类装入器是用本地代码实现的类装入器,它负责将 jdk中jre/lib下面的核心类库或-Xbootclasspath选项指定的jar包加载到内存中。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作。
- 扩展类加载器 Extension ClassLoader 扩展类加载器是由Sun的ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现的。它负责将jdk中jre/lib/ext或者由系统变量-Djava.ext.dir指定位置中的类库加载到内存中。开发者可以直接使用标准扩展类加载器。
- 系统类加载器 System ClassLoader :系统类加载器是由 Sun的 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的。它负责将系统类路径java -classpath或-Djava.class.path变量所指的目录下的类库加载到内存中。开发者可以直接使用系统类加载器。
- 双亲委派机制
- 每个加载器都有父类加载器,如果一个类加载器在接到类加载的请求时,它不会自己加载,而且把这个请求任务委托给父类加载器去完成,依次递归,如果父类加载器能完成任务,就返回成功,如果父类加载器不能完成任务,才会自己加载。
- 类的加载器中父子关系不是继承的方式实现的,是以组合的关系方式复用父加载器的代码的。
- 优势:使java类有一定的优先级的层次关系。相同类型的类在各种类加载环境中都是同一个类。如果不是双亲委派模型,那么一样的类的类型不相同。
- Java类随着加载它的类加载器一起具备了一种带有优先级的层次关系。比如,Java中的Object类,它存放在rt.jar之中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此Object在各种类加载环境中都是同一个类。如果不采用双亲委派模型,那么由各个类加载器自己取加载的话,那么系统中会存在多种不同的Object类。
- 案例一 双亲委派模型的问题:顶层ClassLoader,无法加载底层ClassLoader的类。 JDK的javax.xml.parsers包中定义了xml解析的类接口 Service Provider Interface SPI 位于rt.jar 即接口在启动ClassLoader中。而SPI的实现类,可能由第三方提供,AppClassLoader进行加载。 解决思路:可以在线程中放入底层的ClassLoader到Thread. setContextClassLoader()中,然后在顶层ClassLoader中使用Thread.getContextClassLoader()获得底层的ClassLoader进行加载第三方实现。
- 案例二 Tomcat中使用了自定ClassLoader,并且也破坏了双亲委托机制。 每个应用使用WebAppClassloader进行单独加载,他首先使用WebAppClassloader进行类加载,如果加载不了再委托父加载器去加载,这样可以保证每个应用中的类不冲突。每个tomcat中可以部署多个项目,每个项目中存在很多相同的class文件(很多相同的jar包),他们加载到jvm中可以做到互不干扰。
- 案例三: 利用破坏双亲委派来java的类热部署实现(每次修改类文件,不需要重启服务)。 因为一个Class只能被一个ClassLoader加载一次,否则会报java.lang.LinkageError。当我们想要实现代码热部署时,可以每次都new一个自定义的ClassLoader来加载新的Class文件。JSP的实现动态修改就是使用此特性实现。
- 亲身验证:IDEA 开启热部署时,判断类不一致,就是热部署破坏了双亲委派模型,导致同一个类在不通的类加载器中。
- ClassLoader加载.class文件的方式不仅限于从jar包中读取,还可以从种地方读取,因为ClassLoader加载时需要的是byte[]数组. ClassLoader加载Class文件方式:
- 从本地系统中直接加载
- 通过网络下载.class文件
- 从zip,jar等归档文件中加载.class文件
- 从专有数据库中提取.class文件
- 将Java源文件动态编译为.class文件
- jvm 调优
- 没有必要调整JVM,如果要调整一定是你的代码有问题
- CMS 收集器
- G1收集器
- 并发垃圾回收器
- •不要把MaxGCPauseMillis设置的很小,优先设定一个GCTimeRatio来达到吞吐量的目标
- CMS Full GC频繁
- •增大堆的容量
- •调整CMSInitiatingOccupancyFraction,尽早启动并发后台线程
- •增加后台线程数
- G1 Full GC频繁
- •增大堆容量
- •调整InitiatingHeapOccupancyFraction,尽早启动并发后台线程
- •增加后台线程数
- •Mixed GC
JVM类加载机制 --由调用链使用字节码增强技术引出的类加载
最新推荐文章于 2021-02-25 00:17:28 发布