类加载器

什么是类加载器

就是负责把磁盘上的.class文件 ,加载到JVM内存中,并生成java.lang.Class类的一个实例。

java程序写好以后是以.java(文本文件)的文件存在磁盘上,我们通过(bin/javac.exe)编译命令把.java文件编译成.class文件(字节码文件),并存在磁盘上。但是程序要运行,首先一定要把.class文件加载到JVM内存中才能使用的。

JVM并不是一开始就会将所有的类加载到内存,而是用到某个类,才会去加载,只加载一次。




java中类加载的过程

类加载过程分为三个步骤:装载(Load),链接(Link)和初始化(Initialize)

  • 加载:将.class文件的二进制字节流读入内存(JDK1.7及之前为堆内存,JDK1.8及之后为metaspace),并在堆内存中为之创建Class对象(注意在堆中),作为.class进入内存后的数据的访问入口

    • 懒加载:java虚拟机规范并没有规定什么时候加载,一般使用的时候才加载
    • class相同的必要条件:1、类的完整名称必须一致,2、加载这个类的加载器实例对象必须同一个
  • 连接

    1. 验证:确保字节流中包含信息符合JVM要求,不会危害JVM自身安全。主要包括四种验证,文件格式验证,元数据验证,字节码验证,符号引用验证。
    2. 准备:为类的类变量开辟空间并赋默认值(final static修饰的会显示初始化,实例变量不会初始化。即final修饰的变量在编译时确定值)
    3. 解析:类class字节码都包含有本类独有的“常量池”,这个常量池相当于方法区中的运行时常量池的子集,解析的过程实际上就是常量池内的符号引用替换为直接引用的过程。(比如String s =”aaa”,转化为 s的存储内容为指向“aaa”的地址)
  • 初始化:执行类构造方法clinit的过程

    • 此方法不是我们创建的,是javac编译器自动收集类变量的赋值动作和静态代码块中的语句合并而来
    • 执行顺序按原文件实际顺序
    • 有父类时需要先执行父类的clinit
    • 一个类的clinit会在多线程下被加锁

注意事项:

  1. 遇到父类未初始化时会先加载父类再加载子类,以此类推,object类一定是先被加载。
  2. 虚拟机会保证一个类的构造器方法在多线程环境中被正确加锁和同步
  3. 当访问一个java类的静态域时,只有真正声明这个静态变量的类才会被初始化。
  4. JVM中两个class对象是否相等必须满足两个条件:第一是完整类名必须一致,包括包名,第二类的加载器必须相同。




类加载器层次结构

引导类加载器(bootstrap class loader):

  • 它用来加载 Java 的核心库(jre/lib/rt.jar)
  • 并不继承自classloader,没有父加载器
  • 是用原生C++代码来实现的
  • 其是扩展类加载器、系统类加载器的父加载器
  • 只加载java、javax、sun开头的包

扩展类加载器(extensions class loader):

  • 用java语言编写
  • 继承自classloader
  • 用来加载 Java 的扩展库(jre/ext/*.jar)
  • 用户创建的类也在扩展目录也会被加载

系统类加载器(app class loader):

  • java语言编写
  • 继承自classloader
  • 加载环境变量classpath或系统属性java.class.path指定路径下的类库
  • Java 应用的类都是由它来完成加载的
  • 可以通过 ClassLoader.getSystemClassLoader()来获取它,其是自定义类加载器的父加载器

自定义类加载器(custom class loader):

除了系统提供的类加载器以外,开发人员可以通过继承 java.lang.ClassLoader类并重写findclass方法的方式实现自己的类加载器,以满足一些特殊的需求。




双亲委派机制

某个特定的类加载器在接到加载类的请求时,首先将加载任务委托交给父类加载器,父类加载器又将加载任务向上委托,直到最父类加载器,如果最父类加载器可以完成类加载任务,就成功返回,如果不行就向下传递委托任务,由其子类加载器进行加载。

优势有什么?

  • 避免类重复加载
  • 保护java核心库的安全性(例如:如果用户自己写了一个java.lang.String类就会因为双亲委派机制不能被加载,不会破坏原生的String类的加载,否则可能破坏java核心库并且造成安全问题)




自定义类加载器

  • 如何实现:继承ClassLoader后重写findclass方法即可

  • 类加载代码逻辑:执行类加载器中的loadclass方法,此方法中先从缓存获取,如果获取不到则委托父类加载器加载,即实现了双亲委派,直到找到可加载的加载器,再执行findclass方法进行加载。

  • 如何打破双亲委派:重写loadclass方法

  • 打破双亲委派的案例:tomcat中的类加载、热部署等,(大体思路是每次加载不去判断是否已经加载,而是直接加载并让上一个类加载器以及加载的类失效)

  • 什么场景下需要自定义类加载器?

    • 资源隔离:简单来说,我们项目中引入某外部框架A和B,他们都依赖另一框架C,但是依赖的版本不同,这个时候可以做资源隔离加载,各自加载各自的
    • 修改类加载方式
    • 扩展加载源
    • 代码保护:将字节码加密,并自行解密加载
    • 热部署:在运行时更新java类文件




classloader常用方法

是一个抽象类,除了启动类外的其它类加载器都是继承自它

  • getparent:返回父类加载器
  • loadclass:加载指定名称的类,返回java.lang.class类的实例
  • findclass:查找指定名称的类,返回java.lang.class类的实例
  • findloadedclass:查找指定名称并且已经被加载的类,返回java.lang.class类的实例



版权声明:本文为博主原创文章,欢迎转载,转载请注明作者、原文超链接,感谢各位看官!!!

本文出自:monkeyGeek

座右铭:生于忧患,死于安乐

欢迎志同道合的朋友一起交流、探讨!

monkeyGeek
#

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×