Class Loader Subsystem

Class Loader Subsystem类加载子系统负责从文件系统或者网络中加载class文件,class文件在文件开头有特定的文件标识。
ClassLoader只负责class文件的加载,能否运行由Execution Engine决定。
加载的类信息存放于Method Area方法区Method Area中还会存放run-time constant pool运行时常量池信息final字符串字面量数字常量这部分常量信息是class文件中常量池部分的内存映射

加载过程

类加载特性和机制

Loading

  • 通过一个类的全限定名全类名获取定义此类的二进制字节流
  • 将这个字节流所代表的静态存储结构转化为Method Area运行时数据结构
  • 在内存中创建一个代表这个类的java.lang.Class对象,作为访问这个类的数据的访问入口
  • 加载.class文件方式
    • 本地文件系统直接加载
    • 通过网络获取,典型场景Web Applet
    • 压缩包中读取,jar包,war包基础
    • 运行时计算生成,动态代理技术
    • 由其他文件生成,JSP应用
    • 从专有数据库中提前.class文件,比较少见
    • 加密文件中获取,防止反编译的保护措施

ClassLoader

JVM规范:所有派生于抽象类java.lang.ClassLoaderClassLoader都划分为User-Defined ClassLoader自定义类加载器
JVM支持两种 ClassLoader

  • Bootstrap ClassLoader 引导类加载器cc++实现,嵌套在jvm内部
  • User-Defined ClassLoader 自定义类加载器java实现,派生于抽象类java.lang.ClassLoader

常见类加载器:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// sun.misc.Launcher
// sun.misc.Launcher$AppClassLoader
// sun.misc.Launcher$ExtClassLoader

// java.net.URLClassLoader
// java.security.SecureClassLoader
// java.lang.ClassLoader
// URLClassLoader extends SecureClassLoader
// SecureClassLoader extends ClassLoader  
ClassLoader systemClassLoader=ClassLoader.getSystemClassLoader();//sun.misc.Launcher$AppClassLoader 系统类加载器java实现
ClassLoader extClassLoader = systemClassLoader.getParent();//sun.misc.Launcher$ExtClassLoader 扩展类加载器java实现
ClassLoader bootstrapClassloader = extClassLoader.getParent();//null 引导类加载器(由c和c++实现)

各种类加载器不是上下层关系,也不是父子类的继承关系,是包含关系,类似a文件夹里有b和c这种包含关系

  • Bootstrap ClassLoader 引导类加载器:
    • cc++实现,嵌套在jvm内部
    • 加载java核心库JAVA_HOME/jre/lib/rt.jar、resources.jarsun.boot.class.path路径下的内容用于提供JVM自身需要的类
    • 没有parent父类加载器
    • 加载ExtClassLoader扩展类加载器和AppClassLoader系统类加载器,并指定他们的parent父类加载器
    • 处于安全考虑,Bootstrap ClassLoader只加载包名为java、javax、sun等开头的类
  • User-Defined ClassLoader 自定义类加载器
    • ExtClassLoader 扩展类加载器。ClassLoader extClassLoader = systemClassLoader.getParent();
      • java实现。实现位置sun.misc.Launcher内静态内部类static class ExtClassLoader extends URLClassLoader
      • parent父类加载器Bootstrap ClassLoader
      • java.ext.dirs系统属性所指定的目录中加载类库
      • 从JDK的安装目录jre/lib/ext子目录(扩展目录)下加载类库,如果用户创建的jar放在此目录下,也会自动由ExtClassLoader加载
    • SystemClassLoader系统类加载器或称为应用程序类加载器AppClassLoaderClassLoader systemClassLoader=ClassLoader.getSystemClassLoader();
      • java实现。实现位置sun.misc.Launcher内静态内部类static class AppClassLoader extends URLClassLoader
      • parent父类加载器ExtClassLoader 扩展类加载器
      • 负责加载环境变量classpath系统属性java.class.path指定路径下的类库
      • 程序中的默认类加载器,一般java应用的类都是由其来完成加载
      • 通过CLassLoader.getSystemClassLoader()可以获取到该类加载器
    • User Defined Class Loader 用户自定义类加载器
      • java日常应用程序开发中,类加载几乎是由Bootstrap ClassLoader、ExtClassLoader、AppClassLoader3种类加载器相互配合执行的,在必要时,还可以自主实现类加载器,来定制类的加载方式
      • 自定义类加载器功能:
        • 隔离加载类
          • 引入多个框架或中间件时,防止同名同路径的类冲突,通过自定义类加载器仲裁,防止冲突
        • 修改类加载的方式
          • 需要时加载、动态加载
        • 扩展加载源
          • 扩展class字节码文件的来源方式。本地文件系统、网络、数据库等
        • 防止源码泄漏
          • 通过自定义类加载器加载加密过的class字节码。分发加密class字节码文件,从而防止反编译。
      • 自定义类加载器实现:
        • 开发人员可以通过继承抽象类java.lang.Classloader类的方式实现自己的类加载器
        • JDK1.2之前,在自定义类加载器时,总会去继承ClassLoader类并重写loadClass方法,来实现自定义的类加载器
        • JDK1.2之后,不再建议用户去重写loadClass方法,建议把自定义的类加载逻辑写在findClass方法中
        • 在编写自定义类加载器时,如果没有太过于复杂的需求,可以直接继承URLClassLoader类,这样就可以避免自己去编写findClass方法及其获取字节码流的方式,使自定义类加载器编写更简洁

Linking

Verify

  • 确保class文件的字节流中包含的信息符合当前jvm要求,保证被加载类的正确性,保证不会危害jvm自身安全
  • 验证方式
    • 文件格式验证
    • 元数据验证
    • 字节码验证
    • 符号引用验证

Prepare

  • 类变量分配内存并且设置该类变量的默认初始值static int iClass=1; 此时赋默认初始值iClass=0
  • 不包含用final修饰的static静态常量,因为final编译时分配,prepare阶段会显示初始化
  • 这里不会实例变量分配初始化,类变量会分配在Method Area,而实例变量随着对象一起分配到Heap Area

Resolve

  • class文件的常量池内的symbolic reference符号引用转换为直接引用的过程。virtual method table虚方法表创建并开始初始化
  • 事实上,Resolve解析操作往往会伴随JVM在执行完Initialization初始化之后再执行
  • symbolic reference符号引用就是一组符号来描述所应用的目标,symbolic reference符号引用的字面量形式明确定义在jvm规范的class文件格式中。 直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄
  • Resolve解析主要针对类、接口、字段、类方法、接口方法、方法类型等。对应class文件的常量池
    • CONSTANT_Class_info
    • CONSTANT_Fieldref_info
    • CONSTANT_Methodref_info

Initialization

  • Initialization初始化阶段就是执行<clinit>()类构造器方法不是类的构造器的过程当有静态变量赋值静态代码块赋值时才会有clinit
  • <clinit>()类构造器方法不需要定义,是javac编译器自动收集类中的所有类静态变量的赋值动作静态代码块中的语句合并而来
  • <clinit>()类构造器方法中指令按语句在源文件中出现的顺序执行
  • <clinit>()类构造器方法不同于类的构造器类的构造器在jvm视角下是<init>()
  • 若A类具有父类B类,jvm会保证B类的<clinit>()执行完毕后,A类的<clinit>()立即执行
  • jvm必须保证一个类的<clinit>()方法在多线程下被同步加锁。类只会被加载一次,并放置到method areaJDK8为metadata

双亲委派机制

JVM对class文件采用的是按需加载的方式,当需要使用该类时才会将它的class文件加载到内存,生成Class对象
JVM采用双亲委派机制,即把加载类的请求交给parent父类加载器处理,是一种任务委派模式 image

image

工作原理

  • 如果一个类加载器A收到了类加载的请求,并不会立即去执行加载,而是把这个请求委托给parent父类加载器B加载
  • 如果该parent父类加载器B还存在parent父类加载器C,则类加载器C继续向上委托,直到请求到达顶层的BootstrapClassloader
  • 如果parent父类加载器X可以完成类加载任务,则成功加载返回。如果parent父类加载器X不能完成加载任务,子加载器才会尝试自己去加载

优势:

  • 避免重复加载类
  • 保护程序安全,防止核心API被随意篡改:
    • 自定义java.lang.String.main():Error: Main method not found in class java.lang.String
    • 自定义类java.lang.MyBougainvillea:Exception in thread "main" java.lang.SecurityException: Prohibited package name: java.lang
  • 类隔离说明: A1,A2类 由 ExtClassLoader 加载;B1,B2类 由 AppClassLoader 加载。
    • B1 可以访问 A1 A2 B2
    • B2 可以访问 A1 A2 B1
    • A1 可以访问 A2
    • A2 可以访问 A1

沙箱安全机制

  • 自定义java.lang.String类,加载自定义String类的时候根据双亲委派原则会率先使用BootstrapClassLoader加载
  • 此时加载的是jdk自带的rt.jar包中java\lang\String.class 报错信息说没有main方法是因为rt.jar下的String类没有main方法
  • 这样保证对java核心源代码的保护这就是沙箱安全机制

类的主动使用和被动使用

判断JVM中两个Class对象是否为同一个类

  • 类的完整类名必须一致,包括包名
  • 加载这个类的ClassLoaderClassLoader实例对象必须相同 在JVM中即使这两个类对象Class对象来源同一个class文件,被同一个jvm所加载,但只要加载他们的ClassLoader实例对象不同,那么这两个类对象Class对象也是不相等==

JVM必须知道一个类型是由BootstrapClassLoader加载还是由用户类加载器加载

如果一个类型是由用户类加载器加载,那么JVM会将这个用户类加载器的一个reference引用作为类型信息的一部分保存在Method Area

Dynamic Linking动态链接解析一个类型另一个类型reference引用的时候,JVM需要保证这两个类型类加载器是相同的

主动使用

  • 类会执行Initialization 阶段
    • 创建类的实例
    • 访问某一个类或接口的静态变量或者对静态变量赋值
    • 调用类的静态方法
    • 反射Class.forName("org.bougainvillea.jvm.Demo")
    • 初始化一个类的子类加载一个类的时候其父类会先初始化
    • JVM启动时被标明为启动类的类
    • JDK7开始提供的动态语言支持
      • java.lang.invoke.MethodHandle实例的解析结果
      • REF_getStatic、REF_putStatic、REF_invokeStatic句柄对应的类没有初始化则初始化

被动使用

  • 类加载阶段不会执行 Initialization 阶段。比如:静态内部类。
  • 除了主动使用的七种情况,其他使用java类的方式都被看作是对类的被动使用,都不会导致类的初始化