深入理解JVM

第一部分 走进Java

第1章 走进Java

第二部分 自动内存管理机制

第2章 Java内存区域与内存溢出异常
  • 运行时数据区。
    1. 程序计数器。线程私有,是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里,字节码解释器工作时就是通过改变这个计数器 的指来选取下一条需要执行的字节码指令、分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。如果线程正在执行Java方法,这个 计数器记录的是正在执行的虚拟机字节码指令的地址。如果执行的是Native方法,这个计数器值则为空(Undefined).此内存区域是唯一一个在Java虚拟机 规范中没有规定任何OutOfMemoryError情况的区域。
    2. Java虚拟机栈。线程私有,生命周期与线程相同。描述的是Java方法执行的内存模型。 用于存储局部变量表,局部变量表存放了编译期可知的各种基本数据类型(boolean, byte, char, short, int, float, long, double),对象引用(reference类型,它不等同对象本身,可能是一个指向对象起始地址的引用指针,也可能 是指向一个代表对象的句柄或其他与此对象相关的位置)和returnAddress类型。
    3. 本地方法栈。与虚拟机栈发挥的作用类似。
    4. Java堆。线程共享内存区域。所有的对象实例以及数组都要在堆山分配。Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可,就像我们的磁盘空间一样。
    5. 方法区。与Java堆一样,是各个线程共享的内存区域,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
    6. 运行时常量池。它是方法区的一部分。用于存放编译期生成的各种字面量和符号引用,这个部分内容将在类加载后进入方法区的运行时常量池存放。
  • -XX:+HeapDumpOnOutOfMemoryError 虚拟机出现内存溢出异常时Dump出当前的内存堆快照转储快照以便事后进行分析。
  • 常见堆内存溢出是因为new了大量对象,并无法回收。产生栈溢出常见是大量局部变量,不断迭代。
第3章 垃圾收集器与内存分配策略
  • 对象已死吗?
    1. 引用计数器;高效,有对象间循环引用问题。
    2. 可达性分析算法;GC Roots(可以作为GCRoot的对象包括:虚拟机栈中引用的对象;方法区中类静态属性引用的对象和常量引用的对象;本地方法栈中JNI引用对象)。
    3. 引用分类;四类:
      • 强引用就是指在程序代码中普遍存在的类似”Object object = new Object();”这类引用,只要强引用还在,垃圾收集器永远不会回收掉被引用的对象。
      • 软引用用来描述一些还有用但并非必须的对象。
      • 弱引用也是用来描述非必需对象的,但它的强度比软引用更弱一些,被弱引用用关联的对象只能生存到下一次垃圾收集发生之前。
      • 虚引用也称为幽灵引用或者幻影引用,它是最弱的一种引用关系。设置为它的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。
    4. 生存还是死亡. 一个对象真正的回收必须要经过两个标记过程。1是否重载finalize()它只会被调用一次。2是否还存在与存活的对象之间的引用链。
    5. 回收方法区;无用类的判定:该类所有的实例都已经被收回,也就是Java堆中不存在该类的任何实例。加载该类的ClassLoader已经被收回。该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
  • 垃圾收集算法
    1. 标记-清除算法:标记和清除两个阶段,效率低,产生大量不连续的内存碎片,若此时需要分配大对象不得不提前出发一次垃圾收集动作。
    2. 复制算法:将内存按容量大小分大小相同的两块,每次只用一块,将存活的放到另一块,然后清除上一块。简单高效,代价就是将内存缩小为原来的一半。新生代采用这种算法,因为新生代对象”朝生夕死”,所以并不需要按照1:1的比例划分内存空间,分一块较大的Eden空间和两块较小的Survivor空间,每次只使用Eden和其中一块Survivor。
    3. 标记-整理算法:对象存活较多时效率低。
    4. 分代收集算法。
  • HotSpot的算法实现。GC分析时需要确保”一致性”指的就是整个分析期间整个系统”Stop The World”。
  • Serial收集器:单线程意味着只会使用一个CPU或一条收集器线程去完成垃圾收集工作,会出现”Stop The World”的情况。简单高效,没有线程切换的开销,新生代收集器。运行client模式下比较好。
  • ParNew收集器:是Serial收集器的多线程版本,一起特性几乎与Serial一样。运行在server模式下比较好,也是新生代收集器,目前只有它与Serial可以与CMS收集器配合工作。
  • ParallelScavenge收集器:新生代收集器,复制算法,并行的多线程收集器。关注点是吞吐量即CPU用于运行用户代码的时间与CPU总消耗时间的比值。比如虚拟机执行用时共100min,其中GC用时1min则吞吐量99%。可以高效率的利用CPU时间。-XX:MaxGCPauseMillis;-XX:GCTimeRatio这两个参数可以做调整。具有自动调节策略。
  • Serial Old收集器:老年代版本,单线程收集器,采用标记-整理算法,主要意义是在于给client模式下的虚拟机使用。可以在Server模式下使用。
  • parallel Old收集器:老年代,多线程,标记-整理。
  • CMS(Concurrent Mark Sweep)收集器。以获取最短回收停顿时间为目标的收集器。重视响应速度,以带给用户更好的体验。基于标记-清除算法。
    • 初始标记 STW
    • 并发标记
    • 重新标记 STW
    • 并发清除
    • 缺点:对CPU资源非常敏感,并发回收时要求不少于25%的CPU资源。无法处理浮动垃圾。由于采用标记-清除算法所以继承了标记-清除算法的缺点。
  • G1(Grabage-First):并行与并发,分代收集,空间整合,可预测停顿。将整个堆划分为多个大小相等的独立区域Region。当Region之间的对象存在引用时, 虚拟机使用Remembered Set来避免全堆扫描,G1中每个Region都有一个与之对应的Remembered Set。可以理解为是把对其他Region的扫描在产生引用时就放到Remembered Set中 当GC时就直接从Remembered Set中获取引用从而避免全堆扫描。浮动对象的处理是暂存到Remembered Set Logs中在最终标记阶段需要将logs的数据合并到Remembered Set中。
第4章 虚拟机性能监控与故障处理工具
  • Sun JDK 监控和故障处理工具
名称 主要作用
jps JVM Process Status Tool,显示指定系统内所有的HotSpot虚拟机进程
jstat JVM Statistics Monitoring Tool, 用于收集HotSpot虚拟机各方面的运行数据
jinfo Configuration Info for Java,显示虚拟机配置信息
jmap Memory Map for Java,生成虚拟机的内存转储快照(heapdump文件)
jhat JVM Heap Dump Browser,用于分析heapdump文件,它会建立一个HTTP/HTML服务器,让用户可以在浏览器上查看分析结果
jstack Stack Trace for Java,显示虚拟机的线程快照
  • jmap -dump:format=b,file=abc.bin pid 用于生成dump快照文件
第5章 调优案例分析与实战
  • 堆外内存也可能造成溢出异常。包括:Direct Memory,可通过-XX:MaxDirectMemorySize调整大小;线程堆栈,可通过-Xss调整;Socket缓存区;JNI代码,如果代码中使用JNI调用本地库,那么本地库使用的内存也不在堆中。虚拟机和GC。
  • 高性能硬件上的程序部署策略。
  • 集群间同步导致的内存溢出。
  • 服务器JVM进程崩溃。
  • 不恰当的数据结构导致内存占用过大。
  • 编译时间和类加载时间的优化
  • 调整内存设置控制垃圾收集器频率。
  • 选择收集器降低延迟

第三部分 虚拟机执行子系统

第6章 类文件结构
第7章 虚拟机类加载机制
第8章 虚拟机字节码执行引擎
第9章 类加载及执行子系统的案例与实战

第四部分 程序编译与代码优化

第10章 早期(编译期)优化
第11章 晚期(运行期)优化

第五部分 高效并发

第12章 Java内存模型与线程
第13章 线程安全与锁优化