深入理解Java虚拟机
Chapter1:走进Java
1.1 概述
1.2 Java体系
- java Card: 支持一些java小程序运行在小内存设备的平台
- java ME(Micro Edition):支持java在移动端的平台
- java SE(standard edition):支持桌面级应用的平台,提供了完整的java核心api
- java EE(enterprise edition):支持多层框架的企业级应用
1.3 Java发展史
- oak
- java
1.4 Java虚拟机发展史
- Sun Classic/Exact VM
- Sun HotSpot VM
- Sun Mobile-Embedded VM/Meta-Circular VM
- BEA JRockit/IBM J9 VM
- Azul VM/BEA Liquid VM
- Apache Harmony/Google Android Dalvik VM
- Microsoft JVM
1.5 展望Java技术的未来
- 模块化
- 混合语言
- 多核并行
- 进一步丰富的语法
- 64位虚拟机
1.6 实现自己的JDK
Chapter2:Java内存区域与内存溢出异常
2.1 概述
2.2 运行时区域
- 程序技术器 (Program Counter Register)
- 当前线程所执行的字节码的行号指示器
- Java虚拟机栈(java virtual machine stacks)
- 线程私有,生命周期同线程
- 每个方法在执行的同时都会创建一个栈帧(stack frame)用于存储局部变量、操作数栈、动态链接、方法出口等信息
- 本地方法栈(Native Method Stack)
- 虚拟机使用到的Native 方法
- Java 堆(Heap)
- 为各个线程共享的内存区域
- Young:Eden + S0 + S1
- Old
- Thread Local Allocation Buffer (TLAB)
- 方法区:
- 为各个线程共享的内存区域
- 存储虚拟机加载的类信息、常量、静态变量、即时编译器编译后的大麦
- 老年代(Pemanent Genration)
- java8之后改为MetaSpace
- 运行时常量池:runtime constant Pool
- 方法区的一部分
- 直接内存:Direct Memory
- 不受GC控制
2.3 HotSpot 虚拟机对象探秘
- 对象的创建
- 分配新内存
- 指针碰撞(Bump the Point):在Seial、ParNew等带Compact过程的收集器
- 空闲列表(Free List):使用CMS这种基于Mark-Sweep算法搜集器
- 多个线程同时申请内存时:
- 同步处理:CAS
- 将内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在Java堆中预先分配一小块内存 TLAB
- -XX:+/-UseTLAB
- 设置对象头Object Header
- 类的元数据信息、对象的hashcode、对象的GC分代年龄
- 分配新内存
- 对象的内存布局
- 对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)
- 对象头
- 两部分组成
- Mark Word: hashcode、gc分代年龄、所状态标志、线程持有的锁、偏向线程ID、偏向时间戳等
- 类型指针:指向类的元数据信息
- 两部分组成
- 对象的访问定位
- 句柄访问
- reference中存储的是稳定的句柄地址,对象被移动时知会改变句柄中的实例数据指针
- 直接指针
- 速度更快, Sun HotSpot使用的是这种
- 句柄访问
2.4 实战:OutOfMemoryError异常
- java堆溢出
- -Xms -Xmx设置为一样,则jvm不用再进行自动扩展
- 虚拟机和本地方发栈溢出
- -Xss设置栈的内存容量
- 方法区和运行时常量池溢出
- String.intern()
- CGLIB 创建更多的类
- -XXPermSize -XX:MaxPermSize
- 本机直接内存溢出
- -XX:MaxDirectMemorySize
Chapter3:垃圾收集器与内存分配策略
3.1 概述
- Lisp
- GC三个事情
- 哪些内存需要回收
- 什么时候回收
- 如何回收
3.2 对象已死吗
- 引用计数法(Reference Counting)
- 循环引用的问题
- 可发性分析方法(Reachability Analysis)
- GC ROOT
- 虚拟栈(栈帧中的本地变量表)中引用的对象
- 方法区中类静态变量引用的对象
- 方法区中常量引用的对象
- 本地方法栈中JNI引用的对象
- GC ROOT
- 再谈引用
- 有强到弱的引用类型
- 强引用(Strong Reference)
- Object obj = new Object();只要引用在就不会搜集
- 软引用(Soft Reference)
- 描述一些还有用但是非必需的对象,对于此类引用关联着的对象, 在系统将要发生内存溢出异常之前,将会吧这些对象列进回收范围内,进行二次回收。
- 弱引用(Weak Reference)
- 也是用来描述非必需对象,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾搜集发生之前
- 虚引用(Phantom Reference)
- 也称幽灵引用或者幻影引用,最弱的一种引用
- 为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时候收到一个系统通知
- 强引用(Strong Reference)
- 有强到弱的引用类型
- 生存还是死亡
- finalize()方法,该方法只会被调用一次,对象可以再此处逃生
- 回收方法区
3.3 垃圾收集器算法
- 标记清除(Mark-Sweep)
- 问题:效率不高 + 内存碎片
- 复制算法(Copying)
- serial
- 标记整理算法(Mark-Compact)
- 分代搜集算法(Generational Collection)
3.4 HotSpot的算法实现
- 枚举根节点
- STW
- 使用一组称为OopMap的数据结构来存储对象的这些信息
- 安全点 Safepoint
- 抢先式中断(Preemptive Suspension):首先中断所有线程,如果有线程未达安全点,则使其run到安全点
- 主动式中断(Voluntary Supension):简单设置一个变量,各个线程主动轮询这个变量,如设置则主动中断。
- 安全区域
- 如果线程处于sleep或者blocked状态
3.5 垃圾收集器
- Serial收集器
- 单个cpu、单个线程手机
- ParNew收集器
- serial的多线程版本
- Parallel Scavenge收集器(吞吐量优先收集器)
- 目标是达标一个可控制的吞吐量
- 吞吐量= 运用用户代码时间/ (运行用户代码时间+垃圾搜集时间)
- -XX:MaxGCPauseMills:最大垃圾收集器停顿时间
- -XX:GCTimeRatio:吞吐量大小
- 目标是达标一个可控制的吞吐量
- SerialOld收集器
- 单线程
- ParllelOld收集器
- 多线程并发
- CMS收集器(Concurrent Mark Sweep)
- 分为四个阶段
- 初始标记(有STW)
- 仅标记GC ROOTS能直接关联到的对象
- 并发标记
- 重新标记(有STW)
- 并发清除
- 初始标记(有STW)
- 缺点
- 对cpu资源敏感
- 无法处理浮动垃圾(floating Gabage)
- 在并发清除阶段,新的垃圾不能被清楚掉
- -XX:CMSInitiatingOccpancyFraction来设置触发
- 有可能导致SerialOld
- 垃圾碎片
- 可设置fullgc和压缩fullgc的频率
- 分为四个阶段
- G1收集器
- 并行与并发
- 充分利用多核多cpu的硬件优势,使用多个cpu来缩短STW的停顿时间,
- 分代搜集
- 空间整合
- 从整体上来看是基于标记整理,局部上上看是复制算法
- 可预测的停顿
- 降低提顿时间是G1和CMS共同的关注点,但是G1除了最求地停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定一个长度为M毫秒的时间片内,消耗在垃圾收集上的时间不得操作N毫秒
- G1跟踪各个Region里面的垃圾堆积的价值大小(回收锁获得的空间所需时间的经验值),在后台维护一个优先列表,每次根据允许的手机时间,优先回收价值最大的Region。
- region 不是孤立的
- region之间的对象引用以及其他收集器中的新生代与老年代之间的对象引用,虚拟机都是使用Remembered Set来避免全局扫描的。
- G1的四个步骤
- 初始标记
- 并发标记
- 最终标记
- 筛选回收
- 并行与并发
- 理解GC日志
- 内存分配与回收策略
- 对象主要分配在新生代的Eden区上,如果启动了本地线程分配缓冲,将按线程优先在TLAB上分配,少数情况直接分配到老年代中,分配的规则不是100%的,其细节取决于当前使用的是哪一种垃圾回收组合,还有虚拟机中与内存相关的参数的设置。
- 对象优先分配在Eden区
- 当eden区不够分配的时候,发起MinorGC
- 大对象直接接入老年代
- 很长的字符串以及数组
- -XX:PretenureSizeThreshold, 令大于这个值的对象直接分配到老年代
- 长期存活的对象将进入老年代
- -XX:MaxTenuringThreshold
- 到达年龄的对象将会进入老年代
- 动态对象年龄判定
- 如果再Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一般,年龄大于或者都能与该年龄的对象就可以直接进入老年代,无须等到MaxTenuringThreshold中啊哟球的年龄。
- 空间分配担保
- 在发生MinorGC之前, 虚拟机会先检查老年代对大连续可用空间是否大于新生代所有对象总空间,如果这个条件成立,那么MinorGC就可以确保是安全的。
- 如果不成立,则虚拟机会查看HandlePromotionFailure设置值是否允许担保失败,如果允许,那么会继续检查老年代最大连续可用空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次MinorGC,尽管这次MinorGC是有风险的;如果小于,或者HandlerPromotionFailure设置不允许冒险,那么这是也要修改进行一次FullGC。
Chapter 4:虚拟机性能监控与故障处理工具
4.1 概述
4.2 JDK命令行工具
- jps
- jps -mlvV, 查看指定系统内所有的HotSpot虚拟机进程
- jstat
- jstat -gcutil vmid:查看gc状态
- jinfo
- java 配置信息工具,可以查看也可以设置
- jmap
- 生成对转出快照、查询finalize执行对垒,java堆和永久代的详细信息:如空间使用率和当前使用的是那种收集器
- jhat
- 虚拟机堆转出快照分析工具
- jstack
- java堆占跟踪工具
- HSDIS:JIT生成代码反汇编
4.3 JDK可视化工具
- JConsole:java简史与管理控制台
- VisualVM
- BTrace日志动态跟踪
4.4 查找死循环
- Linux : top -H 找出busy的线程ID,转换为16进制
- jstack 打印线程stack,找到对应的ID,分析是哪个func在死循环
Chapter 5: 调优案例分析与实战
5.1 概述
5.2 案例分析
- 高性能硬件上的程序部署策略
- 通过64位JDK来使用大内存
- 使用若干个32位虚拟机简历逻辑集群来利用硬件资源
- 集群键同步导致的内存溢出
- 堆外内存导致的溢出操作
- 外部命令导致系统缓慢
- 服务器JVM进程崩溃
- 不恰当的数据结构导致内存占用过大
- 由windows虚拟内存导致的长时间停顿
5.3 实战:Eclipse运行速度调优
- 调优前的程序运行状态
- 升级JDK1.6的性能变化及兼容问题
- 编译时间和类加载时间的优化
- 调整内存设置控制垃圾收集频率
- 选择收集器降低延迟
Chapter 6:类文件结构
6.1 概述
6.2 无关性的基石
- 字节码 (class文件)
- 虚拟机
6.3 class类文件的结构
- magic Number与class文件的版本
- 常量池
- 访问标志
- 类索引、父类索引与接口索引集合
- 字段表结合
- 方法表集合
- 属性表集合
6.4 字节码指令简介
- 字节码与数据类型
- 加载和存储指令
- 运算指令
- 类型转换指令
- 对象创建与访问指令
- 操作数找管理指令
- 控制转移指令
- 方法调用和返回指令
- 异常处理指令
- 同步指令
- 方法同步
- 隐式的,无须通过字节码指令来控制,它的实现在方法调用和返回操作数中。
- 虚拟机可以从方法常量池的方法表结构中ACC_SYNCHRONIZED访问标志得知一个方法是否声明为同步方法;当方法调用时将会检查方法的ACC_SYNCHRONIZED访问标志是否被设置,如果设置了, 执行线程就要求先成功持有管程,然后才能执行,最后当方法完成(无论成功与否)时释放管程,
- 方法内部一段指令的同步
- 显式的
- 字节码中会有monitorenter和monitorexit
- 方法同步
6.5 共有设计和私有实现
6.6 Class文件结构的发展
Chapter 7:虚拟机类加载机制
7.1 概述
7.2 类加载机制
- 生命周期
- 加载、验证、准备、解析、初始化、使用和卸载
- 5种情况必须立即对类进行初始化
- 遇到new、getstatic、putstatic、或invokestatic
- 使用java.lang.reflect
- 初始化一个类的时候,如果发现其弗雷还没有进行过初始化,则需要先触发器父类的初始化
- 当虚拟机启动时,用户需要制定一个执行的主类,虚拟机会先初始化这个类
- 使用JDK1.7的动态语言支持时, 如果一个java.lang.invoke.Methodahndle最后的解析结构REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个句柄所对应的类没有进行过初始化,则需要先触发其初始化
7.3 类加载的过程
- 加载
- 通过一个类的全限定名来获取定义此类的二进制流
- 将这个字节流所代表的静态存储结构转化为方法区的运行时结构
- 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据访问接口
- 验证
- 文件格式验证
- 元数据验证
- 字节码验证
- 符号引用验证
- 准备
- 解析
- 初始化
7.4 类加载器
- 类与类加载器
- 判断类是否相同,也需要考虑到类加载器
- 双亲委派模式
- 两种类加载器
- 启动类加载器(Bootstrap ClassLoader):c++实现
- 其他类加载器:由java实现,并且全部集成java.lang.ClassLoader
- 从开发人员角度看
- 启动类加载器
- 扩展类加载器(Extension ClassLoader)
- sun.misc.Launcher$ExtClassLoader
- 应用程序类加载器(Application ClassLoader)
- sun.misc.Launcher$AppClassLoader
- 工作过程
- 如果一个类加载器收到了类加载请求,它首先不会自己去尝试加载这个类,而是吧这个请求为派给自己的弗雷加载器去完成, 每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动了器加载器中,只有当父类加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时, 子类加载器才会尝试自己去加载。
- 两种类加载器
- 破坏双亲委派模型
- 线程上下文加载器
- OSGi
- 将以java.* 开头的类为派给父类加载器加载
- 否则,将类委派列表名单内的类为派给父类加载器
- 否则,将Import列表中的类为派给Export这个类的Bundle的类加载器加载
- 否则,查找当前Bundle的classpath,使用自己的类加载器
- 否则,查找类是否在自己的fragment Bundle中,如果在,则委派给Fragment Bundle的类加载器
- 否则,查找Dynamci IMport 列表中的Bundle,为派给相应的类加载器
- 否则,查找失败
Chapter 8:虚拟机字节码执行引擎
8.1 概述
- 执行引擎
- 解释执行(通过解释器执行)
- 编译执行(通过及时编译产生本地代码)
8.2 运行时栈帧结构
- 栈帧是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区中的虚拟机栈的栈元素。
- 栈帧存储了局部变量表、操作数栈、都昂太链接和方法返回地址等信息。
- 局部变量表
- local variable table
- 操作数栈(operand statck)
- 动态连接
- 方法返回地址
- 附加信息
8.3 方法调用
- 解析
- invokestatic:调用静态方法
- invokespecial:调用实例构造器、私有和父类方法
- invokevirtual:调用所有的虚方法
- invokeinterface:调用接口方法,会在运行时确定一个实现此接口的对象
- invokedynamic:先在运行时动态解析出调用点限定符所引用的方法,然后再执行该方法, 在此之前的4条调用指令,分派逻辑是固化在jvm内部的, 而该命令的分派逻辑是有用户所设定的引导方法指定的。
- 分派
- 静态分派
- 动态分派
- 但分派与多分派
- 虚拟机动态分派的实现
- 动态类型语言支持
- 动态语言类型
- JDk1.7 与动态类型
- java.lang.invoke 包
- invokedynamic指令
- 掌握方法分派规则
8.4 基于栈的字节码解释执行引擎
- 解释执行
- 基于栈的指令集合与基于寄存器的指令集合
- 基于栈的解释执行过程
Chapter9: 类加载及执行子系统的案例与实战
9.1 概述
9.2 案例分析
- Tomcat:正统的类加载器架构
- OSGi:灵活的类加载器
- 字节码生成技术与动态代理的实现
- Retrotranslator:跨越JDK版本
9.3 实战:自己动手实现远程执行g功能
Chapter10:早起(编译期)优化
10.1 概述
10.2 javac编译器
- javac的源码与调试
- 解析与填充符号表
- 注解处理器
- 语义分析与字节码生成
10.3 java语法糖的味道
- 泛型与类型擦除
- 自动装箱】拆箱与遍历循环
- 条件编译
10.4 实战:插入式注解处理器
- 目标
- 实现
- 运行与测试
- 其他案例
Chapter11:晚期(运行期)优化
11.1 概述
- JIT : 热点代码
11.2 HotSpot虚拟机内的及时编译器
- 解释器和编译器
- 编译对象和出发条件
- 被多次调用的方法
- 被多次执行的循环体
- 编译过程
- 查看以及分析即时编译结果
11.3 编译优化技术
- 编译优化技术概览
- 公共字表达式的消除
- 数组边界检查消除
- 方法内联
- 逃逸分析
- 如果一个对象被分析出不会逃逸,可做如下优化
- 栈上分配
- 同步消除
- 标量替换
- 如果一个对象被分析出不会逃逸,可做如下优化
11.4 Java与C/C++的编译器对比
Chapter12: Java内存模型与线程
12.1 概述
- TPS: Traction Per Second
12.2 硬件的效率与一致性
- 高速缓存 + MESI协议(缓存一致性协议)
- 处理器 <==> 高速缓存 <==> 缓存一致性协议 <==> 主内存
12.3 Java内存模型
- Java Memory Model
- JSR 133
- 主内存与工作内存
- java线程 <==> 工作内存 <==> save & load操作 <==> 主内存
- 内存间交互操作
- 对于volatile型变量的特殊规则
12.4 Java与线程
- 线程的实现
- 线程的实现方式
- 使用内核实现(内核线程 Kernel-Level Thread KLT): LWP <1===1> KLT
- 使用用户线程实现 UT <N===1> P
- 使用用户线程 + 轻量级进程混合 UT <N===M> LWP <1===1> KLT
- java线程的实现: LWP <1===1> KLT
- 线程的实现方式
- Java线程的调度
- 协同式线程调度(Cooperative Threads-Scheduling)
- 好处是实现简单, 缺点是:线程执行时间不可控制
- 抢占式线程调度(Preemptive Threads-Scheduling)
- 系统自调度
- java 使用
- 协同式线程调度(Cooperative Threads-Scheduling)
- 状态转换
- new
- runnable
- wating
- timed wating
- blocked
- teminated
Chapter 13: 线程安全与锁优化
- 概述
- 面向对象, 线程交互
- 线程安全
- 当多个线程访问一个对象时,如果不用考虑这些线程在运行时下的调度和交替执行, 也不需要进行额外的同步不,或者在调用方进行任何其他的协调操作,调用对象的欣慰都可以获得正确的结果,那这个对象是线程啊那全的。
- java语言中的线程安全
- 不可变:final
- 绝对线程安全:完全同步
- 相对线程安全
- 线程兼容
- 线程队里
- 线程安全的实现方法
- 互斥同步:Synchroniz Lock
- 非阻塞同步:CAS
- 无同步方案
- 可重入代码
- 线程本地存储
- 锁优化
- 自选所与自适应自旋
- 锁消除
- 锁粗化
- 轻量级锁
- 偏向锁