JVM

ClassLoader加载器

2018-05-03  本文已影响0人  哓晓的故事

Java类的加载机制

Java是面向对象的程式,通过Class来提供服务,Class是二进制字节码(byte code)需要通过类加载器加载后方可以使用。
类的加载是将.class文件的二进制数据读入到内存,放入Method Area内,将实例化的Class信息存储在Heap中。并向开发人员访问Method Area数据结构的接口
JVM规范允许类加载器做pre load,在pre load的时候发.class缺失并不会报错,只有在首次使用类的时候才报告错误。
判断两个类是否相当,首先要类相等,其次是类加载器要相等

任何一个Java类,都是通过某个ClassLoader加载的。通过这个ClassLoader加载的类的实例,会保存这个类对象的引用,也就是MyClass.class这种。而类对象,会保留这个ClassLoader的引用。反过来,在ClassLoader中,也会保持对这个类对象的引用。
ClassLoader和类对象之间是双向引用

JAVA开发人员眼中的JDK类加载器分类

  1. BootStrap ClassLoader 负责加载jre/lib 下的基础类库,类似rt.jar,使用C/C++编写,不可被Java程式引用
  2. Extension ClassLoader 负责加载jre/lib/ext目录下的所有类库,所有的Application ClassLoader都是继承此加载器
  3. Application ClassLoader 负责加载$ClassPath目录下所指定的类

类加载方式

类加载方式.png
  1. new Class() 隐式加载
    • 自动加载静态方法块,带参构造器
    • 并且实例化(将装载初始化合并成一个)
  2. Class.forName() 显示加载
    • 解析类,并执行类中的静态方法块【初始化】(也可以选择不加载静态方法块),无参构造器
    • 返回一个给定类或者接口的一个 Class 对象,如果没有给定 classloader, 那么会使用根类加载器。如果 initalize 这个参数传了 true,那么给定的类如果之前没有被初始化过,那么会被初始化
    • http://www.runoob.com/w3cnote/java-class-forname.html
  3. ClassLoader.loadClass()
    • 不会自动加载静态方法块,采用parent delegate装载
    • Spring框架中的IOC的实现就是使用的ClassLoader,减少初始化造成的性能损耗(只是先加载类到虚拟机),绝大多数对象,在未使用前,都可以不先初始化,原语spring的lazy
    • spring 为什么使用 classloader

分析加载源码原理

JDK类加载机制

类的生命周期

加载(load)-->验证(validate)-->准备(prepare)-->解析(parse)-->初始化(init)-->使用(using)-->卸载(unloading)

类的生命周期.png

JDK双亲委派模式

双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,他首先不会自己去尝试加载这个类,而是把这个请求委派父类加载器去完成。每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个请求(他的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载

按照双亲委派机制加载类,每当需要加载一个新的类时,当前的类加载器会先委托其父加载器,查询有没有加载该类。如果父类加载器已近加载该类,那么直接返回加载的class对象,如果没有那么继续向上寻找父类加载器,如果在祖宗类加载器Bootstrap都没有加载该类,那么需要当前的类加载器自己加载,如果当前的类加载器也不能加载则会跑出ClassNotFoundException异常 (PS:类加载器没有向下寻找,没有getChild只有getParent)。

Thread.currentThread().getContextClassLoader()指出当前的类加载器是AppClassLoader,需要加载MyClassLoader.class先在父类加载器(ExtClassLoader)中寻找,没有再向祖宗类加载中寻找(Bootstrap ClassLoader),还没有找到那么AppClassLoader自己去加载。

意义:

如何破坏双亲委任模型?

第一次:在双亲委派模型出现之前—–即JDK1.2发布之前。
第二次:是这个模型自身的缺陷导致的。线程上下文类加载器(Thread Context ClassLoader)如果创建线程时还未设置,它将会从父线程中继承一个,如果在应用程序的全局范围内都没有设置过多的话,那这个类加载器默认即使应用程序类加载器。
第三次:为了实现热插拔,热部署,模块化,意思是添加一个功能或减去一个功能不用重启,只需要把这模块连同类加载器一起换掉就实现了代码的热替换。

tomcat自定义类加载器

tomcat是一个web容器,一个进程可以同时启动多个app线程
多个app之间的Class隔离,同时部分Class需要共享
tomcat 自定义了5个classLoader:

  1. CommonClassLoaderextends ApplicationClassLoader
    公用,catalina和shared共享
  2. CatalinaClassLoder extends CommonClassLoader
    web容器私有
  3. SharedClassLoader extends CommonClassLoader
    webapp之间共享
  4. WebAppClassLoader extends SharedClassLoader(通过配置是否绕过双亲委派)
    webapp私有
    内部重写了loadClass和findClass方法,实现了绕过“双亲委派”直接加载web应用内部的资源
    Context.xml文件中加上<Loader delegate = "true">开启正统的“双亲委派”加载机制
  5. JasperClassLoader extends WebAppClassLoader
    webapp私有

自定义类加载器

自定义类加载器,可以对字节码加密,防止反编译

GroovyClassLoader

            null                      // 即Bootstrap ClassLoader  
             ↑  
sun.misc.Launcher.ExtClassLoader      // 即Extension ClassLoader  
             ↑  
sun.misc.Launcher.AppClassLoader      // 即System ClassLoader  
             ↑  
org.codehaus.groovy.tools.RootLoader  // 以下为User Custom ClassLoader  
             ↑  
groovy.lang.GroovyClassLoader  
             ↑  
groovy.lang.GroovyClassLoader.InnerLoader  

groovy.lang.GroovyClassLoader 当一个脚本里定义了C这个类之后,另外一个脚本再定义一个C类的话,GroovyClassLoader就无法加载了,会将加载的类放置在方法区里
groovy.lang.GroovyClassLoader.InnerLoader 每次编译groovy源代码的时候,都会新建一个InnerLoader的实例,由于编译完源代码之后,已经没有对它的外部引用,除了它加载的类,所以只要它加载的类没有被引用之后,它以及它加载的类就都可以被GC了

结束生命周期

  1. System.exit()
  2. main()正常执行完成
  3. 程序执行过程中出现Exception异常或者Error错误
  4. 操作系统错误导致JVM终止

编译器

解释器

前端编译器(javac)

(*.java -> *.class(byte code))关注程序编码

JIT编译器

(HotSpot自带的JIT(server[开启C2]和client[开启C1])将*.class(byte code)转化为 机器码)关注程序运行

AOT编译器(GCJ)

(*.java -> 机器码

Java代码在执行时一旦被编译器编译为机器码,下一次执行的时候就会直接执行编译后的代码. 编译后的代码被缓存在 method area 中。除了jit编译的代码之外,java所使用的本地方法代码(JNI)也会存在codeCache(method area)中。

上一篇 下一篇

猜你喜欢

热点阅读