jvm

类加载机制

Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被直接使用的Java类型。
在Java语言中,类型(class)的加载、连接与初始化过程都是在程序运行期间完成的,通过这种方式提供了更大的灵活性,
增加了更多的可能性。Java里天生支持的动态扩展的语言特性就是依赖运行期动态加载和动态连接这个特点实现的。
例如,如果编写一个面向接口的应用程序,可以等到运行时再指定其实际的实现类。

类加载时机

类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括以下几个阶段:

  • 加载(Loading):查找并加载类的二进制数据,将类的class文件加载到内存;
  • 连接(Linking):将类与类的关系给确定好,将类与类之间符号引用转换成直接引用,并且对字节码进行相关的处理、验证。
    • 验证(Verification):确保被加载类的正确性;
    • 准备(Preparation):为类的静态变量(类变量)分配内存,并将其初始化为默认值,但在到达初始化之前,类变量都没有初始化为真正的初始值;
    • 解析(Resolution):把类中的符号引用转换为直接引用;
  • 初始化(Initialization):为类的静态变量赋予正确的初始值(真实值覆盖默认值)。
  • 使用(Using)
    • 类实例化:为新的对象分配内存,为实例变量赋默认值,为实例变量赋正确的初始值(开发人员设置的初始值)。
  • 卸载(Unloading)

加载、验证、准备、初始化和卸载5个阶段的顺序是确定的,类的加载过程必须按照这种顺序按部就班地开始
(注意,这里写的是开始,而不是进行或完成,因为这些阶段通常都是互相交叉地混合式进行的,通常会在一个阶段执行的过程中调用、激活另一个阶段)。
而解析阶段则不一定,它在某些情况下可以在初始化之后再开始。

对于类加载过程的第一个阶段(加载)什么时候开始,Java虚拟机规范中并没有进行强制约束,由虚拟机的实现自由把握。但是对于初始化阶段
虚拟机规范严格规定了有且只有5种情况必须立即对类进行初始化(而加载、验证、准备自然需要在此之前开始):

  • 遇到new、getstatic、putstatic或invokestatic 这4条字节码指令时,如果类还没进行过初始化,则需要先触发其初始化。
    生成这4条指令的最常见的场景有:使用new关键字创建类的实例,读取或设置类的静态变量(被final修饰、已在编译期把结果放入常量池的静态字段除外),
    调用类的静态方法。
  • 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类还没进行过初始化,则需要先触发其初始化。
  • 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则先要触发其父类的初始化。
  • 当虚拟机启动时,用户需要指定一个要执行的主类(包含main方法的那个类),虚拟机会先初始化这个主类。
  • 当使用JDK1.7的动态语言支持时,如果一个java.lang.invoke.methodHandle实例的解析结果REF_getStatic, REF_putStatic,
    REF_invokeStatic的方法句柄,并且这个方法句柄对应的类没有初始化则初始化。

以上5种情况对应的行为称为对一个类的主动使用。除此之外,所有引用类的方式都不会触发初始化,被称为被动使用

类加载器Classloader

Java虚拟机与程序的生命周期,在如下几种情况下,Java虚拟机将结束生命周期

  • 执行了System.exit()方法;
  • 程序正常执行结束;
  • 程序在执行过程中遇到了异常或错误而异常终止;
  • 由于操作系统出现错误而导致Java虚拟机进程终止。

类的加载连接与初始化过程详解

Java程序对类对使用方式可分为两种

  • 主动使用:

    • 创建类的实例;
    • 访问某个类或接口的静态变量,或者对该静态变量赋值(被final修饰、已在编译期把结果放入常量池的静态字段除外);
    • 调用类的静态方法;
    • 反射(如:Class.forName(“com.geekymv.MyTest”));
    • 初始化一个类的子类;
    • Java虚拟机启动时被标明为启动类的类(包含main方法的类);
    • jdk1.7开始提供的动态语言支持,java.lang.invoke.methodHandle实例的解析结果REF_getStatic, REF_putStatic, REF_invokeStatic句柄对应的类没有初始化则初始化。
  • 被动使用:除了以上7种情况,其他使用Java类的方式都被看作是对类的被动使用,都不会导致类的初始化。

所有的Java虚拟机实现必须在每个类或接口被Java程序首次主动使用才初始化。

类的初始化步骤

  • 假如这个类还没有被加载和连接,那就先进行加载和连接;
  • 假如类存在直接父类,并且这个父类还没有被初始化,那就先初始化直接父类;
  • 假如类中存在初始化语句(静态变量的声明语句,以及静态代码块都被看做类的初始化语句),那就依次执行这些初始化语句。

类的初始化时机
当Java虚拟机初始化一个类时,要求它的所有父类都已经初始化,但是这条规则并不适用于接口。

  • 在初始化一个类时,并不会先初始化它所实现的接口;
  • 在初始化一个接口时,并不会初始化它的父接口。
    因此,一个父接口并不会因为它的子接口或者实现类的初始化而初始化。
    只有当程序首次使用特定接口的静态变量时,才会导致该接口的初始化。

类的加载

类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在内存中创建一个java.lang.Class对象
(Java虚拟机规范中并未说明Class对象位于哪里,HotSpot虚拟机将其放在了方法区中)用来封装类在方法区内的数据结构。

加载.class文件的方式

  • 从本地系统中直接加载;
  • 通过网络下载.class文件;
  • 从zip、jar等归档文件中加载.class文件;
  • 从专有数据库中提取.class文件;
  • 将Java源文件动态编译为.class文件。

类加载器

  • Java虚拟机自带的加载器
    • 根类加载器(Bootstrap),或者叫做启动类加载器,该加载器没有父加载器,加载jre/lib/rt.jar 或者-Xbootclasspath选项指定的jar包;
    • 扩展类加载器(Extension),加载jre/lib/ext/*.jar 或者-Djava.ext.dirs指定目录下的jar包;
    • 系统类加载器(System)或者叫做应用类加载器,加载classpath 或则-Djava.class.path目录下的类和jar包;
  • 用户自定义的类加载器
    • java.lang.ClassLoader 的子类
    • 用户可以定制类的加载方式

// TODO 画图

除了根类加载器之外,其他的类加载器有且只有一个父加载器。
类加载器并不需要等到某个类”首次主动使用”时再加载它。

JVM规范允许类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了.class文件缺失或者存在错误,
类加载器必须在程序主动使用时才报告错误(LinkageError错误)。
如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误。

双亲委托机制(父亲委托机制)
在父亲委托机制中,各个类加载器按照父子关系形成了树形结构(逻辑意义上的树形结构),
除了根类加载器,其余的类加载器有且只有一个父加载器。

通俗的说,就是一个类加载器自己要加载类,但是自己不去加载而是让自己的父类加载器尝试去加载,直到根类加载器,
然后由根类加载器尝试去加载,如果根类加载加载不了,则让子类加载器去加载,直到加载成功或者加载失败。

自底向上检查类是否已经加载,自顶向下尝试加载类。

扩展类加载器ExtClassLoader 和应用类加载器AppClassLoader(或称系统类加载器SystemClassLoader) 都是由启动类加载器BootstrapClassLoader 加载。
BootstrapClassLoader 是Java虚拟机内建的类加载器。

命名空间

每个类加载器都有自己的命名空间,命名空间由该加载器及所有父加载器所加载的类组成。
在同一个命名空间中,不会出现类的完整名字(包括类的包名)相同的两个类。
在不同的命名空间中,有可能会出现类的完整名字(包括类的包名)相同的两个类。

子加载器加载的类可以访问父加载器加载的类,父加载器加载的类不能访问子加载器加载的类。

垃圾收集器

监控工具使用
jconsole
jvisualvm
jmap
jstack


线程安全
偏向锁、自旋锁与轻量级锁

逃逸分析
内存模型

垃圾收集算法

  • 标记清除
  • 复制算法
  • 标记整理

  • 分代收集
    新生代:复制算法
    老年代:标记清除或标记整理

垃圾收集器

CMS
G1

坚持原创技术分享,您的支持将鼓励我继续创作!