深入理解Java虚拟机

1 走近Java 7
1.1 概述 7
1.2 Java技术体系 7
1.3 Java发展史 7
2 Java内存区域与内存溢出异常 8
2.1 概述 8
2.2 运行时数据区域 8
2.2.1 程序计数器 8
2.2.2 Java虚拟机栈 8
2.2.3 本地方法栈 9
2.2.4 Java堆 9
2.2.5 方法区 9
2.2.6 运行时常量池 9
2.2.7 直接内存 9
2.3 对象访问 9
2.4 实战:OutOfMemoryError异常 10
2.4.1 Java堆溢出 10
2.4.2 虚拟机栈和本地方法栈溢出 11
2.4.3 运行时常量池溢出 11
2.4.4 方法区溢出 11
2.4.5 本机直接内存溢出 11
3 垃圾收集器与内存分配策略 12
3.1 概述 12
3.2 对象已死? 12
3.2.1 引用计数算法 12
3.2.2 根搜索算法 12
3.2.3 再谈引用 12
3.2.4 生存还是死亡? 12
3.2.5 回收方法区 13
3.3 垃圾收集算法 13
3.3.1 标记-清除算法 13
3.3.2 复制算法 14
3.3.3 标记-整理算法 14
3.3.4 分代收集算法 15
3.4 HotSpot的算法实现 15
3.4.1 枚举根节点 15
3.4.2 安全点 15
3.4.3 安全区域 15
3.5 垃圾收集器 15
3.5.1 Serial收集器 16
3.5.2 ParNew收集器 17
3.5.3 Parallel Scavenge收集器 17
3.5.4 Serial Old收集器 17
3.5.5 Parallel Old收集器 18
3.5.6 CMS收集器 18
3.5.7 G1收集器 19
3.5.8 理解GC日志 19
3.5.9 垃圾收集器参数总结 20
3.6 内存分配与回收策略 21
3.6.1 对象优先在Eden分配 21
3.6.2 大对象直接进入老年代 21
3.6.3 长期存活的对象将进入老年代 21
3.6.4 动态对象年龄判定 21
3.6.5 空间分配担保 21
4 虚拟机性能监控与故障处理工具 22
4.1 概述 22
4.2 JDK的命令行工具 22
4.2.1 jps: 虚拟机进程状况工具 22
4.2.2 jstat: 虚拟机统计信息监视工具 23
4.2.3 jinfo: Java配置信息工具 23
4.2.4 jmap: Java内存映像工具 24
4.2.5 jhat: 虚拟机堆转储快照分析工具 24
4.2.6 jstack: Java堆栈跟踪工具 24
4.2.7 HSDIS: JIT生成代码反汇编 25
4.3 JDK的可视化工具 25
4.3.1 JConsole: Java监视与管理控制台 25
4.3.2 VisualVM: 多合一故障处理工具 25
5 调优案例分析与实战 26
5.1 概述 26
5.2 案例分析 26
5.2.1 高性能硬件上的程序部署策略 26
5.2.2 集群间同步导致的内存溢出 26
5.2.3 堆外内存导致的溢出错误 26
5.2.4 外部命令导致系统缓慢 26
5.2.5 服务器JVM进程崩溃 26
5.2.6 不恰当数据结构导致内存占用过大 26
5.2.7 由Windows虚拟内存导致的长时间停顿 26
5.3 实战:Eclipse运行速度调优 26
6 类文件结构 27
6.1 Class类文件的结构 27
6.1.1 魔数与Class文件的版本 28
6.1.2 常量池 29
6.1.3 访问标志 31
6.1.4 类索引、父类索引与接口索引集合 31
6.1.5 字段表集合 31
6.1.6 方法表集合 32
6.1.7 属性表集合 33
6.2 Class文件结构的发展 37
7 虚拟机类加载机制 38
7.1 概述 38
7.2 类加载的时机 38
7.3 类加载的过程 38
7.3.1 加载 38
7.3.2 验证 39
7.3.3 准备 39
7.3.4 解析 40
7.3.5 初始化 41
7.4 类加载器 41
7.4.1 类和类加载器 41
7.4.2 双亲委派模型 41
7.4.3 破坏双亲委派模型 42
8 虚拟机字节码执行引擎 42
8.1 概述 42
8.2 运行时栈帧结构 42
8.2.1 局部变量表 42
8.2.2 操作数栈 43
8.2.3 动态连接 43
8.2.4 方法返回地址 44
8.2.5 附加信息 44
8.3 方法调用 44
8.3.1 解析 44
8.3.2 分派 44
8.4 基于栈的字节码解释执行引擎 45
8.4.1 解释执行 45
8.4.2 基于栈的指令集与基于寄存器的指令集 45
8.4.3 基于栈的解释器执行过程 45
9 类加载及执行子系统的案例与实战 45
9.1 概述 45
9.2 案例分析 46
9.2.1 Tomcat: 正统的类加载器架构 46
9.2.2 OSGi: 灵活的类加载器架构 47
9.2.3 字节码生成技术与动态代理的实现 47
9.2.4 Retrotranslator: 跨越JDK版本 47
9.3 实战:自己动手实现远程执行功能 47
9.3.1 目标 47
9.3.2 思路 47
9.3.3 实现 47
10 早期(编译器)优化 47
10.1 概述 47
10.2 Javac编译器 48
10.2.1 Javac的源码与调试 48
10.2.2 解析与填充符号表 48
10.2.3 注解处理器 49
10.2.4 语义分析与字节码生成 49
10.3 Java语法糖的味道 49
10.3.1 泛型与类型擦除 49
10.3.2 自动装箱、拆箱与遍历循环 49
10.3.3 条件编译 50
10.4 实战:插入式注解处理器 50
10.4.1 实战目标 50
10.4.2 代码实现 50
11 晚期(运行期)优化 50
11.1 概述 50
11.2 HotSpot虚拟机内的即时编译器 50
11.2.1 解释器与编译器 50
11.2.2 编译对象与触发条件 51
11.2.3 编译过程 52
11.2.4 查看与分析即时编译结果 53
11.3 编译优化技术 53
11.3.1 优化技术概览 53
11.3.2 公共子表达式消除 53
11.3.3 数组边界检查消除 54
11.3.4 方法内联 54
11.3.5 逃逸分析 54
11.4 Java与C/C++的编译器对比 54
12 Java内存模型与线程 54
12.1 概述 54
12.2 硬件的效率与一致性 54
12.3 Java内存模型 55
12.3.1 主内存与工作内存 55
12.3.2 内存间交互操作 55
12.3.3 对于volatile型变量的特殊规则 55
12.3.4 对于long和double型变量的特殊规则 56
12.3.5 原则性、可见性和有序性 56
12.3.6 先行发生原则 56
12.4 Java与线程 56
12.4.1 线程的实现 56
12.4.2 Java线程调度 59
12.4.3 状态转换 59
13 线程安全与锁优化 59
13.1 概述 59
13.2 线程安全 60
13.2.1 Java语言中的线程安全 60
13.2.2 线程安全的实现方法 60
13.3 锁优化 61
13.3.1 自旋锁与自适应自旋 61
13.3.2 锁消除 61
13.3.3 锁粗化 61
13.3.4 轻量级锁 61
13.3.5 偏向锁 62
14 附录 62
14.1 HotSpot虚拟机主要参数表 62
14.1.1 内存管理参数 62
14.2 对象查询语言(OQL)简介 62
15 其它 62
15.1 关于内存分配 62
15.2 其它 62

1 走近Java
1.1 概述
略过
1.2 Java技术体系
Java技术体系可以分为四个平台:
 Java Card
 Jave ME
 Java SE
 Java EE
1.3 Java发展史

2 Java内存区域与内存溢出异常
2.1 概述
2.2 运行时数据区域

2.2.1 程序计数器
程序计数器(Program Counter Register)是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器。
2.2.2 Java虚拟机栈
与程序计数器一样,Java虚拟机栈(Java Virtual Machine Stacks)也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

两种异常状况:
 如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;
 如果虚拟机栈可以动态扩展,当扩展时无法申请到足够的内存时会抛出OutOfMemoryError异常。
2.2.3 本地方法栈
2.2.4 Java堆
各个线程共享
理论上,所有的对象实例都在堆上分配内存,但是随着JIT编译器的发展与逃逸分析技术的逐渐成熟,栈上分配、标量替换优化技术的出现,这个已经不绝对了。
Java堆可以细分为:新生代和老年代;再细致一点的有Eden空间、From Survivor空间、To Survivor空间等。
2.2.5 方法区
 各个线程共享
 用于存储已被虚拟机加载的类信息、常量、静态变量、即使编译器编译后的代码等数据。
 别名:Non-Heap(非堆)
 对于HotSpot虚拟机,有人称方法区为“永久代”。
 当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。
2.2.6 运行时常量池
运行时常量池(Runtime Constant Pool)是方法区的一部分。
2.2.7 直接内存
2.3 对象访问
Object obj=new Object();
 “Object obj”这部分的语义将会反映到Java栈的本地变量表中。
 “new Object()”这部分的语义将会反映到Java堆中。
 Java对中必须包含能查找到此对象类型数据的地址信息,这些类型数据存储在方法区中。

reference类型引用,主流访问方式有两种:使用句柄和直接指针。
 使用句柄:java堆中会划出一块内存来作为句柄池。

 直接指针方式:

2.4 实战:OutOfMemoryError异常
2.4.1 Java堆溢出
示例: 代码清单2-1 Java堆内存溢出异常测试

使用Eclipse Memory Analyzer对dump信息进行分析
Shallow Heap:对象自身占用的内存大小,不包括它引用的对象
Retained Size:Retained Size=当前对象大小+当前对象可直接或间接引用到的对象的大小总和。
相关资料参考:http://rdc.taobao.com/team/jm/archives/900
2.4.2 虚拟机栈和本地方法栈溢出
 HotSpot虚拟机不区分虚拟机栈和本地方法栈
 -Xss: 设定每个线程的栈大小
2.4.3 运行时常量池溢出
-XX:PermSize: 设置永久代最小尺寸,初始分配,示例:-XX:PermSize=64MB
-XX:MaxPermSize: 设置永久代运行分配的最大尺寸,按需分配,示例:-XX:MaxPermSize=256MB
过小会导致:java.lang.OutOfMemoryError: PermGen space

MaxPermSize缺省值和-server -client选项相关。
-server选项下默认MaxPermSize为64m
-client选项下默认MaxPermSize为32m

示例:代码清单2-4 运行时常量池导致的内存溢出异常
异常分析:抛出异常信息:
Exception in thread “main” java.lang.OutOfMemoryError: PermGen space
at java.lang.String.intern(Native Method)
at org.fenixsoft.test0243.RuntimeConstantPoolOOM.main(RuntimeConstantPoolOOM.java:19)
PermGen space,持久代空间不足,说明是方法区的溢出。
虽然Dump分析的是堆信息,但是也许能简介看出些问题。
2.4.4 方法区溢出
示例:代码清单2-5 借助CGLib使得方法区出现内存溢出异常。
2.4.5 本机直接内存溢出
DirectMemory容量可通过 –XX:MaxDirectMemorySize指定,如果不指定,则默认与Java堆的最大值(-Xmx指定)一样。
示例:代码清单2-6 使用unsafe分配本机内存
3 垃圾收集器与内存分配策略
3.1 概述
程序计数器、虚拟机栈、本地方法栈,这几个区域不需要过多考虑回收问题。
3.2 对象已死?
3.2.1 引用计数算法
Java语言没有选用引用计数算法来管理内存,最主要的原因是它很难解决对象之间的相互循环引用的问题。
示例:代码清单3-1 引用计数算法的缺陷
3.2.2 根搜索算法
在Java语言里,可作为GC Roots的对象包括下面几种:
 虚拟机栈(栈帧中的本地变量表)中的引用的对象。
 方法区中的类静态属性引用的对象。
 方法区中的常量引用的对象。
 本地方法栈中JNI(即一般说的Native方法)的引用的对象。

3.2.3 再谈引用
分为四种:强引用、软引用、弱引用、虚引用
 强引用:最普遍
 软引用:通过SoftReference类来实现软引用。内存溢出之前,会把这些对象列进回收范围进行第二次回收。
 弱引用:通过WeakReference类来实现弱引用。只能生存到下一次垃圾收集发生之前。
 虚引用:通过PhantomReference类来实现虚引用。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。唯一目的就是希望能在这个对象被收集器回收时收到一个系统通知。
3.2.4 生存还是死亡?
主要介绍了回收的复杂过程,finalize()方法的作用。略过
3.2.5 回收方法区
 方法区(HotSpot虚拟机中的永久代)的垃圾收集“性价比”比较低。
 回收废弃常量:
 回收无用的类:
 该类所有的实例都已经被回收,也就是Java堆中不存在该类的任何实例。
 加载该类的ClassLoader已经被回收。
 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

-Xnoclassgc: HotSpot虚拟机默认进行无用类的垃圾回收,设置这个参数表示不进行类的垃圾回收。

3.3 垃圾收集算法
3.3.1 标记-清除算法
 标记-清除:Mark-Sweep;标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。
 缺点:
 效率问题,标记和清除过程的效率都不高
 空间问题,产生大量不连续的内存碎片。

3.3.2 复制算法
将可用内存按容量划分为大小相等的两块,每次只使用其中一块。当这一块内存用完,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。

新生代的回收即采用复制算法。
新生代内存包括一块较大的Eden空间和两块较小的Survivor空间。

3.3.3 标记-整理算法
这是老年代的垃圾收集算法。

3.3.4 分代收集算法
把Java堆分为新生代和老年代,根据各个年代的特点采用最适当的收集算法。
3.4 HotSpot的算法实现
3.4.1 枚举根节点
为什么必须要有GC停顿?P72
3.4.2 安全点
3.4.3 安全区域
3.5 垃圾收集器
HotSpot 1.6版Update 22的收集器列表:

3.5.1 Serial收集器
最基本、历史最悠久的收集器
必须暂停七天所有的工作线程,直到它收集结束。
是虚拟机运行在Client模式下的默认新生代收集器。

3.5.2 ParNew收集器
 Serial收集器的多线程版本
 为什么会有这个收集器?一个原因是:除了Serial收集器外,只有它能与CMS收集器配合工作。
并行(Parallel): 多条垃圾收集器线程并行工作,但此时用户线程仍处于等待状态。
并发(Concurrent): 用户线程与垃圾收集线程同时执行,用户程序继续运行,垃圾收集程序运行于另一个CPU上。

 -XX:+UseConcMarkSweepGC:表示使用CMS收集器,如果配置了这个,默认的新生代收集器是ParNew
 -XX:+UseParNewGC:指定新生代收集器是ParNew
 -XX:ParallelGCThreads:默认开启的收集线程数与CPU的数量相同,在CPU非常多时,可以使用-XX:ParallelGCThreads参数来限制垃圾收集的线程数

3.5.3 Parallel Scavenge收集器
 CMS收集器:尽可能缩短垃圾收集时用户线程的停顿时间;Parallel Scavenge收集器的目标是达到一个可控制的吞吐量(Throughput)
 吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)
 -XX:+UseParallelGC:JVM运行在Server模式下的默认值,打开此开关后,使用Parallel Scavenge + Serial Old的收集器组合进行回收
 -XX:MaxGCPauseMillis:控制最大垃圾收集停顿时间,大于0的毫秒值 (注:一般不设置)
 -XX:GCTimeRatio:设置吞吐量大小;如果把此参数设置为19,那允许的最大GC时间=1/(1+19)=5%
 -XX:+UseAdaptiveSizePolicy(默认开启),虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整参数以提供最合适的停顿时间或最大吞吐量。(建议在使用Parallel Scavenge收集器时开启)
3.5.4 Serial Old收集器
 Serial收集器的老年代版本
 Client模式下的默认使用

3.5.5 Parallel Old收集器
 Parallel Scavenge的老年代版本

3.5.6 CMS收集器
 CMS(Concurrent Mark Sweep):以获取最短回收停顿时间为目标的收集器。
 一款优秀的收集器:并发收集,低停顿
 缺点:
 CMS收集器对CPU资源非常敏感。默认启动的回收线程数是(CPU梳理+3)/4,也就是当CPU在4个以上时,并发回收时垃圾收集线程最多占用不超过25%的CPU资源。但是当CPU不足4个时,那么CMS对用户程序的影响就可能变得很大。
 无法处理浮动垃圾(Floating Garbage),可能出现“Concurrent Mode Failure”失败而导致另一次Full GC的产生。 浮动垃圾:…;默认设置下,CMS收集器在老年代使用了68%的空间后就会被激活,这偏保守了,可以调高参数-XX:CMSInitiatingOccupancyFraction的值来提高触发百分比,以降低内存回收次数以获取更好的性能。当然设置太高也有问题…
 产品大量空间碎片。为了解决这个问题,CMS收集器提供了一个-XX:+UseCMSCompactAtFullCollection开关参数,用于在“享受”完Full GC服务之后额外附送一个碎片整理过程,内存整理的过程是无法并发的。-XX:CMSFullGCsBeforeCompaction,这个参数用于设置在执行多少次不压缩的Full GC后,跟着来一次带压缩的
 整个过程分4步:
 初始标记(CMS initial mark)
 并发标记(CMS concurrent mark)
 重新标记(CMS remark)
 并发清除(CMS concurrent sweep)

3.5.7 G1收集器
 在JDK1.7里已经实现商用,目标是在将来替换CMS收集器。
 特点:
 并行与并发
 分代收集
 空间整合
 可预测的停顿

3.5.8 理解GC日志
33.125: [GC [DefNew: 3324K->152K(3712K), 0.0025925 secs] 3324K->152K(11904K), 0.0031680 secs]
100.667: [Full GC [Tenured: 0K->210K(10240K), 0.0149142 secs] 4603K->210K(19456K), [Perm : 2999K->2999K(21248K)], 0.0150007 secs] [Times: user=0.01 sys=0.00, real=0.02 secs]

 33.125和100.667:GC发生的时间,从Java虚拟机启动以来经过的秒数。
 [GC 和 [Full GC: 垃圾收集的停顿类型,而不是用来区分新生代GC还是老年代GC的。如果有“Full”,说明这次GC是发生了Stop-The-World的,例如下面这段新生代收集器ParNew的日志也会出现“[Full GC”(这一般是因为出现了分配担保失败之类的问题,所以才导致STW)。如果是调用System.gc()方法所触发的收集,那么在这里将显示“[Full GC (System)”。
 接下来的“[DefNew”、“[Tenured”、“[Perm”表示GC发生的区域,这里显示的区域名称与使用的GC收集器是密切相关的
 [DefNew:Default New Generation,缺省新生代,[Tenured:年老代,[Perm:永久代
 [ParNew:Parallel New Generation,Parallel新生代
 [PSYoungGen:Parallel Scavenge收集器
 [Tenured:年老代
3.5.9 垃圾收集器参数总结
参数 描述
UseSerialGC 虚拟机运行在Client模式下的默认值,打开此开关后,使用Serial + Serial Old的收集器组合进行内存回收
UseParNewGC 打开此开关后,使用ParNew + Serial Old的收集器组合进行内存回收
UseConcMarkSweepGC 打开此开关后,使用ParNew + CMS + Serial Old的收集器组合进行内存回收。Serial Old收集器将作为CMS收集器出现Concurrent Mode Failure失败后的后备收集器使用
UseParallelGC 虚拟机运行在Server模式下的默认值,打开此开关后,使用Parallel Scavenge + Serial Old(PS MarkSweep)的收集器组合进行内存回收
UseParallelOldGC 打开此开关后,使用Parallel Scavenge + Parallel Old的收集器组合进行内存回收
SurvivorRatio 新生代中Eden区域与Survivor区域的容量比值,默认为8,代表Eden∶Survivor=8∶1
PretenureSizeThreshold 直接晋升到老年代的对象大小,设置这个参数后,大于这个参数的对象将直接在老年代分配
MaxTenuringThreshold 晋升到老年代的对象年龄。每个对象在坚持过一次Minor GC之后,年龄就增加1,当超过这个参数值时就进入老年代
UseAdaptiveSizePolicy 动态调整Java堆中各个区域的大小以及进入老年代的年龄
HandlePromotionFailure 是否允许分配担保失败,即老年代的剩余空间不足以应付新生代的整个Eden和Survivor区的所有对象都存活的极端情况
ParallelGCThreads 设置并行GC时进行内存回收的线程数
GCTimeRatio GC时间占总时间的比率,默认值为99,即允许1%的GC时间。仅在使用Parallel Scavenge收集器时生效
MaxGCPauseMillis 设置GC的最大停顿时间。仅在使用Parallel Scavenge收集器时生效
CMSInitiatingOccupancyFraction 设置CMS收集器在老年代空间被使用多少后触发垃圾收集。默认值为68%,仅在使用CMS收集器时生效
UseCMSCompactAtFullCollection 设置CMS收集器在完成垃圾收集后是否要进行一次内存碎片整理。仅在使用CMS收集器时生效
CMSFullGCsBeforeCompaction 设置CMS收集器在进行若干次垃圾收集后再启动一次内存碎片整理。仅在使用CMS收集器时生效
3.6 内存分配与回收策略
3.6.1 对象优先在Eden分配
 当Eden区没有足够空间进行分配时,虚拟机将发起一次Minor GC。
 -XX:+PrintGCDetails:虚拟机在发生垃圾收集行为时打印内存回收日志,并且在进程退出的时候输出当前的内存各区域分配情况
 Minor GC:新生代GC,指发生在新生代的垃圾收集动作,因为Java对象大多都具备朝生夕灭的特性,所以Minor GC非常频繁,一般回收速度也比较快。
 Major GC/Full GC:指发生在老年代的GC,出现了Major GC,经常会伴随至少一次的Minor GC(但非绝对的,在Parallel Scavenge收集器的收集策略里就有直接进行Major GC的策略选择过程)。Major GC的速度一般会比Minor GC慢10倍以上。
 -verbose:gc 表示输出虚拟机中GC的详细情况
3.6.2 大对象直接进入老年代
 -XX:PretenureSizeThreshold,令大于这个设置值的对象直接在老年代分配。
示例:代码清单3-4 大对象直接进入老年代

3.6.3 长期存活的对象将进入老年代
 虚拟机给每个对象定义了一个对象年龄(Age)计数器。如果对象在Eden出生并经过第一次Minor GC后仍然存活,并且能被Survivor容纳的话,将被移动到Survivor空间中,并且对象年龄设为1。对象在Survivor区中每“熬过”一次Minor GC,年龄就增加1岁,当它的年龄增加到一定程度(默认为15岁),就将会被晋升到老年代中。对象晋升老年代的年龄阈值,可以通过参数-XX:MaxTenuringThreshold设置。

示例:代码清单3-5 长期存活的对象进入老年代

3.6.4 动态对象年龄判定
 如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到MaxTenuringThreshold中要求的年龄。
3.6.5 空间分配担保
在发生Minor GC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,那么Minor GC可以确保是安全的。如果不成立,则虚拟机会查看HandlePromotionFailure设置值是否允许担保失败。如果允许,那么会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次Minor GC,尽管这次Minor GC是有风险的;如果小于,或者HandlePromotionFailure设置不允许冒险,那这时也要改为进行一次Full GC。

4 虚拟机性能监控与故障处理工具
4.1 概述
4.2 JDK的命令行工具
对于JDK1.5,添加参数“-Dcom.sun.management.jmxremote”开启JMX管理功能

常用的: jps, jstat, jinfo, jmap, jstack
4.2.1 jps: 虚拟机进程状况工具
jps: 可以列出正在运行的虚拟机进程(jvm ps)
格式:jsp [options] [hosted]
示例:jsp –l

4.2.2 jstat: 虚拟机统计信息监视工具
jstat: JVM Statistics Monitoring Tool,用于监视虚拟机各种运行状态信息的命令行工具。
命令格式:jstat [option vmid [interval[s|ms] [count]]]
本地虚拟机:VMID和LVMID一致
远程虚拟机:VMID格式:[protocol:][//]lvmid[@hostname[:port]/servername]
Interval:查询间隔
Count: 次数

示例: 每250毫秒查询一次进程2764垃圾收集状况,查询20次
jstat –gc 2764 250 20

4.2.3 jinfo: Java配置信息工具
jinfo: Configuration Info for Java,实时查看和调整虚拟机各项参数
建议:使用java -XX:+PrintFlagsFinal查看参数默认值
4.2.4 jmap: Java内存映像工具
 jmap: Memory Map for Java),生成堆转储快照dump文件
 -XX:+HeapDumpOnOutOfMemoryError
 -XX:+HeapDumpOnCtrlBreak,使用[Ctrl]+[Break]键生成dump文件
 格式:jmap [option] vmid

4.2.5 jhat: 虚拟机堆转储快照分析工具(不用)
这个太原始,一般不用

4.2.6 jstack: Java堆栈跟踪工具
 jsack(Stack Trace for Java)命令用于生成虚拟机当前时刻的线程快照
 建议利用java.lang.Thread的getAllStackTraces()方法来获取虚拟机中所有线程的StackTraceElement对象。

jstack [option] vmid

4.2.7 HSDIS: JIT生成代码反汇编
不怎么用
4.3 JDK的可视化工具
4.3.1 JConsole: Java监视与管理控制台
4.3.1.1 启动JConsole
4.3.1.2 内存监控
相当于可视化的jstat
4.3.1.3 线程监控
相当于可视化的jstack
4.3.2 VisualVM: 多合一故障处理工具
4.3.2.1 VisualVM兼容范围与插件安装
4.3.2.2 生成、浏览堆转储快照
4.3.2.3 分析程序性能
 生成环境下不要使用,有比较大的性能影响。
 可能有bug,最好被监控程序中使用-Xshare:off参数来关闭类共享优化。
4.3.2.4 BTrace动态日志跟踪
相关资料见:https://kenai.com/projects/btrace/pages/Home
5 调优案例分析与实战
5.1 概述
5.2 案例分析
5.2.1 高性能硬件上的程序部署策略
5.2.2 集群间同步导致的内存溢出
5.2.3 堆外内存导致的溢出错误
5.2.4 外部命令导致系统缓慢
5.2.5 服务器JVM进程崩溃
5.2.6 不恰当数据结构导致内存占用过大
5.2.7 由Windows虚拟内存导致的长时间停顿
5.3 实战:Eclipse运行速度调优
 1.5升级为1.6,这个过程有可能产生持久代内存溢出,需要调整eclipse.ini

 1.6的类加载时间过长,差不多是1.5的两倍,可以通过参数-Xverify:none来禁止字节码验证过程
 减少Full GC

 屏蔽System.gc(): -XX:+DisableExplicitGC
 选择收集器降低延迟:-XX:+UseConcMarkSweepGC –XX:+UseParNewGC

最终配置结果:

6 类文件结构
6.1 Class类文件的结构
Class文件格式:
ClassFile {
u4 magic;
u2 minor_version;
u2 major_version;
u2 constant_pool_count;
cp_info constant_pool[constant_pool_count-1];
u2 access_flags;
u2 this_class;
u2 super_class;
u2 interfaces_count;
u2 interfaces[interfaces_count];
u2 fields_count;
field_info fields[fields_count];
u2 methods_count;
method_info methods[methods_count];
u2 attributes_count;
attribute_info attributes[attributes_count];
}

6.1.1 魔数与Class文件的版本
1-4字节:0xCAFEBABE
5-6字节:次版本号
7-8字节:主版本号

6.1.2 常量池
 字面量(Literal):Java中的常量
 符号引用(Symbolic References):包括三类常量:
 类和接口的全限定名(Fully Qualified Name)
 字段的名称和描述符(Descriptor)
 方法的名称和描述符

常量池的每一项常量都是一个表,共有11种不同的表结构,表开始的第一位是一个u1类型的标志位

CONSTANT_Class_info型常量的结构:

name_index是一个索引值,它指向常量池中一个CONSTANT_Utf8_info类型的常量

CONSTANT_Utf8_info类型的结构:

使用javap查看字节码的内容:
javap –verbose TestClass

6.1.3 访问标志

6.1.4 类索引、父类索引与接口索引集合

6.1.5 字段表集合

name_index: 字段的简单名称;如:一个类的inc()方法和m字段的简单名称分别是“inc”和“m”
descriptor_index: 字段和方法的描述符;

对于数组类型:。。。
6.1.6 方法表集合

6.1.7 属性表集合

6.1.7.1 Code属性

 attribute_name_index: 对应固定值“Code”
 attribute_length: 属性值的长度,属性表长度-6
 max_stack: 操作数栈深度最大值
 max_locals: 局部变量表所需的存储空间,单位是Slot
 code:存储字节码指令的一系列字节流(字节码指令)

 exception_table:
结构:

6.1.7.2 Exceptions属性
Exceptions属性的作用是列举出方法中可能抛出的受查异常(Checked Exception)

6.1.7.3 LineNumberTable属性
用于描述Java源码行号与字节码行号之间的对应关系。

6.1.7.4 LocalVariableTable属性
用于描述栈帧中局部变量表中的变量与Java源码中定义的变量之间的关系。

name_index: 局部变量的名称
descriptor_index: 该具备变量的描述符

6.1.7.5 SourceFile属性
用于记录生成这个Class文件的源码文件名称。

6.1.7.6 ConstantValue属性

6.1.7.7 InnerClasses属性
用于记录内部类与宿主类之间的关联

6.1.7.8 Deprecated及Synthetic属性

其中,attribute_length数据项的值必须为0x00000000,因为没有任何属性值需要设置

6.2 Class文件结构的发展

7 虚拟机类加载机制
7.1 概述

7.2 类加载的时机

有且只有四种情况必须立即对类进行初始化:
 遇到new、getstatic、putstatic或invokestatic这4条字节码指令时
 使用java.lang.reflet包的方法对类进行反射调用的时候
 当初始化一个类的时候,发现其父类还没有初始化
 当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类

7.3 类加载的过程
全过程:加载、验证、准备、解析和初始化
7.3.1 加载
 通过一个类的全限定名来获取定义此类的二进制字节流
 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
 在Java堆中生成一个代表这个类的java.lang.Class对象,作为方法区这些数据的访问入口。

7.3.2 验证
连接阶段的第一步,目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
7.3.2.1 文件格式验证
验证字节流是否符合Class文件格式的规范,并且能被当前版本的虚拟机处理。
7.3.2.2 元数据验证
对字节码描述的信息进行语义分析,以保证其描述的信息符合Java语言规范的要求。
7.3.2.3 字节码验证
保证被校验类的方法在运行时不会做出危害虚拟机安全的行为。
7.3.2.4 符号引用验证
对类自身以外(常量池中的各种符号引用)的信息进行匹配性的校验。

7.3.3 准备
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中进行分配。

public static int value=123;

变量value在准备阶段过后的初始值为0而不是123。

7.3.4 解析
虚拟机将常量池内的符号引用替换为直接引用的过程。
解析动作主要针对类或接口、字段、类方法、接口方法四类符号引用进行,分别对应于常量池的CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info及CONSTANT_InterfaceMethodref_info四种常量类型。
7.3.4.1 类或接口的解析
把一个从未解析过的符号引用N解析为一个类或接口C的直接引用:

7.3.4.2 字段解析

7.3.4.3 类方法解析

7.3.4.4 接口方法解析

7.3.5 初始化
类加载过程的最后一步,是执行类构造器()方法的过程

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

7.4.1 类和类加载器

7.4.2 双亲委派模型
JVM角度:两种不同的类加载器:启动类加载器,其他的类加载器
开发人员角度:…

7.4.3 破坏双亲委派模型

8 虚拟机字节码执行引擎
8.1 概述

8.2 运行时栈帧结构
栈帧(Stack Frame)是用于支持虚拟机进行方法调用和方法执行的数据结构,是虚拟机运行时数据区中的虚拟机栈(Virtual Machine Stack)的栈元素。
个人理解:一个栈帧对应一个方法调用
概念:每个线程拥有自己独立的堆栈

8.2.1 局部变量表
局部变量表是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。

8.2.2 操作数栈
操作数栈的最大深度在编译时写入Code属性的max_stacks数据项中。
8.2.3 动态连接

8.2.4 方法返回地址

8.2.5 附加信息

8.3 方法调用
方法调用阶段唯一的任务就是确定被调用方法的版本(即调用哪一个方法),暂时还不涉及方法内部的具体运行过程。
8.3.1 解析
类加载阶段,会将其中一部分符号引用转化为直接引用

8.3.2 分派
对Java的多态特征,虚拟机是如何确定正确的目标方法的?
8.3.2.1 静态分派
Human man=new Man();
这里”Human”称为变量的静态类型(Static Type)或者外观类型,后面的”Man”则成为变量的实际类型(Actual Type)。
Java的重载分派基于静态类型进行分派。
8.3.2.2 动态分派
Java的重写分派是基于动态分派的。
注:重写那是可以添加注解@overirde的

8.3.2.3 单分派与多分派
根据分派基于多少种宗量,可以将分派划分为单分派和多分派两种。单分派是根据一个宗量对目标方法进行选择,多分派则是根据多于一个的宗量对目标方法进行选择。

8.3.2.4 虚拟机动态分派的实现

8.4 基于栈的字节码解释执行引擎
8.4.1 解释执行

8.4.2 基于栈的指令集与基于寄存器的指令集

8.4.3 基于栈的解释器执行过程

9 类加载及执行子系统的案例与实战
9.1 概述

9.2 案例分析
9.2.1 Tomcat: 正统的类加载器架构

9.2.2 OSGi: 灵活的类加载器架构

9.2.3 字节码生成技术与动态代理的实现

9.2.4 Retrotranslator: 跨越JDK版本
http://retrotranslator.sourceforge.net/

9.3 实战:自己动手实现远程执行功能
9.3.1 目标
9.3.2 思路

9.3.3 实现

10 早期(编译器)优化
10.1 概述
 前端编译器:Sun的Javac、Eclipse JDT中的增量式编译器(ECJ)
 JIT编译器:HotSpot VM的C1、C2编译器。
 AOT编译器:GNU Compiler for the Java(GCJ)、Excelsior JET
我们所说的编译期专指第一类编译过程。
10.2 Javac编译器
10.2.1 Javac的源码与调试
源码存放在(OpenJDK)JDK_SRC_HOME/langtools/src/share/classes/com/sun/tools/javac中

10.2.2 解析与填充符号表
解析步骤由图10-5中的parseFiles()方法(图中的过程1.1)完成,解析步骤包括了经典程序编译原理中的词法分析和语法分析两个过程。
10.2.2.1 词法、语法分析
词法分析:将源代码的字符流转变为标记(Token)集合。
语法分析:根据Token序列来构造抽象语法树的过程。
10.2.2.2 填充符号表
图10-5中的enterTrees()方法
10.2.3 注解处理器
插入式注解处理器的初始化过程是在initProcessAnnotations()方法中完成的,执行过程则在processAnnotations()方法中完成。
10.2.4 语义分析与字节码生成
10.2.4.1 标注检查
语义分析过程分为标注检查和数据及控制流分析两个步骤,分别由图10-5的attribute()和flow()方法(分别对应图中的过程3.1和过程3.2)完成。
10.2.4.2 数据及控制流分析

10.2.4.3 解语法糖

10.2.4.4 字节码生成

10.3 Java语法糖的味道
10.3.1 泛型与类型擦除

10.3.2 自动装箱、拆箱与遍历循环
注:包装类的“==”运算在没有遇到算术运算的情况下不会自动拆箱。

10.3.3 条件编译

10.4 实战:插入式注解处理器
10.4.1 实战目标
10.4.2 代码实现
11 晚期(运行期)优化
11.1 概述
11.2 HotSpot虚拟机内的即时编译器

11.2.1 解释器与编译器

11.2.2 编译对象与触发条件
方法调用计数器:

回边计数器

11.2.3 编译过程
Client Compiler

Server Compiler:

11.2.4 查看与分析即时编译结果

11.3 编译优化技术
以编译方式执行本地代码比解释方式更快。
11.3.1 优化技术概览
内联:许多优化手段都试图消除机器级跳转指令(例如,x86架构的JMP指令)。跳转指令会修改指令指针寄存器,因此而改变了执行流程。相比于其他汇编指令,跳转指令是一个代价高昂的指令,这也是为什么大多数优化手段会试图减少甚至是消除跳转指令。内联是一种家喻户晓而且好评如潮的优化手段,这是因为跳转指令代价高昂,而内联技术可以将经常调用的、具有不容入口地址的小方法整合到调用方法中。

11.3.2 公共子表达式消除

11.3.3 数组边界检查消除

11.3.4 方法内联

11.3.5 逃逸分析
如果能证明一个对象不会逃逸到方法或线程之外,也就是别的方法或线程无法通过任何途径访问到这个对象,则可能为这个变量进行一些高效的优化。
 栈上分配:
 同步消除:
 标量替换:

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

12 Java内存模型与线程
12.1 概述
TPS: Transactions Per Seconde,每秒事务处理数
12.2 硬件的效率与一致性

12.3 Java内存模型
12.3.1 主内存与工作内存

12.3.2 内存间交互操作
 八种基本操作:
lock
unlock
read
load
use
assign
store
write

 操作规则:

12.3.3 对于volatile型变量的特殊规则
volatile的两种特性:
 保证此变量对所有线程的可见性
 禁止指令重排序优化

12.3.4 对于long和double型变量的特殊规则
12.3.5 原则性、可见性和有序性
12.3.6 先行发生原则
 程序次序规则
 管理锁定规则
 Volatile变量规则
 线程启动规则
 线程中断规则
 对象终结规则
 传递性

12.4 Java与线程
12.4.1 线程的实现
12.4.1.1 使用内核线程实现
LWP:Light Weight Process,轻量级进程
KLT:Kernal Thread,内核线程

12.4.1.2 使用用户线程实现
UT: User Thread 用户线程

12.4.1.3 混合实现

12.4.1.4 Java线程的实现

12.4.2 Java线程调度
线程调度是指系统为线程分配处理器使用权的过程,主要调度方式有两种:协同式线程调度和抢占式线程调度。

12.4.3 状态转换

13 线程安全与锁优化
13.1 概述

13.2 线程安全
13.2.1 Java语言中的线程安全
13.2.1.1 不可变
13.2.1.2 绝对线程安全
13.2.1.3 相对线程安全
13.2.1.4 线程兼容
13.2.1.5 线程对立
13.2.2 线程安全的实现方法
13.2.2.1 互斥同步

13.2.2.2 非阻塞同步
通过硬件来保证一个语义上看起来需要多次操作的行为只通过一条处理器指令就能完成,常用的有:
 测试并设置(Test-and-Set)
 获取并增加(Fetch-an-Increment)
 交换(Swap)
 比较并交换(Compare-and-Swap,下文称CAS)
 加载链接/条件储存(Load-Linked/Store-Conditional,下文称LL/SC)
13.2.2.3 无同步方案
 可重入代码
 线程本地存储
13.3 锁优化
13.3.1 自旋锁与自适应自旋
13.3.2 锁消除
13.3.3 锁粗化
13.3.4 轻量级锁
HotSpot虚拟机的对象头(Object Header)分为两部分信息
 第一部分用于存储对象自身的运行时数据,如HashCode,GC分代年龄等,官方称为“Mark Word”。
 另外一部分用于存储指向方法区对象类型数据的指针,如果是数组对象的话,还会有一个额外的部分用于存储数组长度。
Mark Word被设计成一个非固定的数据结构以便在极小的空间内存储尽量多的信息。

13.3.5 偏向锁

14 附录
14.1 HotSpot虚拟机主要参数表
参数使用的方式有三种:
 -XX:+ 开启option参数
 -XX:- 关闭option参数
 -XX:= 将option参数的值设置为value
14.1.1 内存管理参数
14.2 对象查询语言(OQL)简介

15 其它
15.1 关于内存分配
32位windows有4G的寻址空间,有2G需要留给windows系统用,留给应用的只有2G。
32位Linux内存问题:参考:http://www.iteye.com/topic/368213

15.2 其它
可以直接生成本地代码的编译器(如GCJ,GNU compiler for the Java)
一个问题:使用wait,线程数会变少吗?
Synchronized与读锁比的性能呢?
如何监控虚拟机栈?
可以研究类的垃圾收集算法
如何默认client模式还是server模式? Java –version即可以查询到
Full GC?
如何测试client模式和server模式下色方法调用性能?
测试各种GC的性能问题
我们的通信平台到底是需要吞吐量优先还是低停顿优先?
需要熟悉VisualVM
需要学习BTrace:https://kenai.com/projects/btrace/pages/Home
针对5.2.6 不恰当数据结构导致内存占用过大,进行测试

相关参考资料:
http://taojingrui.iteye.com/blog/548828
http://blog.csdn.net/e5945/article/details/8641698
http://docs.oracle.com/javase/1.5.0/docs/guide/vm/gc-ergonomics.html
http://book.2cto.com/201306/25426.html

你可能感兴趣的:(java虚拟机,java虚拟机)