一 内存结构

image.png

二 类加载过程

类加载系统负责从文件系统,网络等中加载字节码文件,字节码文件是有特定的头部标识的(CA FE BA BE)

image.png

2.1 加载(loading)

1)通过一个类的全限定名来获取类的字节类。
2)然后将这个字节流静态存储结构转化为方法区的运行时数据结构。
3)在内存中生成一个java.lang.Class类的对象,作为方法区中这个类的各种数据结构的访问入口。

2.2 链接(linking)

2.2.1 验证过程(verification)

1)这个阶段的目的是在于确保class字节码文件字节流中包含的信息符合当前jvm规范,保证被加载的类的准确性,不会危害到jvm本身。
2)验证方式有四种:文件格式验证,元数据验证,字节码验证,符号验证。

2.2.2 准备阶段(preparation)

1)这个阶段是已经开始在内存中为类的结构分配位置了,首先给类变量分配内存,并且给类变量默认初始化,设为零值。
2)这个默认初始化的静态类变量不包括被final修饰的静态变量,因为final在编译的时候就已经被分配好了,这时候在这个阶段会进行显式的初始化值。
3)这个阶段也不会为实例变量进行初始化,因为类变量会被分配到方法区,而实例变量则会和对象一起被分配到Java堆中。

2.2.3 解析阶段(revolution)

1)将常量池中的符号引用转化为直接引用的过程。
2)事实上,解析操作通常是伴随着jvm的类加载过程的初始化阶段结束之后才进行解析的。

2.3 初始化(initialization)

1)初始化阶段就是执行类构造器方法()方法的过程。
2)()方法不需要被手动定义,它是javac编译器收集类中所有的类变量赋值动作、静态代码块中的语句合并而成的方法。
3)()方法的代码执行顺序是按照源文件代码语句出现的顺序来执行。
4)()方法不同于类中的构造器,类构造器则更多是相当于jvm中的()方法。
5)如果被加载的类有父类,则会保证在子类的()方法执行前,父类的()方法已经被执行完毕了,就是父类的永远执行在先。
6)虚拟机会保证在多线程的情况下,被加载类的方法是同步加锁的。

三 类加载器

3.1 引导类加载器

1)负责加载Java核心代码的类库,比如 /jre/lib/rt.jar/resource.jar等,这些包下的类都是被引导类加载器所加载的。
2)由C/C++来实现的,镶嵌在Java虚拟机中,所以并不继承java中的java.lang.ClassLoader类,没有父类加载器。
3)额外负责加载扩展类加载器和系统类加载器类,并指定为它们的父类加载器。
4)处于安全考虑,引导类加载器只会加载开头为java,javax,sun开头的类。

3.2 扩展类加载器

  1. 负责加载jre包下的扩展类,lib/ext或者由java.ext.dirs系统属性指定的目录中的JAR包。
  2. Java实现:sun.misc.Launcher$ExtClassLoader

    3.3 系统类加载器

  3. 负责加载开发者编写的类和第三方的jar包。

  4. Java实现:sun.misc.Launcher$AppClassLoader

    3.4 自定义类加载器

    1)可以自定义开发自己的类加载器,来加载特定的类。
    2)原因:在Java的日常开发中,类的加载几乎都是由那三种类加载器相互配合完成,必要时,可以自定义一个类加载器,来定制类的加载方式。
    3)自定义类加载器有如作用:①隔离加载类,②修改类加载的方式,③扩展加载源,④防止源码泄露
    4)开发步骤

  5. 直接继承 java.lang.ClassLoader,实现自己的类加载器。

  6. 建议把加载类的逻辑代码写到 findClass()方法中。
  7. 如果没有特定的需求,还可以直接继承URLClassLoader类,可以省去findClass()方法的重写和获取字节流的方式。

    四 ClassLoader类

    1)ClassLoader类是一个抽象类,其后的所有类加载器都可以继承自ClassLoader(不包括启动类加载器)
    2)获取到 ClassLoader 的途径
  • 获取当前类的ClassLoader

    1. clazz.getClassLoader();
  • 获取当前线程上下文的ClassLoader

    1. Thread.currentThread().getContextClassLoader();
  • 获取系统的ClassLoader

    1. ClassLoader.getSystemClassLoader();
  • 获取调用者的ClassLoader

    1. DriverManager.getCallerClassLoader();

    五 双亲委派机制

    5.1 概述

    1)Java虚拟机class文件采用的是按需加载的方式,即是在需要使用到该类的时候,才会将该类加载到内存中生成java.lang.Class类的对象。
    2)而且加载类的时候,Java虚拟机采用的是一种双亲委派机制模式,即是把需求上交给父类加载器,它是一种任务委派机制。

    5.2 工作原理

    1)如果一个类加载器接收到加载类的任务,它不会立即去加载此类,而是将此任务上递交给它的父类加载器,同时它的父类加载器也是如此。
    2)它的父类也如此递归的将要加载的类上交给自己的父类加载器,直到到达了引导类加载器那里。
    3)引导类加载器会尝试去加载此类,如果不能加载,则会把任务返回给上交任务给它的类加载器,直到找到可以加载这个类的类加载器。
    4)可以从上面工作原理知道,双亲委派机制就是首先从最上面的类加载器来加载类,如果父类加载器可以加载,则就成功结束了。
    5)这样子的优势就是可以防止类被重复加载,只有一个类加载器可以加载当前类。而且保护了程序的安全,防止程序的核心api被随意篡改。

    1. // 沙箱安全机制
    2. packge java.lang
    3. public class String{
    4. public static void main(String[] args){
    5. System.Out.print("自定义的String类,包名和Java源码的String类包名一样");
    6. // java.lang.SecurityException: Prohibited package name: java.lang
    7. }
    8. }
    9. /**
    10. * 自定义的String类和jdk下的String类名一样,包名一样,那么类加载器该如何工作呢?
    11. * 这时候,系统类加载器会首先收到任务,然后上交直到引导类加载器那里,
    12. * 引导类加载器一看,这个String类包名是和jdk一样,然后就直接去jdk下加载String类了。
    13. * 但是自定义的String中有一个main方法,程序运行的时候,类加载JDK下的String类找不到main方法,
    14. * 程序就报错了,找不到main方法
    15. */

    六 其他

    6.1 判断两个Class实例是否属于同一个类

    1)类的完整路径必须是一致的,包括包名。
    2)加载这个类的ClassLoader(ClassLoader实例对象)必须相同。
    3)也就是说,在JVM中,即使这两个Class实例来源同一个Class文件,被同一个虚拟机所加载,但是只要加载它们的类加载器对象不同,那么这两个对象也是不等的。

    6.2 类主动使用和被动使用

    1)主动使用,分为7种情况

  1. 创建类的实例
  2. 访问类或者接口的静态变量,或者对静态变量赋值
  3. 调用类的静态方法
  4. 反射
  5. 初始化一个类的子类
  6. JVM启动时被标明为启动类的类
  7. JDK7开始提供的动态语言支持:
    1. java.lang.invoke.MethodHandler实例的解析结果
    2. REF_getStatic、REF_putStatic、REF_invokeStatic句柄对应的类没有初始化,则初始化

2)除了上面七种主动使用情况,其他使用Java类的方式都可以看作时对类的被动使用,都不会导致类的初始化。