JVM原理速记复习Java虚拟机总结思惟导图面试必备

良心制做,右键另存为保存

JVM
喜欢能够点个赞哦java

Java虚拟机

1、运行时数据区域

线程私有

  • 程序计数器程序员

    • 记录正在执行的虚拟机字节码指令的地址(若是正在执行的是Native方法则为空),是惟一一个没有规定OOM(OutOfMemoryError)的区域。
  • Java虚拟机栈算法

    • 每一个Java方法在执行的同时会建立一个栈桢用于存储局部变量表、操做数栈、动态连接、方法出口等信息。从方法调用直到执行完成的过程,对应着一个栈桢在Java虚拟机栈中入栈和出栈的过程。(局部变量包含基本数据类型、对象引用reference和returnAddress类型)
  • 本地方法栈数组

    • 本地方法栈与Java虚拟机栈相似,它们之间的区别只不过是本地方法栈为Native方法服务。

线程公有

  • Java堆(GC区)(Java Head)安全

    • 几乎全部的对象实例都在这里分配内存,是垃圾收集器管理的主要区域。分为新生代和老年代。对于新生代又分为Eden空间、From Survivor空间、To Survivor空间。
  • JDK1.7 方法区(永久代)数据结构

    • 用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
      对这块区域进行垃圾回收的主要目的是对常量池的回收和对类的卸载,可是通常难以实现。
      HotSpot虚拟机把它当作永久代来进行垃圾回收。但很难肯定永久代的大小,由于它受到不少因素的影响,而且每次Full GC以后永久代的大小都会改变,因此常常抛出OOM异常。
      从JDK1.8开始,移除永久代,并把方法区移至元空间。
    • 运行时常量池多线程

      • 是方法区的一部分
        Class文件中的常量池(编译器生成的字面量和符号引用)会在类加载后被放入这个区域。
        容许动态生成,例如String类的intern()
  • JDK1.8 元空间并发

    • 本来存在方法区(永久代)的数据,一部分移到了Java堆里面,一部分移到了本地内存里面(即元空间)。元空间存储类的元信息,静态变量和常量池等放入堆中。
  • 直接内存函数

    • 在NIO中,会使用Native函数库直接分配堆外内存。

2、HotSpot虚拟机

对象的建立

  • 当虚拟机遇到一条new指令时
  1. 检查参数可否在常量池中找到符号引用,并检查这个符号引用表明的类是否已经被加载、解析和初始过,没有的话先执行相应的类加载过程。
  2. 在类加载检查经过以后,接下来虚拟机将为新生对象分配内存。
  3. 内存分配完成以后,虚拟机须要将分配到的内存空间都初始化为零值(不包括对象头)。
  4. 对对象头进行必要的设置。
  5. 执行构造方法按照程序员的意愿进行初始化。

对象的内存布局

    1. 对象头
      1. 第一部分用于存储对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标识、线程持有的锁、偏向线程ID、偏向实现戳等。
      1. 第二部分是类型指针,即对象指向它的类元数据的指针(若是使用直接对象指针访问),虚拟机经过这个指针来肯定这个对象是哪一个类的实例。
      1. 若是对象是一个Java数组的话,还须要第三部分记录数据长度的数据。
    1. 实例数据
    • 是对象真正存储的有效信息,也就是在代码中定义的各类类型的字段内容。
    1. 对齐填充
    • 不是必然存在的,仅仅起着占位符的做用。
      HotSpot须要对象的大小必须是8字节的整数倍。

对象的访问定位

  • 句柄访问布局

    • 在Java堆中划分出一块内存做为句柄池。
      Java栈上的对象引用reference中存储的就是对象的句柄地址,而句柄中包含了到对象实例数据的指针和到对象类型数据的指针。
      对象实例数据在Java堆中,对象类型数据在方法区(永久代)中。
      优势:在对象被移动时只会改变句柄中的实例数据指针,而对象引用自己不须要修改。
  • 直接指针访问(HotSpot使用)

    • Java栈上的对象引用reference中存储的就是对象的直接地址。
      在堆中的对象实例数据就须要包含到对象类型数据的指针。
      优势:节省了一次指针定位的时间开销,速度更快。

3、垃圾收集

概述

  • 垃圾收集主要是针对Java堆和方法区。
    程序计数器、Java虚拟机栈个本地方法栈三个区域属于线程私有,线程或方法结束以后就会消失,所以不须要对这三个区域进行垃圾回收。

判断对象是否能够被回收

  • 第一次标记(缓刑)

    • 引用计数算法

      • 给对象添加一个引用计数器,当对象增长一个引用时引用计数值++,引用失效时引用计数值--,引用计数值为0时对象能够被回收。

可是它难以解决对象之间的相互循环引用的状况,此时这个两个对象引用计数值为1,可是永远没法用到这两个对象。

- 可达性分析算法(Java使用)

    - 以一系列GC Roots的对象做为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连是,则证实此对象不可用,能够被回收。

GC Roots对象包括

  1. 虚拟机栈(栈桢中的本地变量表)中引用的对象。
  2. 方法区中共类静态属性引用的对象。
  3. 方法区中常量引用的对象。
  4. 本地方法栈中JNI(即通常说的Native方法)引用的对象。
  • 第二次标记

    • 当对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过。
      若是对象在finalize方法中从新与引用链上的任何一个对象创建关联则将不会被回收。
    • finalize()

      • 任何一个对象的finalize()方法都只会被系统调用一次。
        它的出现是一个妥协,运行代价高昂,不肯定性大,没法保证各个对象的调用顺序。
        finalize()能作的全部工做使用try-finally或者其余方式均可以作的更好,彻底能够忘记在这个函数的存在。

方法区的回收

  • 在方法区进行垃圾回收的性价比通常比较低。
    主要回收两部分,废弃常量和无用的类。

知足无用的类三个判断条件才仅仅表明能够进行回收,不是必然关系,可使用-Xnoclassgc参数控制。

  1. 该类的全部实例都已经被回收,也就是Java堆中不存在该类的任何实例。
  2. 加载该类的ClassLoader已经被回收。
  3. 该类对应的java.lang.Class对象没有在任何地方被引用,没法在任何地方经过反射访问到该类的方法。

引用类型

    1. 强引用
    • 使用new一个新对象的方式来建立强引用。
      只要强引用还存在,被引用的对象则永远不会被回收。
    1. 软引用
    • 使用SoftReference类来实现软引用。
      用来描述一些还有用可是并不是必须的对象,被引用的对象在将要发生内存溢出异常以前会被回收。
    1. 弱引用
    • 使用WeakReference类来实现弱引用。
      强度比软引用更弱一些,被引用的对象在下一次垃圾收集时会被回收。
    1. 虚引用
    • 使用PhantomReference类来实现虚引用。
      最弱的引用关系,不会对被引用的对象生存时间构成影响,也没法经过虚引用来取得一个对象实例。
      惟一目的就是能在这个对象被收集器回收时收到一个系统通知。

垃圾收集算法

    1. 标记 - 清除
    • 首先标记出全部须要回收的对象,在标记完成后统一回收被标记的对象并取消标记。

不足:

  1. 效率问题,标记和清除两个过程的效率都不高。
  2. 空间问题,标记清除以后会产生大量不连续的内存碎片,没有连续内存容纳较大对象而不得不提早触发另外一次垃圾收集。
    1. 标记 - 整理
    • 和标记 - 清除算法同样,但标记以后让全部存活对象都向一段移动,而后直接清理掉端边界之外的内存。
      解决了标记 - 清除算法的空间问题,但须要移动大量对象,仍是存在效率问题。
    1. 复制
    • 将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另一块上面,而后再把已使用多的内存空间一次清理掉。
      代价是将内存缩小为原来的通常,过高了。

如今商业虚拟机都采用这种算法用于新生代。
由于新生代中的对象98%都是朝生暮死,因此将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中一块Survivor空间。
当回收时,若是另一块Survivor空间没有足够的空间存放存活下来的对象时,这些对象将直接经过分配担保机制进入老年代。

    1. 分代收集
    • 通常把Java堆分为新生代和老年代。
      在新生代中使用复制算法,在老年代中使用标记 -清除 或者 标记 - 整理 算法来进行回收。

HotSpot的算法实现

  • 枚举根节点(GC Roots)

    • 目前主流Java虚拟机使用的都是准确式GC。
      GC停顿的时候,虚拟机能够经过OopMap数据结构(映射表)知道,在对象内的什么偏移量上是什么类型的数据,并且特定的位置记录着栈和寄存器中哪些位置是引用。所以能够快速且准确的完成GC Roots枚举。
  • 安全点

    • 为了节省GC的空间成本,并不会为每条指令都生成OopMap,只是在“特定的位置”记录OopMap,这些位置称为安全点。

程序执行只有到达安全点时才能暂停,到达安全点有两种方案。

  1. 抢断式中断(几乎不使用)。GC时,先把全部线程中断,若是有线程不在安全点,就恢复该线程,让他跑到安全点。
  2. 主动式中断(主要使用)。GC时,设置一个标志,各个线程执行到安全点时轮询这个标志,发现标志为直则挂起线程。

可是当线程sleep或blocked时没法响应JVM的中断请求走到安全点中断挂起,因此引出安全区域。

  • 安全区域

    • 安全区域是指在一段代码片断之中,引用关系不会发生变化,是扩展的安全点。

线程进入安全区域时表示本身进入了安全区域,这个发生GC时,JVM就不须要管这个线程。
线程离开安全区域时,检查系统是否完成GC过程,没有就等待能够离开安全区域的信号为止,否者继续执行。

垃圾收集器

  • 新生代

      1. serial收集器
      • 它是单线程收集器,只会使用一个线程进行垃圾收集工做,更重要的是它在进行垃圾收集时,必须暂停其余全部的工做线程。

优势:对比其余单线程收集器简单高效,对于单个CPU环境来讲,没有线程交互的开销,所以拥有最高的单线程收集效率。

它是Client场景下默认新生代收集器,由于在该场景下内存通常来讲不会很大。

- 2. parnew收集器

    - 它是Serial收集器的多线程版本,公用了至关多的代码。

在单CPU环境中绝对不会有比Serial收集器更好的效果,甚至在2个CPU环境中也不能百分之百超越。

它是Server场景下默认的新生代收集器,主要由于除了Serial收集器,只用它能与CMS收集器配合使用。

- 3. parallel scavenge收集器

    - “吞吐优先”收集器,与ParNew收集器差很少。

可是其余收集器的目标是尽量缩短垃圾收集时用户线程停顿的时间,而它的目标是达到一个可控制的吞吐量。这里的吞吐量指CPU用于运行用户程序的时间占总时间的比值。

  • 老年代

      1. serial old收集器
      • 是Serial收集器老年代版本。

也是给Client场景下的虚拟机使用的。

- 5. parallel old收集器

    - 是Parallel Scavenge收集器的老年代版本。

在注重吞吐量已经CPU资源敏感的场合,均可以优先考虑Parallel Scavenge和Parallel Old收集器。

- 6. cms收集器

    - Concurrent Mark Sweep收集器是一种以获取最短回收停顿时间为目标的收集器。
    - 运做过程

        - 1. 初始标记(最短)。仍须要暂停用户线程。只是标记一下GC Roots能直接关联到的对象,速度很快
  1. 并发标记(耗时最长)。进行GC Roots Tracing(根搜索算法)的过程。
  2. 从新标记。修正并发标记期间因用户程序继续运做而致使标记产生变更的那一部分对象的标记记录。比初始标记长但远小于并发标记时间。
  3. 并发清除

1 和4 两个步骤并无带上并发两个字,即这两个步骤仍要暂停用户线程。

- 优缺点

        - 并发收集、低停顿。
  1. CMS收集器对CPU资源很是敏感。虽然不会致使用户线程停顿,可是占用CPU资源会使应用程序变慢。
  2. 没法处理浮动垃圾。在并发清除阶段新垃圾还会不断的产生,因此GC时要控制“-XX:CMSinitiatingOccupancyFraction参数”预留足够的内存空间给这些垃圾,当预留内存没法知足程序须要时就会出现”Concurrent Mode Failure“失败,临时启动Serial Old收集。
  3. 因为使用标记 - 清除算法,收集以后会产生大量空间碎片。
    1. g1收集器
    • Garbage First是一款面向服务端应用的垃圾收集器
    • 运做过程

        1. 初始标记
  1. 并发标记
  2. 最终标记
  3. 删选标记

5、类加载机制

概述

  • 虚拟机把描述类的数据从Class问价加载到内存,并对数据进行校验、转换解析和初始化,最终造成能够被虚拟机直接使用的Java类型。
    Java应用程序的高度灵活性就是依赖运行期动态加载和动态链接实现的。

类的生命周期

  • 加载 -> 链接(验证 -> 准备 -> 解析) -> 初始化 -> 使用 - >卸载

类初始化时机

  • 主动引用

    • 虚拟机规范中没有强制约束什么时候进行加载,可是规定了有且只有五种状况必须对类进行初始化(加载、验证、准备都会随之发生)
  1. 遇到new、getstatic、putstatic、invokestatic这四条字节码指令时没有初始化。
  2. 反射调用时没有初始化。
  3. 发现其父类没有初始化则先触发其父类的初始化。
  4. 包含psvm(mian()方法)的那个类。
  5. 动态语言支持时,REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄。
  • 被动引用

    • 除上面五种状况以外,全部引用类的方式都不会触发初始化,称为被动引用。
  1. 经过子类引用父类的静态字段,不会致使子类的初始化。
  2. 经过数组定义来引用类,不会触发此类的初始化。该过程会对数组类进行初始化,数组类是一个由虚拟机自动生成的、直接继承Object的子类,其中包含数组的属性和方法,用户只能使用public的length和clone()。
  3. 常量在编译阶段会存入调用类的常量池中,本质上并无直接引用到定义常量的类,所以不会触发定义常量的类的初始化。

类加载过程

    1. 加载
      1. 经过类的全限定名来获取定义此类的二进制字节流。
  1. 将这个字节流所表明的静态存储结构转化为方法区的运行时数据结构。
  2. 在内存中生成一个表明这个类的java.lang.Class对象(HotSpot将其存放在方法区中),做为方法区这个类的各类数据的访问入口。
    1. 验证
    • 为了确保Class文件的字节类中包含的信息符合当前虚拟机的要求,而且不会危害虚拟机自身的安全。能够经过-Xverify:none关闭大部分类验证。
  1. 文件格式验证。确保输入字节流能正确的解析并存储于方法区,后面的3个验证所有基于方法区的存储结构进行,不会再操做字节流。
  2. 元数据验证。对字节码描述信息进行语义分析,确保其符合Java语法规范。(Java语法验证)
  3. 字节码验证。最复杂,经过数据流和控制流分析,肯定程序语义时合法的、符合逻辑的。能够经过参数关闭。(验证指令跳转范围,类型转换有效等)
  4. 符号引用验证。将符号引用转化为直接引用,发生在第三个阶段——解析阶段中发生。
    1. 准备
    • 类变量是被static修饰的变量,准备阶段为类变量分配内存并设置零值(final直接设置初始值),使用的是方法区的内存。
    1. 解析
    • 将常量池内的符号引用替换为直接引用的过程。
      其中解析过程在某些状况下能够在初始化阶段以后再开始,这是为了支持Java的动态绑定。
      解析动做主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄、和调用点限定符。
    1. 初始化
    • 初始化阶段才真正执行类中定义的Java程序代码,是执行类构造器 ()方法的过程。
      在准备阶段,类变量已经给过零值,而在初始化阶段,根据程序员经过程序制定的主观计划去初始化类变量和其余资源。

      • ()

        • 类构造器方法。是由编译器自动收集类中的全部类变量的赋值动做和静态语句块中的的语句合并产生的。
  1. 不须要显式调用父类构造器,JVM会保证在子类clinit执行以前,父类的clinit已经执行完成。
  2. 接口中不能使用静态语句块但仍能够有类变量的赋值操做。当没有使用父接口中定义的变量时子接口的clinit不须要先执行父接口的clinit方法。接口的实现类也不会执行接口的clinit方法。
  3. 虚拟机会保证clinit在多线程环境中被正确的加锁、同步。其余线性唤醒以后不会再进入clinit方法,同一个类加载器下,一个类型只会初始化一次。

    - <init>()
    
         - 对象构造器方法。Java对象被建立时才会进行实例化操做,对非静态变量解析初始化。
  4. 会显式的调用父类的init方法,对象实例化过程当中对实例域的初始化操做所有在init方法中进行。

类(加载) 器

  • 类与类加载器

    • 类加载器实现类的加载动做。
      类加载器和这个类自己一同确立这个类的惟一性,每一个类加载器都有独立的类命名空间。在同一个类加载器加载的状况下才会有两个类相等。
      相等包括类的Class对象的equals()方法、isAssignableFrom()方法、isInstance()、instanceof关键字。
  • 类加载器分类

    • 启动类加载器

      • 由C++语言实现,是虚拟机的一部分。负责将JAVA_HOME/lib目录中,或者被-Xbootclasspath参数指定的路径,可是文件名要能被虚拟机识别,名字不符合没法被启动类加载器加载。启动类加载器没法被Java程序直接引用。
    • 扩展类加载器

      • 由Java语言实现,负责加载JAVA_HOME/lib/ext目录,或者被java.ext.dirs系统变量所指定的路径中的全部类库,开发者能够直接使用扩展类加载器。
    • 应用程序类加载器

      • 因为这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,因此通常也称他为系统类加载器。负责加载用户类路径(ClassPath)上所指定的类库,通常状况下这个就是程序中默认的类加载器。
    • 自定义类加载器

      • 由用户本身实现。
  1. 若是不想打破双亲委派模型,那么只须要重写findClass方法便可。
  2. 不然就重写整个loadClass方法。
  • 双亲委派模型

    • 双亲委派模型要求除了顶层的启动类加载器外,其他的类加载器都应该有本身的父类加载器。父子不会以继承的关系类实现,而是都是使用组合关系来服用父加载器的代码。
      在java.lang.ClassLoader的loadClass()方法中实现。
    • 工做过程

      • 一个类加载器首先将类加载请求转发到父类加载器,只有当父类加载器没法完成(它的搜索范围中没有找到所须要的类)时才尝试本身加载
    • 好处

      • Java类随着它的类加载器一块儿具有了一种带有优先级的层次关系,从而使得基础类库获得赞成。

4、内存分配与回收策略

Minor GC 和 Full GC

  • Minor GC

    • 发生在新生代的垃圾收集动做,由于新生代对象存活时间很短,所以Minor GC会频繁执行,执行速度快。
    • 时机

      • Eden不足
  • Full GC

    • 发生在老年区的GC,出现Full GC时每每伴随着Minor GC,比Minor GC慢10倍以上。
    • 时机

        1. 调用System.gc()
        • 只是建议虚拟机执行Full GC,可是虚拟机不必定真正去执行。
          不建议使用这种方式,而是让虚拟机管理内存。
        1. 老年代空间不足
        • 常见场景就是大对象和长期存活对象进入老年代。
          尽可能避免建立过大的对象以及数组,调大新生代大小,让对象尽可能咋新生代中被回收,不进入老年代。
        1. JDK1.7 以前方法区空间不足
        • 当系统中要加载的类、反射的类和常量较多时,永久代可能会被占满,在未配置CMS GC的状况下也会执行Full GC,若是空间仍然不够则会抛出OOM异常。
          可采用增大方法区空间或转为使用CMS GC。
        1. 空间分配担保失败
        • 发生Minor GC时分配担保的两个判断失败
        1. Concurrent Mode Failure
        • CMS GC 并发清理阶段用户线程还在执行,不断有新的浮动垃圾产生,当预留空间不足时报Concurrent Mode Failure错误并触发Full GC。

内存分配策略

    1. 对象优先在Eden分配
    • 大多数状况下,对象在新生代Eden上分配,当Eden空间不够时,发起Minor GC,当另一个Survivor空间不足时则将存活对象经过分配担保机制提早转移到老年代。
    1. 大对象直接进入老年代
    • 配置参数-XX:PretenureSizeThreshold,大于此值得对象直接在老年代分配,避免在Eden和Survivor之间的大量内存复制。
    1. 长期存活对象进入老年代
    • 虚拟机为每一个对象定义了一个Age计数器,对象在Eden出生并通过Minor GC存活转移到另外一个Survivor空间中时Age++,增长到默认16则转移到老年代。
    1. 动态对象年龄绑定
    • 虚拟机并非永远要求对象的年龄必须到达MaxTenuringThreshold才能晋升老年代,若是在Survivor中相同年龄全部对象大小总和大于Survivor空间的一半,则年龄大于或等于该年龄的对象直接进入老年代。
    1. 空间分配担保
    • 在发生Minor GC以前,虚拟机先检查老年代最大可用的连续空间是否大于新生代的全部对象,若是条件成立,那么Minor GC能够认为是安全的。 能够经过HandlePromotionFailure参数设置容许冒险,此时虚拟机将与历代晋升到老年区对象的平均大小比较,仍小于则要进行一次Full GC。 在JDK1.6.24以后HandlePromotionFailure已无做用,即虚拟机默认为true。
相关文章
相关标签/搜索