JVM系列1--通过几个问题了解JAVA类的加载机制

一、为什么需要类加载?类加载做些什么?

jvm执行某段代码的时候,需要把类相关信息加载到jvm的内存中去。
所谓类加载就是jvm把编译后的class文件中的类信息加载到JVM内存,并且生成对应的class对象的过程

二、java类什么时候被加载?

jvm并不是一开始就把所有的类都一次性加载到内存中去,只有在需要的时候才会加载,并且在jvm生命周期中一个类只会加载一次,类被加载过了,就不会再重复加载了。所以什么时候才需要加载java类呢?

  • 需要加载类的时机有以下几种情况:
  1. 实例化类的时候,加载该类
  2. 实例化子类的时候,他未被初始化的父类被加载
  3. 调用类的静态函数
  4. 调用类的静态变量或赋值
  5. 反射使用class.forname()时,ClassLoader.loadClass()时
    Class.forName()和ClassLoader.loadClass()区别:
    Class.forName():将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static块;
    ClassLoader.loadClass():只干一件事情,就是将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static块
  6. java虚拟机启动的时候会加载启动类
  7. 类被加载的时候,他的静态代码块、静态方法及静态变量会被加载,在他进行到初始化这个步骤时,如果这个类的静态代码块、静态方法或静态变量引用到了另一个类,则这个被引用的类也会被加载。

三、类从哪里加载?

– 从本地系统中直接加载
– 通过网络下载.class文件
– 从zip,jar等归档文件中加载.class文件
– 从专有数据库中提取.class文件
– 将Java源文件动态编译为.class文件(动态代理什么的)

四、类加载到哪里去?

编译后的.class文件的二进制数据加载到jvm的方法区内,生成的Class对象放到jvm的堆,
Class对象封装了类的数据结构,并且向程序员提供了访问方法区内的数据结构的接口

五、类加载的时候做了什么?类加载的流程?

类加载的过程包括了加载、验证、准备、解析、初始化五个阶段

他们的顺序是加载、验证、准备、解析、初始化 或者 加载、验证、准备、初始化、解析
其中加载、验证、准备的顺序是固定的,解析和初始化的顺序是不一定的,这是为了支持java的运行时绑定

需要注意的是,这里的顺序是指按某种顺序开始进行这一阶段的任务,而不是串行的,不是开始并完成一项才进行下一项的,这些阶段都是交叉混合的进行。

  • 加载
    这里的加载是指把class字节码文件用类加载器从各个来源装载入内存中
    1、通过一个类的全限定名来获取其定义的二进制字节流。
    2、将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
    3、在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口。
  • 验证
    确保被加载的类的正确性,文件格式验证、语法语义验证等
  • 准备
    类的静态变量(static修饰的)在方法区中初始化零值(如0、0L、null、false等)
    类的静态常量(static final修饰的)在此时赋予代码中设定的值
    常量(final修饰)系统不会为其赋予默认零值
  • 解析
    虚拟机将常量池内的符号引用替换为直接引用的过程
    符号引用就是一组符号来描述目标,可以是任何字面量。
    直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。
  • 初始化
    为类的静态变量赋予正确的初始值
    因为Java中对类变量进行初始值设定有两种方式:
    ①声明类变量是指定初始值
    ②使用静态代码块为类变量指定初始值
    所以为类的静态变量赋予正确的初始值就是执行这两项,他们之间的优先级是一样的,如果同时包含多个静态变量和静态代码块,则按照自上而下的顺序依次执行。
    如果开始初始化一个类的时候,发现他的父类还没初始化,那就优先初始化其父类。(这时候判断父类有没进行了加载、验证、准备、解析等,没有的话就去先做这些然后再对父类初始化)
    上面提到,静态代码块和静态变量引用的类也是一样,这个时候被引用的类没有初始化就去做初始化类的操作(也需要想判断有没有加载、验证、准备、解析等)。

六、类用什么加载?

用类加载器加载,类加载器包含以下几种:

  • 启动类加载器:Bootstrap ClassLoader,负责加载存放在JDK\jre\lib(JDK代表JDK的安装目录,下同)下,或被-Xbootclasspath参数指定的路径中的,并且能被虚拟机识别的类库(如rt.jar,所有的java.*开头的类均被Bootstrap ClassLoader加载)。启动类加载器是无法被Java程序直接引用的。

  • 扩展类加载器:Extension ClassLoader,该加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载DK\jre\lib\ext目录中,或者由java.ext.dirs系统变量指定的路径中的所有类库(如javax.*开头的类),开发者可以直接使用扩展类加载器。

  • 应用程序类加载器:Application ClassLoader,该类加载器由sun.misc.Launcher$AppClassLoader来实现,它负责加载用户类路径(ClassPath)所指定的类,开发者可以直接使用该类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

  • 自定义的类加载器

应用程序都是由前三种类加载器互相配合进行加载的,如果有必要,我们还可以加入自定义的类加载器。因为JVM自带的ClassLoader只是懂得从本地文件系统加载标准的java class文件,因此如果编写了自己的ClassLoader,便可以做到如下几点:
1)在执行非置信代码之前,自动验证数字签名。
2)动态地创建符合用户特定需要的定制化构建类。
3)从特定的场所取得java class,例如数据库中和网络中。

七、类加载器的加载机制?

• 全盘负责,当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入

• 父类委托,先让父类加载器试图加载该类,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类

• 缓存机制,缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区寻找该Class,只有缓存区不存在,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓存区。这就是为什么修改了Class后,必须重启JVM,程序的修改才会生效

双亲委派模型
双亲委派模型的工作流程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。
双亲委派的例子:
1、当AppClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。
2、当ExtClassLoader加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader去完成。
3、如果BootStrapClassLoader加载失败(例如在$JAVA_HOME/jre/lib里未查找到该class),会使用ExtClassLoader来尝试加载;
4、若ExtClassLoader也加载失败,则会使用AppClassLoader来加载,如果AppClassLoader也加载失败,则会报出异常ClassNotFoundException。

双亲委派模型意义:

  • 系统类防止内存中出现多份同样的字节码
  • 保证Java程序安全稳定运行,不然可能会出现自己定义一个Object对象的情况出现

补充1:类的生命周期是什么?

类的生命周期是加载、验证、准备、解析、初始化、使用、卸载

补充2:类对象实例化的初始化顺序

实例化一个类的时候,初始化顺序是:
父类静态代码块、父类静态变量(优先级并列) --->
子类静态代码块、子类静态变量 (优先级并列)--->
父类代码块 --->
父类构造函数 --->
子类代码块 --->
子类构造函数

其中父类静态代码块、父类静态变量>子类静态代码块、子类静态变量这个是类加载的时候做的事,所以只在jvm生命周期内加载类的时候做一次,后面实例化这个类的对象就执行代码块和构造函数的代码就好了

你可能感兴趣的:(JVM系列1--通过几个问题了解JAVA类的加载机制)