0 深入理解Java虚拟机-目录

01 走近Java
02 Java内存区域与内存溢出异常
03 垃圾收集器与内存分配策略
04 虚拟机性能监控与故障处理工具
05 调优案例分析与实战
06 类文件结构
07 虚拟机类加载机制
08 虚拟机字节码执行引擎
09 类加载及执行子系统的案例与实战
10 早期(编译期)优化
11 晚期(运行期)优化
12 Java内存模型与线程
13 线程安全与锁优化

2019/11/28 posted in  深入理解虚拟机

01 走近Java

2019/11/28 posted in  深入理解虚拟机

02 Java内存区域与内存溢出异常

2.1 概述

本章主要介绍Java虚拟机内存的各个区域,及其作用、服务对象以及可能产生的问题。

2.2 运行时数据区域

2.2.1 程序计数器

程序计数器 (Program Counter Register)

  • 线程私有,当前线程所执行的字节码的行号指示器。
  • PC寄存器,保证程序能够连续地执行下去,可以确定下一条指令的地址。

2.2.2 Java虚拟机栈

Java虚拟机栈(Java Virtual Machine Stacks)

  • 线程私有。
  • 每个方法在执行的同时都会创建一个栈帧,栈帧存储局部变量表、操作数栈、动态链接、方法出口等信息。调用Java方法时栈帧入栈,方法执行完成栈帧出栈。
    • 栈内存就是虚拟机栈。
    • 局部变量表存放编译期可知的基本数据类型、引用类型和returnAddress类型。
  • 栈容量超出,StackOverflowError;内存不够,OOM。

2.2.3 本地方法栈

本地方法栈(Native Method Stack)

  • Java虚拟机实现可能用到C Stacks支持Native语言。

2.2.4 Java堆

Java堆(Java Heap)

  • 所有线程共享的内存区域。
  • 存放对象实例。
  • Java堆是垃圾收集器管理的主要区域,也被称为GC堆。
  • 按分代收集算法,分为新生代和老年代。
  • 堆中没有内存或无法扩展时,抛出OOM。

2.2.5 方法区

方法区(Method Area)

  • 各个线程共享的内存区域。
  • 存储被Java虚拟机加载的类信息、常量、静态变量、即时编译期编译后的代码等数据。

2.2.6 运行时常量池

运行时常量池(Runtime Constant Pool)

  • 是方法区的一部分。
  • 存放编译期生成的字面量和符号引用。

2.2.7 直接内存

直接内存(Direct Memory)

  • 并不是虚拟机运行时数据区的一部分,会被频繁使用,导致OOM。
  • JDK 1.4引入的NIO,可以使用Native函数库直接分配堆外内存。不受Java堆大小限制,容易被忽略,导致OOM。

2.3 HotSpot虚拟机对象探秘

对象如何创建、如何布局以及如何访问。

2.3.1 对象的创建

  1. 检查类是否被加载、解析、初始化过。
  2. 为新生对象分配内存
    1. Java堆内存绝对规整:指针碰撞。用过的内存在一边,空闲在另一边,中间用指针作为分界点指示器。分配空间就需要把指针移动与对象大小相等的距离。
    2. Java堆内存不规整:虚拟机维护一个表记录内存是否可用。分配空间时从列表中查询、分配,并更新列表。
  3. 处理并发安全问题
    1. 对分配内存的动作进行同步处理,比如在虚拟机采用CAS算法并配上失败重试的方式,保证更新操作的原子性。
    2. 预先分配本地线程分配缓存(Thread Local Allocation Buffer,TLAB),在TLAB上分配内存。当TLAB用完并且被分配到新的TLAB时,才需要同步锁定。
  4. 初始化分配到的内存空间
    1. 除了对象头外都初始化为零。
  5. 设置对象的对象头
    1. 将对象的所属类、HashCode和GC分代年龄等存储在对象头中。
  6. 执行init方法进行初始化
    1. 执行init方法,初始化对象的成员变量、调用类的构造方法,这样一个对象就被创建出来了。

2.3.2 对象的内存布局

  • 对象头(Header)
    • Mark World:存储对象自身的运行时数据,比如HashCode、GC分代年龄、锁状态标志、线程持有的锁等。
    • 元数据指针:用于指向方法区中的目标类的元数据,通过元数据可以确定对象的具体类型。
  • 实例数据(Instance Data)
    • 用于存储对象中的各个类型的字段信息(包括从父类继承来的)
  • 对齐填充(Padding)
    • 不一定存在,起到了占位符的作用。

2.3.3 对象的访问定位

  • 句柄访问
    • Java堆中划出内存作为句柄池,reference中存储对象的句柄地址。句柄中包含对象实例数据与类型数据各自的具体地址信息。
    • 好处:对象被移动时只改变句柄中的实例数据指针,reference不需要修改。
  • 直接指针访问
    • Java堆对象的布局中放置访问类型数据的相关信息,reference中存储对象地址。
    • 好处:速度更快,节省指针定位的实际开销,HotSpot虚拟机中更常使用。

2.4 实战:OutOfMemoryError异常

2.4.1 Java堆溢出

2.4.2 虚拟机栈和本地方法栈溢出

2.4.3 方法区和运行时常量池溢出

2.4.4 本机直接内存溢出

2.5 本章 小结

2019/11/28 posted in  深入理解虚拟机

03 垃圾收集器与内存分配策略

3.1 概述

GC需要完成的3件事情:

  1. 哪些内存需要回收?
  2. 什么时候回收?
  3. 如何回收?

3.2 对象已死吗

垃圾收集器在堆进行回收前,要确定对象是否仍被使用。

3.2.1 引用计数算法

3.2.2 可达性分析算法

3.2.3 再谈引用

3.2.4 生存还是死亡

3.2.5 回收方法区

3.3 垃圾收集算法

3.3.1 标记—清除算法

3.3.2 复制算法

3.3.3 标记—整理算法

3.3.4 分代收集算法

3.4 HotSpot的算法实现

3.4.1 枚举根节点

3.4.2 安全点

3.4.3 安全区域

3.5 垃圾收集器

3.5.1 Serial收集器

3.5.2 ParNew收集器

3.5.3 ParallelScavenge收集器

3.5.4 SerialOld收集器

3.5.5 ParallelOld收集器

3.5.6 CMS收集器

3.5.7 G1收集器

3.5.8 理解GC日志

3.5.9 垃圾收集器参数总结

3.6 内存分配与回收策略

3.6.1 对象优先在Eden分配

3.6.2 大对象直接进入老年代

3.6.3 长期存活的对象将进入老年代

3.6.4 动态对象年龄判定

3.6.5 空间分配担保

3.7 本章 小结

2019/11/28 posted in  深入理解虚拟机

04 虚拟机性能与故障处理工具

2019/11/28 posted in  深入理解虚拟机

05 调优案例与实战

2019/11/28 posted in  深入理解虚拟机

06 类文件结构

2019/11/28 posted in  深入理解虚拟机

07 虚拟机类加载机制

7.1 概述

虚拟机如何加载Class文件?Class文件中的信息进入虚拟机中会发生什么变化?

虚拟机把描述类的数据从Class文件加载到内存,并对内存进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的java类型,这就是虚拟机的类加载机制。

Java语言中,类型的加载、连接和初始化过程都是在程序运行期间完成的。Java可以动态扩展语言特性就是依赖运行期动态加载和动态连接实现的。

7.2 类加载的时机

类的生命周期:加载、验证、准备、解析、初始化、使用、卸载。
验证、准备、解析三个阶段称为连接。
其中,加载、验证、准备、初始化和卸载5个阶段是确定的,解析阶段则不一定:这是为了支持Java语言的运行时绑定。

对于初始化节点,虚拟机规范严格规定了有且只有5种情况必须对类进行“初始化”(加载、验证、准备自然在此之前开始):

  1. 遇到new,getstatic,putstatic或invokestatic这4条字节码指令
  2. 使用java.lang.reflect包的方法对类进行反射调用
  3. 当初始化一个类的时候,发现其父类还没有进行过初始化,则需要先出发父类的初始化。
  4. 当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。
  5. 当使用JDK1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic,REF_putStatic,REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行初始化,则需要先出发初始化。

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

7.3 类加载的过程

7.3.1 加载

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

7.3.2 验证

验证是连接阶段的第一步,目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。

  1. 文件格式验证
    • 验证字节流是否符合Class文件格式的规范,并且能被当前版本的虚拟机处理。
  2. 元数据验证
    • 对字节码描述的信息进行语义分析,以保证其描述的信息符合Java语言规范要求。例如是否有父类、是否继承了不允许继承的类、是否实现了父类或接口中要求实现的所有方法等。
  3. 字节码验证
    • 最复杂,通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。上个阶段校验数据类型,这个分析类的方法体,确保方法运行时不会危害虚拟机。例如操作栈放置int使用时却按long加载入本地变量表中等 。
  4. 符号引用验证
    • 发生在虚拟机将符号引用转换为直接引用的时候,这个转换动作将在连接的第三个阶段——解析阶段中发生。

7.3.3 准备

准备阶段是正式为类变量分配内存并设置类变量初始值的阶段。这些变量所使用的内存都将在方法区中分配。注意:内存分配仅包括类变量(static修饰)。
如果类字段的字段属性表中存在ConstantValue属性,那么在准备阶段变量的值就会被初始化为ConstantValue属性所指定的值。

7.3.4 解析

解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。

  • 符号引用:以一组符号来描述所引用的目标,可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。
  • 直接引用:可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。

虚拟机规范并未规定解析阶段发生的具体时间,所以虚拟机可以根据需要来判断到底是在类被加载器加载时就对常量池中的符号进行解决,还是等到一个符号引用被使用前才解析它。

invokedynamic指令用于支持动态语言支持,即程序运行到这条指令时,解析动作才能进行。

解析动作主要针对接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。

7.3.5 初始化

初始化阶段,才真正开始执行类中定义的Java程序代码。
初始化阶段,根据程序员通过程序制定的主观计划,去初始化变量和其他资源。初始化阶段是执行类构造器<clinit>()方法的过程:

  • <clinit>()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块中的语句合并产生的。
  • <clinit>()与类的构造函数不同,它不需要显示地调用父类构造器,虚拟机会保证在子类的<clinit>()方法执行之前,父类的<clinit>()方法已经执行完毕。
  • 由于父类的<clinit>()方法先执行,也就意味着父类中定义的静态语句块要优于子类的变量赋值操作。
  • <clinit>()方法对于类或接口并不是必须的。
  • 虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确地加锁、同步。

7.4 类加载器

类加载器:通过一个类的全限定名来获取描述此类的二进制字节流。

7.4.1 类与类加载器

对于任意一个类,都需要由加载它的类加载器和这个类本身一同确定其在Java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。即比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才有意义。类加载器不同,类必定不相等。

7.4.2 双亲委派模型

  • 系统类加载器
    • Bootstrap ClassLoader
    • Extensions ClassLoader
    • Application ClassLoader
  • 自定义类加载器

双亲委派模型:如果一个类加载器收到了类加载请求,它首先不会自己去尝试加载这个类,而是把这个请问委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终 都应该传送到顶层的启动类加载器中,只有当 父加载器反馈自己无法完成这个加载请求时(它的搜索范围中没有找到所需的类),子加载器才会尝试自己去加载。

双亲委派模型的好处:Java类随着它的类加载器一起具备了一种带有优先级的层次关系。
双亲委派模型的实现:java.lang.ClassLoader的loadClass()方法。

7.4.3 破坏双亲委派模型

  1. JDK1.2之前使用loadClass(),之后使用findClass()
  2. 基础类调用用户的代码。如JDBC。解决:线程上下文类加载器(Thread Context ClassLoader)。
  3. 用户追求动态性,如代码热替换,模块热部署等。

7.5 本章小结

2019/11/28 posted in  深入理解虚拟机

08 虚拟机字节码执行引擎

2019/11/28 posted in  深入理解虚拟机

09 类加载及执行子系统的案例与实战

2019/11/28 posted in  深入理解虚拟机

10 早起(编译期)优化

10.1概述302

10.2Javac编译器303

10.2.1Javac的源码与调试303

10.2.2解析与填充符号表305

10.2.3注解处理器307

10.2.4语义分析与字节码生成307

10.3Java语法糖的味道311

10.3.1泛型与类型擦除311

10.3.2自动装箱、拆箱与遍历循环315

10.3.3条件编译317

10.4实战:插入式注解处理器318

10.4.1实战目标318

10.4.2代码实现319

10.4.3运行与测试326

10.4.4其他应用案例327

10.5本章 小结328

2019/11/28 posted in  深入理解虚拟机

11 晚期(运行期)优化

11.1概述329

11.2HotSpot虚拟机内的即时编译器329

11.2.1解释器与编译器330

11.2.2编译对象与触发条件332

11.2.3编译过程337

11.2.4查看及分析即时编译结果339

11.3编译优化技术345

11.3.1优化技术概览346

11.3.2公共子表达式消除350

11.3.3数组边界检查消除351

11.3.4方法内联352

11.3.5逃逸分析354

11.4Java与C/C++的编译器对比356

11.5本章 小结358

2019/11/28 posted in  深入理解虚拟机

12 Java内存模型与线程

2019/11/28 posted in  深入理解虚拟机

13 线程安全与锁优化

2019/11/28 posted in  深入理解虚拟机