深入理解Java虚拟机 - 读书笔记
Posted xiaolongtuan
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入理解Java虚拟机 - 读书笔记相关的知识,希望对你有一定的参考价值。
第1章 走近Java
- Java程序设计语言、Java虚拟机、JavaAPI类库统称为JDK。
- Java技术体系分为4个平台:
- Java Card:Applets运行于小内存设备上的平台,如智能卡
- Java ME:移动终端,对API有所精简
- Java SE:面向桌面级应用,有完整核心Java API
- Java EE:支持使用多层架构的企业应用(如ERP、CRM等)的Java平台,提供的大量的扩充并提供了相关的部署支持(
javax.*
作为包名,也有部分后来加入核心Java API中)。
- Java发展历史:
- HotSpot虚拟机在1.2版本出现,JDK1.3之后作为所有SunJDK的默认虚拟机。
- JDK1.5版本提供了大量的语法特性(自动装箱、泛型、动态注解、枚举、可变参数等等),大大提升易用性,并且提供了
java.util.concurrent
并发包。 - JDK 7开源出OpenJDK
- JDK 8加入Lambda表达式等等
- Java虚拟机发展史:
- Sun Classic/Exact VM:Classic VM是第一款商用JVM,在1.4版本退出历史舞台,而Exact甚至没有完整发布版本即被HotSpot取代。
- Sun HotSpot VM:收购原来开发HotSpot的公司之后开发的虚拟机,1.3版本作为SunJDK及OpenJDK的默认虚拟机。
- JDK 8的HotSpot VM:2009年Oracle收购BEA与Sun,将JRockit与HotSpot融合而成新的HotSpot,即传说中的HotRockit。
- 展望Java
- 模块化:Java已经越来越庞大,OSGi等出现就是为了将其模块化,从而制定标准
- 混合语言:Clojure、JRuby、Groovy等,各个语言有其独特优势,如果能统一运行在一个虚拟机上,相互之间透明,将大幅提升其可用性。
- 多核并行:concurrent包以及后来的forkjoin子包、还有适用于函数式编程的Lambda表达式(函数式编程天然适合于并行运行),另外兼顾GPU的一些项目,以及Hadoop、Map/Reduce等。
- 更多语言特性
- 64位虚拟机
第2章 Java内存区域与内存溢出异常
运行时数据区:
- 程序计数器:可看作当前线程锁执行字节码的行号指示器
线程隔离
无OOME异常(OutOfMemoryError) - 虚拟机栈:方法调用其栈帧保存区域,而栈帧包含局部变量表、操作数栈、动态链接等
线程隔离
每一个方法调用从调用到执行完成就是一个栈帧入栈到出栈的过程。
StackOverFlow异常:线程请求栈深度过深
OutOfMemoryError异常:栈扩展时如果无法申请到足够内存时则抛出 - 本地方法栈:JNI服务使用的栈,与虚拟机栈作用相似,只是作用对象不同,本地方法栈作用于本地方法(如调用C/C++的方法)
线程隔离 - Java堆:存放对象实例,几乎所有对象实例都在这儿分配内存
线程共享
垃圾回收器的主要管理区域,也称为GC堆,可细分为:新生代/老年代(默认比例1:2),或进一步细分为Eden空间、From Survivor空间、To Survivor空间(8:1:1)等。
OOME异常:给新对象分配内存空间不够时 - 方法区:用于存储一杯虚拟机加载的类信息、常量、静态常量、JIT编译后的代码数据等
线程共享
运行时常量池所在,也有人称之为“永久代”
OOME异常:其本身或其常量池都有可能抛出
此外,不属于虚拟机的所谓“直接内存”(Direct Memory,调用本地函数库直接分配的堆外内存)也有可能抛出OOME异常。
- 程序计数器:可看作当前线程锁执行字节码的行号指示器
- HotSpot虚拟机对象探秘
- 对象的创建:
- 类加载检查:检查该对象的类是否已经被加载、解析、初始化过,如果没有则先进行类加载操作。
- 分配内存:如果内存规整使用“指针碰撞”分配,否则一般使用“空闲列表”分配,具体看垃圾回收器是否带有整理(Compact)空闲内存功能。
- 初始化:将内存区初始化置零,若设置了TLAB
- 对象头设置:这个对象是哪个类的实例、如何找到类的元数据信息、哈希码、GC分代年龄信息等即为对象头
- 对象的方法:即按照程序员的意愿进行初始化
- 对象的内存布局:
- 对象头:
一部分称为Mark Word,包含哈希码、GC分代年龄、锁状态标志等等,采用压缩存储,压缩到虚拟机位数(32位/64位)
另一部分为类型指针,指向它的类元数据,用于对象的访问定位,非必需,看虚拟机的具体实现。 - 实例数据:具体其成员字段的内容,包含父类(一般靠前)及自己的。
- 对齐填充:非必需,只有前两者加起来非8的倍数时才会有。
- 对象头:
- 对象的访问定位,主流的有两种(具体取决于虚拟机的实现):
- 通过句柄访问对象:当java虚拟机GC移动堆对象时,并不需要修改reference,只需修改句柄对象的实例数据指针。
- 通过直接指针访问对象:加快了对象访问速度,比间接访问少一次对象实例数据的访问,HotSpot则采用的这种访问方式。
- 对象的创建:
第3章 垃圾回收器与内存分配策略
对象已死吗?
即确定哪些内存需要回收,判断对象是否已死。- 引用计数:十分简单,但是无法解决循环引用问题
- 可达性分析算法:以GC Roots为起点顺着引用链搜索出所有的活着的对象
典型的GC Roots Set包含虚拟机栈中引用的对象、方法区中类静态属性与常量引用的对象,还有本地方法中引用的对象。 - 引用分类
- 强引用,即普通常见的引用,只要存在其引用对象就不会被回收
- 软引用,二次回收的对象
- 弱引用,只能生存到下次回收之前
- 虚引用,完全不管是否有,有的话在回收时会得到一个通知。
- 两次标记:如果待回收会被一次标记进入F-Queue,执行finalize方法,之后对F-Queue进行二次标记,如果finalize方法中逃逸(比如将this传出去)出来则暂缓回收,下次会被直接回收(finalize方法只会被执行一次)。
- 回收方法区:废弃常量(没有被指向的常量)、无用的类(无实例、Classloader已被回收且未被反射使用)
垃圾收集算法
- 标记-清除算法(Mark-Sweep)
最基础的算法,顾名思义,即标记可被回收的,之后回收,会有碎片。 - 复制算法
标记活着的,复制到新空间中,常用于新生代的回收,无碎片,但太浪费空间,且不能用于老年代,因为老年代回收效率低。 - 标记-整理算法
标记活着的,将活着的整理到空间开头,无碎片。 - 分代收集
根据存活周期将内存分块:新生代/老年代(1:2),根据特点使用算法
- 新生代:一般使用复制算法,因存活率低,附带分配担保保证高存活率下新生代Survivor区不够放。
- 老年代:一般用标记-清理或标记-整理。
- 标记-清除算法(Mark-Sweep)
HotSpot的算法实现
- 枚举根节点:使用准确式GC,减少停顿。
- 安全点:使用安全点保证引用关系变化情况下能安全回收。
- 安全区域:扩展安全点概念为安全区域,应对诸如Thread.sleep()的长时间挂起状态导致无法GC。
垃圾收集器
- Serial:串行收集,单线程,新生代/老年代均适用,是最早的收集器。
- ParNew:Serial的多线程版本,多线程收集新生代,可配合CMS使用。
- Parallel Scavenge:多线程,面向吞吐量,可自适应调节。
- Serial Old:串行收集老年代,单线程。
- Parallel Old:并行收集老年代。
- CMS(Concurrent Mark Sweep):面向最短停顿时间,初始标记->并发标记->重新标记->并发清除。
- G1(Garbage First):并行并发、分代收集、空间整合、可预测停顿,优先回收价值最大的Region,用Remembered Set避免全部扫描。
内存分配与回收策略
- 对象优先在Eden分配
- 新生代GC(MinorGC)
- 老年代GC(MajorGC/FullGC)
- 大对象直接进入老年代
- 长期存活的对象将进入老年代
对象在Survivor区每经过一次MinorGC就会长大一岁,超过阈值则进入老年代 - 动态年龄判定
相同年龄大小的对象总和超过Survivor空间的一半则大于等于该年龄的进入老年代。 - 空间分配担保:防止MinorGC时Survivor空间不足,需要老年代部分空间进行担保,(since6u24)只要老年代的连续空间大于新生代对象总大小或者历次晋升的平均大小就会进行MinorGC,否则进行FullGC。
- 对象优先在Eden分配
第4章 虚拟机性能监控与故障处理工具
以上是关于深入理解Java虚拟机 - 读书笔记的主要内容,如果未能解决你的问题,请参考以下文章