第二章 Java内存区域与内存溢出异常
2.2 运行时数据区域
2.2.1 程序计数器
如果线程正在执行一个Java方法,计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是本地(Native)方法,这个计数器值是null。
2.2.2 Java虚拟机栈
每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧,存储局部变量表、操作数栈、动态链接、方法出口
2.2.3 本地方法栈
2.2.4 Java堆
存对象实例
2.2.5 方法区
存储被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据
2.2.6 运行时常量池
方法区的一部分,存放编译期生成的各种字面量与符号引用
2.2.7 直接内存
在jdk1.4引入了NIO类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,他通过Native函数库直接分配堆外内存,使用存储再Java堆里的DirectByteBuffer对象最为这块内存的引用
2.3 HotSpot对象揭秘
2.3.1 对象的创建
内存分配方式:指针碰撞、空闲列表(维护一个列表,记录空闲内存和使用过的内存)
选择哪种分配方式由内存是否规整决定,内存是否规整,由垃圾收集器是否带有空间压缩整理的能力决定
虚拟机如何保证操作的原子性?
采用CAS配上失败重试的方式保证更新操作的原子性;
2.3.2 对象的内存布局
在HotSpot里,对象在堆里的存储布局分为三部分:对象头、实例数据、对齐填充
2.3.3 对象的访问定位
句柄、直接指针
第三章 垃圾收集器与内存分配策略
3.2 对象已死?
3.2.1 引用计数法
给对象加一个引用计数器,为0时表示对象死亡,但解决不了互相引用的问题
3.2.2 可达性分析
3.2.3 再谈引用
强引用:只要存在强引用,对象就不会被回收,Object obj = new Object();
软引用:有用但非必须,如果内存不足,会被二次回收;
弱引用:只能生存到下一次GC,无论内存是否足够,都会被回收;
虚引用:没什么用,只是在被GC的时候收到一个系统通知。
3.2.4 生存还是死亡?
在可达性分析认为对象不可达之后,还需要经历两次标记:
在对象被认为没有与GC Roots相连之后,被第一次标记,随后进行一次筛选,条件是对象是否有必要执行finalize()方法,如果对象没有覆盖finalize()方法或finalize()方法已经被调用过,就被认为是没必要执行;
对象被判定为有必要执行finalize()方法之后,被放到F-Queue队列,之后由线程执行,执行finalize()方法时会进行二次标记,如果此时重新连接,那么二次标记时会把对象从队列移出,否则被回收。
3.2.5 回收方法区
废弃的常量和不再使用的类型
3.3 垃圾收集算法
3.3.1 分代收集理论
新生代收集(Minor GC/Young GC)
老年代收集(Major GC/Old GC)
3.3.2 标记-清除算法
标记需要回收的对象并回收。
两个缺点:1.数据量大时标记和清除的效率很低;2.会产生内存碎片
3.3.3 标记-复制算法(一般用于新生代垃圾收集)
把内存均分为两半,每次只使用其中一块,回收一次后,把对象复制到另一块内存,然后清理这一块内存。
缺点:空间浪费
优化:把内存分为一个Eden区和两个Survivor区,比例是Eden区占8,两个Survivor分别占1,每次只使用Eden和其中一个Survivor,每次回收时把Eden和Survivor里存活的对象复制到空闲的Survivor里,然后清理Eden和使用过的Survivor。
3.3.4 标记-整理算法(一般用于老年代垃圾收集)
清除掉不可用的对象后,把存活对象向内存空间的一侧移动。
缺点:移动时,必须暂停用户应用程序才能进行,被称为“Stop The World”。
3.5 经典垃圾收集器
1. Serial收集器
曾经是虚拟机新生代收集器的唯一选择(目前仍然是hotspot运行在客户端模式下的默认的新生代收集器);
单线程工作的收集器;
Stop The World,进行垃圾收集时,必须停止其他所有工作线程;
2.ParNew收集器
Serial收集器的多线程版本;
JDK7之前系统首选的新生代收集器;
在JDK5发布时,hotspot发布了老年代收集器CMS,CMS只能搭配Serial或ParNew使用,这提高了ParNew的地位;
推出G1之后,ParNew+CMS退出历史舞台;
3.Parallel Scavenge收集器
新生代收集器,标记-复制,并行收集的多线程收集器;
关注点和其他收集器不同,其他收集器关注减少垃圾收集时用户线程的停顿时间,本收集器关注吞吐量,
吞吐量=运行用户代码时间/(运行用户代码时间+运行垃圾收集时间)
4.Serial Old收集器
Serial的老年代版本,单线程,标记-整理
作用:配合Parallel Scavenge使用;CMS失败的备用预案
5.Parallel Old收集器
Parallel Scanenge的老年代版本,并发,标记-整理
6.CMS收集器
标记-清除;
四个步骤:初始标记、并发标记、重新标记、并发清除
其中初始标记和重新标记仍然需要Stop The World,但时间比较短。这四个步骤就是可达性分析后的两次标记过程
优点:并发收集(两次STW时间都很短,近似并发),低停顿
缺点:对处理器资源敏感(处理器少于4个的话,占用资源很大);
无法处理浮动垃圾(标记之后,程序正常运行产生的垃圾),可能会触发Full GC;
标记-清除会产生内存碎片,从而触发Full GC;
7.Garbage First收集器(G1)
JDK9发布时,G1宣布取代Parallel Scavenge+Parallel Old的收集器组合,成为服务器模式下的默认垃圾收集器;
G1跳出了分代收集的牢笼,虽然仍然遵循分代收集的方式,但不再坚持固定大小和固定数量的区域,而是把Java堆分为多个相同大小的Region,每个region都可以根据需要来扮演Eden、Survivor、老年代的角色
处理器让G1去跟踪每个Region里的垃圾堆的价值大小,价值就是垃圾收集获得的空间大小以及停顿时间,然后在后台维护一个优先级列表,每次根据用户设定的停顿时间,优先处理价值最大的Region,这就是Garbage First名字的由来。
步骤:初始标记、并发标记、最终标记、筛选回收
除了并发标记,其他都需要Stop The World。
G1仍然存在的问题:
Q:跨Region引用对象如何解决?
A:目前使用记忆集,但要耗费比较多的资源
Q:并发问题:
A:原始快照
Q:如何建立可靠的停顿预测模型?
A:记录各种值,然后分析
第四章 虚拟机性能监控、故障处理工具
4.2 基础故障处理工具
4.2.1 jps:虚拟机进程状况工具
jps [options] [hostid]
4.2.2 jstat:虚拟机统计信息监视工具
显示本地或远程虚拟机进程中的类加载、内存、垃圾收集、即时编译等运行数据
主要使用:jstat -gc [hostid] 监视Java堆状况
4.2.3 jinfo:Java配置信息工具
jinfo作用是实时查看和调整虚拟机参数
jinfo [option] pid
jinfo -v pid 显式指定的参数列表
jinfo -flag pid 默认的参数值
4.2.4 jmap:Java内存映像工具
生成堆转储快照
4.2.5 jhat:虚拟机堆转储快照分析工具
搭配jmap使用
4.2.6 jstack:Java堆栈跟踪工具
生成虚拟机当前时刻的线程快照,目的是定位线程出现长时间停顿的而原因,如线程间死锁、死循环
4.3 可视化故障处理工具
JConsole、JHSDB、VisualVM、JMC
第五章 调优案例分析与实战