1 JVM内存区域
1.1 架构图
1.1.1 JVM
HotSpotJVMArchitecture

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

1.1.2 Heap

2 GC
2.1 判断对象是否存活
| 算法 | 描述 | 优点 | 缺点 |
|---|---|---|---|
| Reference Counting | 给对象添加引用计算器 | 简单 | 难以解决对象之间相互引用问题 |
| Reachability Analysis | 设置 GC root, 构造一颗树, 看一个对象是否和GC root相连 | 复杂,需要遍历整棵树 | 解决了相互引用问题 |
2.2 对象引用概念
我们希望能描述这样一类对象: 当内存空间还足够时, 则能保留在内存之中; 如果内存空间在进行垃圾收集后还是非常紧张, 则可以抛弃这些对象. 所以对象的引用不仅仅只有一种, 衍生出来了 Soft, Weak, Phantom 等形式
- StrongReference, 即 Object
- SoftReference
- WeakReference
- PhantomReference
2.3 垃圾收集算法
- Mark Sweep, 标记, 清除. 会产生大量的内存碎片, 可能会导致程序在分配内存时获取不到连续的内存空间, 而不得不进行第二次GC操作
- Copying, 复制算法. 将内存按容量划分为两块, 每次只用其中的一块. 用完了就将存活的对象拷贝到另外一块, 再将原来的清除
- Mark Compact. 标记, 整理. Compact的意思是, 让存活的对象往一端移动, 避免大量的内存碎片
- Generational Collection. 分代收集, 1,2,3的结合体, 在不同的情况采取不同的方式.
推荐阅读这篇文章: Java Garbage Collection Basics, 她讲述了GC收集算法是如何从最初的 Mark Sweep 到最后的 Generational Collection
2.4 HotSpot GC算法
2.4.1 结构

-
Young Generation
- 新分配对象分配会存在这里
- 该部分的GC收集为
Minor Garbage Collection, 属于Stop the World Event
-
Old Generation
- 存放长期存活的对象(是否长期存活, 有对应的配置值, 从Young Generation转移过来)
- 该部分的GC收集为
Major Garbage Collection, 也属于Stop the World Event
-
Permanent Generation
- 存放Java的一些方法和Class, 可以认为是
Method Area - 在
Full Garbage Collection的时候, 会清理该部分
- 存放Java的一些方法和Class, 可以认为是
各个Generation的内存分配比例默认为: Young Generation 中, Eden 和 Survivor 的默认比例为8:1, 可以通过 -XX:SurvivorRatio=8 进行设置, 而 Old Generation 和 Young 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区

S0和S1有一个为冗余内存, 类似于 Copying 算法的冗余内存, S0和S1始终有一块为空
处理步骤为:
为什么需要有两个区域? 我的理解是为了避免内存碎片问题, 在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 |
下面仅仅对图形化工具 jconsole 和 jvisualvm 进行介绍, 并写一些 内存泄漏
和 线程死锁的例子.
2.9.2 第三方工具
3 class File
参考自0xCAFEBABE ? - java class file format, an overview , JAVA BYTECODE STRUCTURE 和 Understanding Jvm Internals
3.1 架构图
SE7的架构如下: Major Version: 0x0033, 即51

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-
#4为 class_index, 描述了她的class为 com/dengqinghua/calculate/Salary, 即com.dengqinghua.calculate.Salary -
#20为 name_and_type_index, 描述了字段的名称为:monthSalary, 字段的类型为 I, 即 int
-
-
com/dengqinghua/calculate/Salary.monthSalary:I为注释
比较难理解的是 #4.#20 部分. #4 是 CONSTANT_Class 索引, #20 是 CONSTANT_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中用到的常量, 包括总的常量数, 常量类型, 常量索引值等

3.4 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
