More: More Dengqinghua

JVM剖析

2018-04-11

该文档涵盖了JVM的一些知识点和总结.

阅读完该文档后,您将会了解到:

1 JVM内存区域

1.1 架构图

1.1.1 JVM

HotSpotJVMArchitecture HotSpotJVMArchitecture

如果更细分地认识, 可以按照下面的方式划分: jvm_structure

1.1.2 Heap

HotSpotHeapStructure

2 GC

2.1 判断对象是否存活

算法 描述 优点 缺点
Reference Counting 给对象添加引用计算器 简单 难以解决对象之间相互引用问题
Reachability Analysis 设置 GC root, 构造一颗树, 看一个对象是否和GC root相连 复杂,需要遍历整棵树 解决了相互引用问题

2.2 对象引用概念

我们希望能描述这样一类对象: 当内存空间还足够时, 则能保留在内存之中; 如果内存空间在进行垃圾收集后还是非常紧张, 则可以抛弃这些对象. 所以对象的引用不仅仅只有一种, 衍生出来了 Soft, Weak, Phantom 等形式

  • StrongReference, 即 Object
  • SoftReference
  • WeakReference
  • PhantomReference

2.3 垃圾收集算法

  1. Mark Sweep, 标记, 清除. 会产生大量的内存碎片, 可能会导致程序在分配内存时获取不到连续的内存空间, 而不得不进行第二次GC操作
  2. Copying, 复制算法. 将内存按容量划分为两块, 每次只用其中的一块. 用完了就将存活的对象拷贝到另外一块, 再将原来的清除
  3. Mark Compact. 标记, 整理. Compact的意思是, 让存活的对象往一端移动, 避免大量的内存碎片
  4. Generational Collection. 分代收集, 1,2,3的结合体, 在不同的情况采取不同的方式.

推荐阅读这篇文章: Java Garbage Collection Basics, 她讲述了GC收集算法是如何从最初的 Mark Sweep 到最后的 Generational Collection

2.4 HotSpot GC算法

2.4.1 结构

HotSpotHeapStructure

  1. Young Generation

    • 新分配对象分配会存在这里
    • 该部分的GC收集为Minor Garbage Collection, 属于 Stop the World Event
  2. Old Generation

    • 存放长期存活的对象(是否长期存活, 有对应的配置值, 从Young Generation转移过来)
    • 该部分的GC收集为Major Garbage Collection, 也属于 Stop the World Event
  3. Permanent Generation

    • 存放Java的一些方法和Class, 可以认为是Method Area
    • Full Garbage Collection的时候, 会清理该部分

各个Generation的内存分配比例默认为: Young Generation 中, EdenSurvivor 的默认比例为8:1, 可以通过 -XX:SurvivorRatio=8 进行设置, 而 Old GenerationYoung Generation 比例为 2: 1, 可以通过 -XX:NewRatio=2 进行配置. 参考: Java HotSpot VM Options

2.5 内存分配和回收策略(Serial)

如果 Old Generation 区域, 即 Tenured 区域满了之后, 会触发 Major GC

2.5.1 object age

JVM给了每一个对象一个Age计数器, 当对象在survivor经历一次Minor GC的时候, Age便加1 当Age达到threshold(默认为15, 通过-XX:MaxTenuringThreshold=15进行设置), 则会进行 Promotion

2.5.2 survivor choose

我们知道有两个survivor区

HotSpotHeapStructure

S0和S1有一个为冗余内存, 类似于 Copying 算法的冗余内存, S0和S1始终有一块为空

处理步骤为:

Back

为什么需要有两个区域? 我的理解是为了避免内存碎片问题, 在StackOverflow的 这篇回答中有解释. 另外, 拆分出两个区域可以使得算法变得更简单.

2.6 GC触发条件

  • Eden full(Minor)
  • Tenured full(Major)
  • System.gc(Major)

2.7 Garbage Collectors

  • Serial GC (Young: Copying, Old: Mark Compact)
  • Paralle Scavange GC(Young(MultiThread): Copying, Old(SingleThread): Mark Compact)
  • CMS, Concurrent Mark Sweep
  • G1
2.7.1 CMS

参考: Garbage Collectors - Serial vs. Parallel vs. CMS vs. G1 (and what’s new in Java 8)

吞吐量 Throughput 为 CPU用于运行用户代码的时间 / (运行用户代码的时间 + GC时间), 一般来说, 当 Generation 的空间变小之后, 一次GC的时间更快, 但是GC会更频繁, 这样的话在相同的时间内, GC的总时间 不一定会更快

2.8 GC日志

参数设置

-XX:+DisableExplicitGC
-XX:+PrintGCDetails
-XX:+PrintGCApplicationStoppedTime
-XX:+PrintGCApplicationConcurrentTime
-XX:+PrintGCDateStamps
-Xloggc:gclog.log
-XX:+UseGCLogFileRotation
-XX:NumberOfGCLogFiles=5
-XX:GCLogFileSize=2000k

如果是用 maven exec:java 来执行, 可以添加配置 export MAVEN_OPTS="-XX:+DisableExplicitGC -XX:+PrintGCDetails -XX:+PrintGCApplicationStoppedTime -XX:+PrintGCApplicationConcurrentTime -XX:+PrintGCDateStamps -Xloggc:gclog.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=2000k -Xmx30M"

参考: Enabling and Analyzing the Garbage Collection Log

2.9 GC研究工具

2.9.1 SDK自带工具
名称 描述
jps 查看当前所有的java进程
jstat -gc 查看当前gc情况, 包括GC情况, 新生代/老生代的内存占用情况等, 参数含义见这里
jinfo 查看JVM的启动参数
jstack JVM的栈信息
jconsole JVM的可视化工具
jvisualvm 多合一故障处理工具
jmap 内存映象工具, heapdump

下面仅仅对图形化工具 jconsolejvisualvm 进行介绍, 并写一些 内存泄漏 和 线程死锁的例子.

2.9.2 第三方工具

3 class File

3.1 架构图

SE7的架构如下: Major Version: 0x0033, 即51

class_file_overview

3.2 javap分析class文件

通过javap可以解析class文件

git clone https://github.com/dengqinghua/my_examples.git
cd my_examples/java
mvn compile
javap -v target/classes/com/dengqinghua/calculate/Salary.class

如果想直接查看16进制的存储, 可以用shell命令: xxd target/classes/com/dengqinghua/calculate/Salary.class

如java源码为:

package com.dengqinghua.calculate;

public class Salary {
    private int monthSalary;
    private int bonus;

    /**
     * 计算一年的总的薪水
     *
     * @return 返回总薪水值
     */
    public long calculateYearSalary() {
        return this.monthSalary * 12 + bonus;
    }

    public Salary(int monthSalary, int bonus) {
        this.monthSalary = monthSalary;
        this.bonus = bonus;
    }
}

解析后的class文件为

Classfile my_examples/java/target/classes/com/dengqinghua/calculate/Salary.class
  Last modified Apr 25, 2018; size 505 bytes
  MD5 checksum 57ea37e27fa44146cdcf54c9c532799d
  Compiled from "Salary.java"
public class com.dengqinghua.calculate.Salary
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Fieldref           #4.#20         // com/dengqinghua/calculate/Salary.monthSalary:I
   #2 = Fieldref           #4.#21         // com/dengqinghua/calculate/Salary.bonus:I
   #3 = Methodref          #5.#22         // java/lang/Object."<init>":()V
   #4 = Class              #23            // com/dengqinghua/calculate/Salary
   #5 = Class              #24            // java/lang/Object
   #6 = Utf8               monthSalary
   #7 = Utf8               I
   #8 = Utf8               bonus
   #9 = Utf8               calculateYearSalary
  #10 = Utf8               ()J
  #11 = Utf8               Code
  #12 = Utf8               LineNumberTable
  #13 = Utf8               LocalVariableTable
  #14 = Utf8               this
  #15 = Utf8               Lcom/dengqinghua/calculate/Salary;
  #16 = Utf8               <init>
  #17 = Utf8               (II)V
  #18 = Utf8               SourceFile
  #19 = Utf8               Salary.java
  #20 = NameAndType        #6:#7          // monthSalary:I
  #21 = NameAndType        #8:#7          // bonus:I
  #22 = NameAndType        #16:#25        // "<init>":()V
  #23 = Utf8               com/dengqinghua/calculate/Salary
  #24 = Utf8               java/lang/Object
  #25 = Utf8               ()V
{
  public long calculateYearSalary();
    descriptor: ()J
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: getfield      #1                  // Field monthSalary:I
         4: bipush        12
         6: imul
         7: aload_0
         8: getfield      #2                  // Field bonus:I
        11: iadd
        12: i2l
        13: lreturn
      LineNumberTable:
        line 13: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      14     0  this   Lcom/dengqinghua/calculate/Salary;

  public com.dengqinghua.calculate.Salary(int, int);
    descriptor: (II)V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=3
         0: aload_0
         1: invokespecial #3                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: iload_1
         6: putfield      #1                  // Field monthSalary:I
         9: aload_0
        10: iload_2
        11: putfield      #2                  // Field bonus:I
        14: return
      LineNumberTable:
        line 16: 0
        line 17: 4
        line 18: 9
        line 19: 14
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      15     0  this   Lcom/dengqinghua/calculate/Salary;
            0      15     1 monthSalary   I
            0      15     2 bonus   I
}
SourceFile: "Salary.java"

如何理解上面这个文件呢?

java原始代码的这一行

private int monthSalary;

对应着 class 文件的这一行:

Constant pool:
   #1 = Fieldref           #4.#20         // com/dengqinghua/calculate/Salary.monthSalary:I

即字段 monthSalary 的定义.

  • #1 代表索引, 通过该索引可以找到对应的数据
  • #4.#20 代表 Fieldref 下对应的class和name_and_type的索引值, 即 class_index 和 name_and_type_index
    • #4class_index, 描述了她的class为 com/dengqinghua/calculate/Salary, 即 com.dengqinghua.calculate.Salary
    • #20name_and_type_index, 描述了字段的名称为: monthSalary, 字段的类型为 I, 即 int
  • com/dengqinghua/calculate/Salary.monthSalary:I 为注释

比较难理解的是 #4.#20 部分. #4CONSTANT_Class 索引, #20CONSTANT_NameAndType 索引.

我们找到 class 文件的 #4#20 部分如下:

#4  = Class              #23            // com/dengqinghua/calculate/Salary
#20 = NameAndType        #6:#7          // monthSalary:I

Fieldref 和其相关的数据结构为:

// u1, u2 分别代表 1个字节, 2个字节

// 字段
CONSTANT_Fieldref_info {
    u1 tag;                 // 标识她的身份属性
    u2 class_index;         // 对应的 CONSTANT_Class 的索引
    u2 name_and_type_index; // 对应的 CONSTANT_NameAndType 的索引
}

// class 或者 interface
CONSTANT_Class_info {
  u1 tag;
  u2 name_index; // 对应的 字符串 CONSTANT_Utf8 的索引
}

// 字符串的表示
CONSTANT_Utf8_info {
  u1 tag;
  u2 length;
  u1 bytes[length];
}

// 标示一个字段或者一个方法
CONSTANT_NameAndType_info {
  u1 tag;
  u2 name_index; // 对应的 字符串 CONSTANT_Utf8 的索引
  u2 descriptor_index; // 对应的描述符标示, 对应的 字符串 CONSTANT_Utf8 的索引, 如 int 用 I 表示
}

在上面的 CONSTANT_NameAndType_info 中, descriptor_index 的值有下面这几种

名称 释义
B Byte
C Char
D Double
F Float
I int
J long
S short
Z boolean
V void
L 所有的Object, 如Ljava/lang/Object
[ Array,如果是int[], 则表示为 [I
() 方法描述, 如 Object method(int i, int[] j) 表示为 (I[I)Ljava/lang/Object

3.3 Constant Pool

Constant Pool(常量池), 定义了Java中用到的常量, 包括总的常量数, 常量类型, 常量索引值等

Constant_pool

3.4 Access Flags

描述了可见性等参数

access_flags

3.5 Fields

Fields为字段内容, 如上面提到的 monthSalary

field_info {
  u2 access_flags; // 可申明多个flags, 如 public static int oneField;
  u2 name_index;
  u2 descriptor_index;
  u2 attributes_count; // attributes 的数目

  // 如果是常量, 如 final, 会存储在 attribute_info 中
  // 注意, 可变的初始化变量是不会存储在这儿的
  attribute_info attributes[attributes_count];
}

常量的数据结构为:

ConstantValue_attribute {
  u2 attribute_name_index;
  u4 attribute_length;
  u2 constantvalue_index;
}

3.6 Attributes

属性表可以认为是认为是基本的util, Filed, Method, Class 都会根据自己需要存储 Attribute

3.6.1 Code Attribute

下面是Code部分中的attribute

Code_attribute

3.7 Methods

4 References