类加载子系统

类加载器子系统作用

  • 类加载器子系统负责从文件系统或者网络中加载Class文件,class文件在文件开头有特定的文件标识 (魔数 0xCAFEBABE)
  • ClassLoader只负责class文件的加载,至于它是否可以运行,则由执行引擎决定
  • 加载的类信息存放于方法区(称为DNA元数据模板),方法区中还会存放运行时常量池信息,包括字符串字面量和数字常量

加载阶段

  • 通过一个类的全限定名获取定义此类的二进制字节流
  • 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
  • 在 Java 堆内存中实例化一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据的访问入口

加载class文件的方式

  • 从本地系统中直接加载
  • 通过网络获取,典型场景:Web Applet
  • 从zip压缩包中读取,成为日后jar、war格式的基础
  • 运行时计算生成,使用最多的是:动态代理技术
  • 由其他文件生成,JSP 文件生成对应的 Class 文件
  • 从数据库中读取
  • 从加密文件中获取,典型的防Class文件被反编译的保护措施

链接阶段

验证阶段
  • 确保 Class 文件的字节流中包含信息符合当前虚拟机要求,保证这些信息被当作代码运行后不会危害虚拟机自身的安全
  • 文件格式验证
  • 元数据验证
  • 字节码验证
  • 符号引用验证
准备阶段
  • 为类变量(静态变量)分配内存并且设置该类变量的默认初始值,即零值
  • 如果是 final 修饰的类变量,在编译时就会分配值,准备阶段会显示初始化
  • 不包括实例变量, 实例变量会在对象实例化时随着对象一起分配在 Java 堆中
解析阶段
  • 将常量池内的符号引用转换为直接引用的过程
  • 解析操作往往会伴随着JVM在执行完初始化之后再执行

初始化阶段

  • 初始化阶段就是执行类构造器 () 方法的过程
  • 此方法是 Javac 编译器自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的,按语句在源文件中出现的顺序执行
  • Java 虚拟机会保证子类的 () 方法执行前,父类的 () 方法已经执行完毕,因此在 Java 虚拟机中第一个被执行的 () 方法的类型肯定是 java.lang.Object
  • Java 虚拟机必须保证一个类的 () 方法在多线程的环境中被正确的加锁同步,如果多个线程同时去初始化一个类,只会有一个线程去执行这个类的 () 方法,其他线程都需要阻塞等待,在实际应用中这种阻塞往往是很隐蔽的

类加载器的分类

  • 站在 Java 虚拟机的角度来看,只存在两种类型不同的加载器:启动类加载器和自定义类加载器
  • 站在开发人员的角度来看,分为启动类加载器、拓展类加载器、应用程序类加载器、自定义类加载器
用户自定义加载器的作用
  • 隔离加载类、修改类加载的方式、扩展类的加载源、防止源码泄露
用户自定义类加载器的实现步骤
  • 通过继承抽象类ava.1ang.ClassLoader类的方式,实现自己的类加载器。 jdk1.2 之前,通过重写 loadClass() 方法,之后建议把自定义的类加载逻辑写在 findClass() 方法中
  • 如果没有过于复杂的需求,可以直接继承 URIClassLoader 类,这样就可以避免自己去编写 findClass() 方法及其获取字节码流的方式,使自定义类加载器编写更加简洁

双亲委派机制的工作原理

  • 如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行; 如果父类加载器还存在其父类加载器,则进一步向上委托,请求最终将到达顶层的启动类加载器; 只有当父类加载器反馈自己无法完成这个加载请求时,子加载器才会尝试自己去完成加载。
优势
  • 避免类的重复加载
  • 保护程序安全,防止核心 API 被随意篡改

类的主动使用和被动使用

区别
  • 会不会导致类的初始化
主动使用
  • 使用 new 关键字实例化对象
  • 读取或设置类的静态变量(不包括被 final 修饰,已在编译期把结果放入常量池的静态字段)
  • 调用一个类的静态方法
  • 反射调用
  • 初始化一个类的子类
  • 虚拟机启动时被标明为启动类的类