1. 运行时数据区
    1. 方法区(Method Area)
      1. 运行时常量池
      2. 元空间(Meta Space)JDK 8 +
        1. 字符串常量池
        2. 静态变量
        3. 类型信息
        4. 溢出
          1. -XX: MaxMetaspaceSize
          2. -XX: MetaspaceSize
          3. -XX: MinMetaspaceFreeRatio
      3. 在JDK 7及之前,HotSpot使用永久代来实现方法区时,而在JDK 8及之后,类变量则会随着Class对象一起存放在Java堆中,这时候类变量在方法区就完全是一种种对逻辑概念的表述了。
    2. 虚拟机栈(VM Stack)
    3. 本地方法栈(Native Method Stack)
    4. 堆(Heap)
      1. 溢出(Java.lang.OutOfMemoryError、Java heap space)
      2. 可以通过Java内存(映像)分析工具(hprof文件分析工具)打开内存堆转储快照分析
    5. 程序计数器(Program Counter Register)
    6. HotSpot不区分虚拟机栈和本地方法栈,同时也不支持栈的动态扩展,栈容量由`-Xss`参数设定,HotSpot的栈溢出抛出的异常全都是StackOverFlowError。
      1. 溢出
        1. 使用-Xss参数减少栈内存容量。
        2. 定义了大量的本地变量,增大此`方法帧`中`本地变量表`的长度。
        3. 通过不断建立线程的方式,在HotSpot上也是可以产生内存溢出异常的。
  2. 直接内存(Direct Memory)
    1. 溢出
      1. 使用unsafe直接分配本机内存,模拟内存溢出
      2. 由直接内存导致的内存溢出,一个明显的特征是在Heap Dump文件中不会看见有什么明显的异常情况,如果读者发现内存溢出之后产生的Dump文件很小,而程序中又直接或间接使用了DirectMemory(典型的间接使用就是NIO),那就可以考虑重点检查一下直接内存方面的原因了。
  3. 垃圾收集器
    1. 程序计数器
    2. 虚拟机栈
    3. 本地方法栈
    4. Java堆
      1. 对象已死?
        1. 引用计数法
        2. 可达性分析算法(Reachability Analysis)
        3. 生存与死亡?(两次标记过程)
    5. 方法区
      1. 回收方法区的废弃常量和不再使用的类型(方法区垃圾收集的“性价比"通常也是比较低的)
    6. 对象存活判断算法和垃圾收集算法
      1. 对象消亡
        1. “引用计数式垃圾收集”(Reference Counting GC)(直接垃圾收集)
        2. “追踪式垃圾收集”(Tracing GC)(间接垃圾收集)
      2. 分代收集理论
        1. 弱分代假说(Weak Generational Hypothesis):绝大多数对象都是朝生夕灭的
        2. 强分代假说(Strong Generational Hypothesis):熬过越多次垃圾收集过程的对象就越难以消亡
      3. 收集类型
        1. 部分收集(Partial GC)
          1. 新生代收集(Minor GC/Young GC)
          2. 老年代收集(Major GC/Old GC)
          3. 混合收集(Mixed GC)
        2. 整堆收集(Full GC)
      4. 不同区域安排不同的算法
        1. 标记-清除算法(Mark-Sweep)
        2. 标记-复制算法
          1. 半区复制(Semispace Copying)
          2. Appel式回收
          3. Eden空间(默认占80%)
          4. Survivor From空间(默认占10%)
          5. Survivor To空间(默认占10%)
          6. 分配担保(Handle Promotion)
          7. 超大对象在Minor GC时无法被移步到Survivor时将被一直划分到老年代
          8. 这个算法的高效是建立在大部分对象都“朝生夕灭”的特性上的,如果存活对象过多,把这些对象复制到Survivor并维持这些对象引用的正确性就成为一个沉重的负担,因此导致垃圾收集的暂停时间明显变长。
        3. 标记-整理算法(移动式)
          1. 如果移动存活的对象太多,这将是一笔很大的开销
          2. 如果完全不考虑移动和整理存活对象的话,弥散于堆中的存活对象导致的空间碎片化问题就只能依赖更为复杂的内存分配器和内存访问器来解决。
          3. “和稀泥式”解决方案
        4. 通常是需要短暂地暂停用户程序来做垃圾收集的
      5. Java堆
        1. 新生代(Young Generation)
        2. 老年代(Old Generation)
        3. 跨代引用假说(Intergenerational Reference Hypothesis)
    7. 经典垃圾收集器
      1. Serial收集器
      2. ParNew收集器(退役)
      3. Parallel Scavenge收集器(吞吐量优先收集器)
      4. Serial Old收集器(标记-整理)
      5. Parallel Old收集器(Parallel Scavenge收集器的老年代版本)(标记-整理)(多线程并发收集)
      6. CMS收集器(JDK5发布时,在当时具有划时代意义)(标记-清除)(关注停顿时间控制)(如今已经被官方声明为不推荐使用)
      7. Garbage First收集器(G1)(关注停顿时间控制)(全功能的垃圾收集器)(CMS的接替者和继承人)
      8. 这是最古老,最基础的垃圾收集器,只会使用一个处理器单线程去做垃圾收集,而且它在进行垃圾收集期间,会停掉所有的用户工作线程
      9. 是Serial的多线程并行版本
      10. Serial收集器的老年代版本,它同样是一个单线程收集器,使用标记-整理算法。
      11. 适合较为关注服务的响应速度,希望系统停顿时间尽可能短,以给用户带来良好的交互体验的场景。
    8. 垃圾收集器组合
      1. Parallel Scavenge + Parallel Old
      2. Serial + CMS
      3. ParNew + Serial Old
      4. 适合注重吞吐量或者处理器资源较为稀缺的场合(该组合已经被官方宣告被G1取代)
      5. 在JDK 8时将这两个组合声明为废弃(JEP 173),并在JDK 9中完全取消了这些组合的支持(JEP 214)
    9. 低延迟垃圾收集器
      1. 重要指标
        1. 内存占用(Footprint)
        2. 吞吐量(Throughput)
        3. 延迟(Latency)
        4. 一款优秀的收集器通常最多可以同时达成其中的两项
      2. G1
      3. Shenandoah收集器(致力于要比G1更低延迟的更新一代搜集器,和G1有着一部分共同的代码)
      4. ZGC收集器(具有实验性质的低延迟垃圾收集器)
      5. ZGC和Shenandoah的目标是高度相似的,都希望在尽可能对吞吐量影响不太大的前提下,实现在任意堆内存大小下都可以把垃圾收集的停顿时间限制在十毫秒以内的低延迟。
    10. 选择合适的垃圾收集器
      1. Epsilon收集器
      2. 收集器的权衡
        1. 应用程序的关注点
          1. 吞吐量
          2. 如果是数据分析、科学计算类的任务、异步实时性追求不高的后台任务,目标是能尽快算出更多的结果,那吞吐量就是主要关注点
          3. 停顿时间
          4. 如果是SLA应用、服务影响时间要求高的应用、抢购服务等,那停顿时间直接影响服务质量,严重的甚至会导致事务超时,这样延迟就是主要关注点
          5. 内存占用
          6. 如果是客户端应用或者嵌入式应用、内存资源紧张的机器,那垃圾收集的内存占用则是不可忽视的
        2. 运行应用的基础设施如
          1. 硬件规格,要涉及的系统架构是x86-32/64、SPARC还是ARM/Aarch64
          2. 处理器的数量多少,分配内存的大小
          3. 选择的操作系统是Linux、Solaris 还是Windows等
        3. 使用JDK的发行商是什么
          1. 版本号是多少
          2. 对应哪个版本的《Java虚拟机规范》
      3. 虚拟机和垃圾收集器日志
        1. JDK 9之前鱼龙混杂
        2. JDK 9之后
          1. -Xlog[:[selector][:[output][:[decorators][:output-options]]]]
      4. 如果客户应用只要运行数分钟甚至数秒,只要Java虚拟机能正确分配内存,在堆耗尽之前就会退出,那显然运行负载极小、没有任何回收行为的Epsilon便是很恰当的选择。
    11. 内存分配和策略回收
      1. 自动内存管理最根本的目标
        1. 自动给对象分配内存
        2. 自动回收分配给对象的内存
      2. 使用Serial和Serial Old客户端默认收集器组合做测试
        1. 对象优先分配在Eden空间,当Eden区没有足够空间进行分配时,虚拟机将发起一次Minor GC。
        2. 大对象直接进入老年代
          1. -XX: PretenureSizeThreshold
          2. 要尽量避免“短命大对象”
        3. 长期存活的对象将进入老年代
          1. -XX:MaxTenuringThreshold
        4. 动态对象年龄判定
          1. Survivor空间中的一批相同年龄的对象大小的总和是否大于Survivor空间的一半
        5. 空间分配担保
          1. 冒险
    12. 这3个区域随线程而生,随线程而灭,栈中的栈帧随着方法的进入和退出而有条不紊地执行着出栈和入栈操作。每一个栈帧中分配多少内存基本上是在类结构确定下来时就已知的(尽管在运行期会由即时编译器进行一些优化,但在基于概念模型的讨论里,大体上可以认为是编译期可知的),因此这几个区域的内存分配和回收都具备确定性,在这几个区域内就不需要过多考虑如何回收的问题,当方法结束或者线程结束时,内存自然就跟随着回收了。
    13. 两个区域则有着很显著的不确定性,一个接口的多个实现类需要的内存可能会不一样,一个方法所执行的不同条件分支所需要的内存也可能不一样,只有处于运行期间,我们才能知道程序究竟会创建哪些对象,创建多少个对象,这部分内存的分配和回收是动态的。垃圾收集器所关注的正是这部分内存该如何管理。
  4. 特性
    1. 不同的模式
      1. 服务端模式(Server)
      2. 客户端模式(Client)
  5. 高效并发
  6. 虚拟机字节码执行引擎
  7. 虚拟机类加载机制
  8. 虚拟机性能监控、故障处理工具
    1. 基础故障处理工具(命令行)
      1. jps
        1. 类似Unix的ps命令,可以列出正在运行的虚拟机进程,并显示虚拟机执行主类(Main Class,main()函数所在的类)名称以及这些进程的本地虚拟机唯一ID (LVMID, Local Virtual Machine Identifier) 。
        2. 对于本地虚拟机进程来说,LVMID与操作系统的进程ID(PID, Process Identifier)是一致的,使用Windows的任务管理器或者UNIX的ps命令也可以查询到虚拟机进程的LVMID,但如果同时启动了多个虚拟机进程,无法根据进程名称定位时,那就必须依赖jps命令显示主类的功能才能区分了。
      2. jstatd
        1. 可以很方便地建立远程RMI服务器
      3. jstat(JVM Statistics Monitoring Tool)
        1. 用于监视虚拟机各种运行状态信息的命令行工具
      4. jinfo(Configuration Info for Java)
        1. 实时查看和调整虚拟机各项参数
      5. jmap(Memory Map for Java)
        1. 生成堆转储快照(般称为heapdump或dump文件)
        2. 也可用-XX:+HeapDumpOnOutOfMemoryError参数”暴力“获取dump文件
      6. jhat(虚拟机堆转储快照分析工具)
        1. 一般少在生产环境用,图形化工具比它更好用
      7. jstack(Stack Trace for Java)
        1. 生成虚拟机当前时刻的线程快照(一般称为threaddump或者javacore文件)
        2. java.lang.Thread.getAllStackTraces()
          1. 通过JDK提供的API获取虚拟机所有线程的StackTraceElement对象,也可以查看线程状况。
      8. jcmd
    2. 可视化故障处理工具
      1. JConsole(最古老)
        1. Memory标签
        2. Threads标签
      2. JHSDB(JDK 9之后才正式提供)
      3. VisualVM
      4. Oracle Java SE Advanced & Suite
        1. Java Mission Control(JMC)
          1. 作为JMX控制台,显示来自虚拟机MBean提供的数据;
          2. 另一方面作为JFR的分析工具,展示来自JFR的数据。
        2. AMC(Java Advanced Management Console)
        3. JUT (Java Usage Tracker)跟踪系统
        4. JFR(Java Flight Recorder)
          1. JFR在生产环境中对吞吐量的影响一般不会高于1% (甚至号称是Zero Performance Overhead)
      5. JProfiler
      6. YourKit
    3. HotSpot虚拟机插件及工具
      1. HSDIS(JIT生成代码反编译)
  9. 调优案例分析与实战
    1. 大内存硬件上的程序部署策略
      1. 机器上只有单个的Java虚拟机实例来管理大量的Java堆内存
        1. 降低Full GC的频率
          1. 应用中绝大多数对象能否符合“朝生夕灭”的原则
          2. 大多数对象的生存时间不应当太长,尤其是不能有成批量的、长生存时间的大对象产生,这样才能保障老年代空间的稳定。
        2. 在服务器空闲时执行定时任务的方式触发Full GC(甚至是重启服务器)
        3. 需要考虑的问题
          1. ZGC和Shenandoah这种低延迟的最好解决方案目前尚未完全成熟(在任意堆内存大小下都能很好地做到低延迟GC)。
          2. 大内存的情况下,64位虚拟机的性能测试结果普遍略低于相同版本的32位虚拟机。
          3. 大型单体应用发生堆内存溢出时,几乎无法产生堆转储快照(要产生十几GB乃至更大的快照文件),即使生成了也难以分析这么大的转储快照,如果一定要分析可能要用JMC这种工具。
          4. 在64位虚拟机中消耗的内存一般比32位虚拟机要大(可以开启(默认即开启)压缩指针功能来缓解)。
      2. 同时使用若干个Java虚拟机,建立逻辑集群来利用硬件资源
    2. 堆外内存导致的内存溢出(直接内存溢出)
    3. 外部命令导致系统缓慢(比如Shell脚本)
    4. 不合适的数据结构导致内存占用过大
  10. 虚拟机执行子系统
    1. Class类文件结构
      1. 数据类型
        1. 无符号数(u1、u2、u4、u8)
        2. 表(由多个无符号数或者其他表作为数据项构成的复合数据类型)
      2. 魔数与Class文件的版本
      3. 常量池
        1. 字面量(Literal)
        2. 符号引用(Symbolic References)
        3. 截至JDK 13,常量表中分别有17种不同类型的常量
      4. 访问标志
        1. 总共16个,目前已用9个,7个归零不用。
      5. 类索引、父类索引与接口索引集合
      6. 字段表集合
        1. access_flags
        2. name_index
        3. descriptor_index
      7. 方法表集合
        1. access_flags
        2. name_index
        3. descriptor_index
      8. 属性表集合
        1. Code属性
          1. 异常表(try-catch-finally)(在Code属性中并不是必须存在的)
        2. 方法体里面的代码经过Javac编译器处理之后,最终变为字节码指令存储在Code属性内。
      9. 异常表
      10. Exceptions属性
      11. Class文件中由这三项数据来确定该类型的继承关系
    2. Java程序
      1. 方法体的代码(Code属性)
      2. 元数据(Metadata,包括类、方法、字段的定义及其他信息)
    3. 字节码
      1. 字节码与数据类型
        1. 大多数指令都包含其操作所对应的数据类型信息
      2. 不同类型的字节码指令
        1. 加载和存储指令
        2. 运算指令
        3. 类型转换指令
        4. 对象创建与访问指令
        5. 操作数栈管理指令
        6. 控制转移指令
        7. 方法调用和返回指令
        8. 异常处理指令
        9. 同步指令
    4. 虚拟机类加载机制
      1. 类的生命周期
      2. 类的加载时机
        1. 如果类型没有进行过初始化,则需要先触发其初始化阶段(主动引用)
        2. 除此之外,所有引用类型的方式都不会触发初始化(被动引用)
      3. 类的加载过程
        1. 加载
        2. 验证
          1. 文件格式验证
          2. 元数据验证
          3. 字节码验证
          4. 符号引用验证
        3. 准备
          1. 这时候进行内存分配的仅包括类变量,而不包括实例变量,实例变量将会在对象实例化时随着对象一起起分配在Java堆中。
        4. 解析
          1. 直接引用
          2. 符号引用
          3. 类或接口的解析
    5. 顾名思义字节码长度只能是一个字节(即0~255),这意味着指令集的操作码总数不能够超过256条