《深入理解Java虚拟机》读书笔记

《深入理解Java虚拟机》读书笔记_第1张图片
image.png

前言

  • 交流或更多内容请关注我的公众号:nezha_blog
  • 我的技术博客:https://nezha.github.io
《深入理解Java虚拟机》读书笔记_第2张图片
微信公众号

自己在阅读《深入理解Java虚拟机》后做了部分的整理,内容有些是来自网络,如有侵权,请联系邮箱:[email protected]

本书第一次阅读,所以并没有全篇通读,主要的阅读的章节是

  • 第2章 Java内存区域与内存溢出异常
  • 第3章 Java垃圾回收机器与内存分配策略
  • 第4章 JVM性能监控与故障处理工具
  • 第7章 虚拟机类加载机制

总之此书很值得一读,不管是理解JVM内存模型或者GC的机制及怎么去JVM调优这本书上写的还是挺全面的。

第2章 Java内存区域与内存溢出异常

概述

对于Java程序员来说,在虚拟机自动内存管理机制下,不需要为new操作去写配对的delete/free代码,不容易出现内存泄漏。但是如果出现内存泄漏问题,如果不了解虚拟机的机制,便难以定位。

运行时数据区域

下图是Java运行时数据区域划分图

《深入理解Java虚拟机》读书笔记_第3张图片
运行时数据区域
区域 是否线程共享 是否会内存溢出
程序计数器 不会
java虚拟机栈
本地方法栈
方法区

1.程序计数器(线程私有)

  • 一块较小的内存,可以看作是当前线程所执行的字节码的行号指示器;
  • 在虚拟机概念模型(各种虚拟机实现可能不一样)中,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令;
  • 程序计数器是属于线程私有的内存;
  • 如果执行的是Java方法,该计数器记录的是正在执行的虚拟机字节码指令的地址;如果是Native方法则为空;

2.Java虚拟机栈(线程私有)

  • Java虚拟机栈也是线程私有的;
  • 描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表操作数栈动态链接方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机中入栈到出栈的过程;
  • 局部变量表存放了编译器可知的各种基本数据类型、对象引用和returnAddress类型;其所需的内存空间在编辑期完成分配,不会再运行期改变;
  • 可能存在两种异常:StackOverflowError(请求栈深度过大)和OutOfMemoryError(内存不够时);

3.本地方法栈

  • 与虚拟机栈非常相似,只不过是为虚拟机使用到的Native方法服务;
  • 可能存在两种异常:StackOverflowError和OutOfMemoryError;

4.Java堆(线程共享)

  • Java堆是被所有线程共享的,在虚拟机启动时创建;
  • 此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这分配;
  • 是垃圾收集器管理的主要区域,可以分为新生代老年代
  • 可以物理不连续,只要逻辑上是连续的即可;
  • 如果堆中没有内存完成实例分配也无法再扩展时,会抛出OutOfMemoryError异常;

Eden:From Survivor:To Survivor比值为8:1:1

《深入理解Java虚拟机》读书笔记_第4张图片
Java堆内存结构

5.方法区/元空间(永久代)(线程共享)

  • 是线程共享的区域;
  • 用于存储已被虚拟机加载的类信息常量静态变量、即时编译器编译后的代码等数据;
  • 该区域对于垃圾收集来说条件比较苛刻,但是还是非常有必要要进行回收处理;
  • 当无法满足内存分配需求时,将抛出OutOfMemoryError异常;

6.运行时常量池

  • 是方法区的一部分;
  • Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译器生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放;
  • Java虚拟机规范要求较少,通常还会把翻译出来的直接引用也存储在此;
  • 另外一个重要特征是具备动态性,可以在运行期间将新的常量放入池中,如String的intern方法;
  • 可能存在的异常:OutOfMemoryError;

7.直接内存

  • 并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域;
  • JDK 1.4的NIO引入了基于通道(Channel)和缓冲区(Buffer)的IO方法,可以使用Native函数库直接分配对外内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作以提升性能;

对象的访问定位

  • 栈上的reference类型在虚拟机规范中只规定了一个指向对象的引用,并没有定义这个引用应该通过何种方式去定位、访问堆栈对象的具体位置,目前主流的方式方式有句柄直接指针两种。
  • 通过句柄:Java堆中划出一块内存作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息。其最大好处就是reference存储的是稳定的句柄地址,在对象被移动(垃圾收集时移到)只改变实例数据指针,而reference不需要修改;
《深入理解Java虚拟机》读书笔记_第5张图片
通过句柄访问对象
  • 通过直接指针:Java堆对象的布局中必须考虑如果放置访问类型数据的相关信息,而reference中存在的直接就是对象地址。其最大好处在于速度更快,节省了一次指针定位的时机开销。HotSpot采用该方式进行对象访问,但其他语言和框架采用句柄的也非常常见。
《深入理解Java虚拟机》读书笔记_第6张图片
通过直接指针

内存参数的调节见这边

JAVA对象在JVM中内存分配

第3章 Java垃圾回收机器与内存分配策略

概述

思考GC需要完成的3件事情:

  • 1.哪些内存需要回收?
  • 2.什么时候回收?
  • 3.如何回收?

再回头看看第二章介绍的Java内存运行时区域的各个部分:

  • 程序计时器、虚拟机栈、本地方法栈:随线程而灭,栈帧随方法而进行出栈和入栈,每一个栈帧分配的内存在类结构确定就已知,因此这几个区域不需要考虑回收
  • 对于Java堆和方法区,只有程序运行期间才知道会创建哪些对象,内存的分配和回收都是动态的,垃圾收集器所关注的是这部分内存

判断Java中对象存活的算法

1.引用计数算法

给对象添加引用计数器,当有地方引用它时就加1,引用失效就减1,为0时就认为对象不再被使用可回收。该算法失效简单,判断高效,但并不被主流虚拟机采用,主要原因是它很难解决对象之间相互循环引用的问题。

2.可达性分析算法

通过一系列的称为“GC Roots”的对象作为起点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),如果一个对象到GC Roots没有引用链相连,则该对象是不可用的。

在Java语言中,可作为GC Roots的对象包括:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象;
  • 方法区中类静态属性引用的对象;
  • 方法区中常量引用的对象;
  • 本地方法栈中JNI(即一般说的Native方法)引用的对象;

垃圾收集算法

  • 1.标记-清除算法(Mark-Sweep):首先标记出所有需要回收的对象,然后统一回收所有被标记的对象;缺点是效率不高且容易产生大量不连续的内存碎片;
《深入理解Java虚拟机》读书笔记_第7张图片
标记-清除算法
  • 复制算法:将可用内存分为大小相等的两块,每次只使用其中一块;当这一块用完了,就将还活着的对象复制到另一块上,然后把已使用过的内存清理掉。在HotSpot里,考虑到大部分对象存活时间很短将内存分为Eden和两块Survivor,默认比例为8:1:1。代价是存在部分内存空间浪费,适合在新生代使用
《深入理解Java虚拟机》读书笔记_第8张图片
复制算法
  • 标记-整理算法:首先标记出所有需要回收的对象,然后让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存适用于老年代

    《深入理解Java虚拟机》读书笔记_第9张图片
    标记-整理算法

  • 分代收集算法:一般把Java堆分新生代和老年代,在新生代用复制算法在老年代用标记-清理或标记-整理算法,是现代虚拟机通常采用的算法。

《深入理解Java虚拟机》读书笔记_第10张图片
分代收集算法

垃圾收集器

  • 垃圾收集算法是内存回收的方法论,垃圾收集器就是内存回收的具体实现。
  • 这里讨论JDK 1.7 Update 14之后的HotSpot虚拟机(此时G1仍处于实验状态),包含的虚拟机如下图所示(存在连线的表示可以搭配使用):
《深入理解Java虚拟机》读书笔记_第11张图片
gc_for_hotspot

1.Serial收集器(单线程的收集器)

《深入理解Java虚拟机》读书笔记_第12张图片
Serial收集器
  • 最基本、发展历史最悠久,在JDK 1.3之前是新生代收集的唯一选择;
  • 是一个单线程(并非指一个收集线程,而是会暂停所有工作线程)的收集器,采用的是复制算法;
  • 现在依然是虚拟机运行在Client模式下的默认新生代收集器,主要就是因为它简单而高效(没有线程交互的开销);

2.ParNew收集器(Serial收集器的多线程版本)

《深入理解Java虚拟机》读书笔记_第13张图片
ParNew收集器
  • 其实就是Serial收集器的多线程版本;
  • ParNew收集器在单CPU环境中绝对不会有比Serial收集器更好的效果;
  • 是许多运行在Server模式下虚拟机首选的新生代收集器,重要原因就是除了Serial收集器外,只有它能与CMS收集器配合工作
  • 并行(Parallel):指多条垃圾收集线程并行工作,但此时用户线程仍处于等待状态;
  • 并发(Concurrent):指用户线程与垃圾收集线程同时执行,用户线程在继续执行而垃圾收集程序运行在另外一个CPU上;

3.Parallel Scavenge收集器

吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)

  • 新生代收集器,使用复制算法,并行的多线程收集器;
  • CMS关注于尽可能缩短垃圾收集时用户线程停顿时间不同,PS的目标是达到一个可控制的吞吐量
  • 高吞吐量可以高效率利用CPU时间,适合在后台运算而不需要太多交互的任务;
  • -XX:MaxGCPauseMillis参数可以设置最大停顿时间,而停顿时间缩短是以牺牲吞吐量和新生代空间来换取的;
  • 另外它还支持GC自适应的调节策略;

4.Serial Old收集器

《深入理解Java虚拟机》读书笔记_第14张图片
Serial Old收集器
  • 是Serial收集器的老年代版本,同样是单线程,使用标记-整理算法;
  • 主要是给Client模式下的虚拟机使用的;
  • 在Server模式下主要是给JDK 1.5及之前配合Parallel Scavenge使用或作为CMS收集器的后备预案

5.Parallel Old收集器

《深入理解Java虚拟机》读书笔记_第15张图片
Parallel Old收集器
  • 是Parallel Scavenge的老年代版本,使用多线程和标记-整理算法;

6.CMS收集器

《深入理解Java虚拟机》读书笔记_第16张图片
CMS收集器
  • 是一种以获取最短回收停顿时间为目标的收集器,特别适合互联网站或者B/S的服务端;
  • 它是基于标记-清除 算法实现的,主要包括4个步骤:初始标记(STW-stop the world,只是初始标记一下GC Roots能直接关联到的对象,速度很快)、并发标记(非STW,执行GC RootsTracing,耗时比较长)、重新标记(STW,修正并发标记期间因用户程序继续导致变动的那一部分对象标记)和并发清除(非STW,耗时较长);
  • 还有3个明显的缺点:CMS收集器对CPU非常敏感(占用部分线程及CPU资源,影响总吞吐量)、无法处理浮动垃圾(默认达到92%就触发垃圾回收)、大量内存碎片产生(可以通过参数启动压缩);

7.G1收集器

《深入理解Java虚拟机》读书笔记_第17张图片
G1收集器
  • 一款面向服务端应用的垃圾收集器,后续会替换掉CMS垃圾收集器;
  • 特点:并行与并发(充分利用多核多CPU缩短Stop-The-World时间)、分代收集(独立管理整个Java堆,但针对不同年龄的对象采取不同的策略)、空间整合(基于标记-整理)、可预测的停顿(将堆分为大小相等的独立区域,避免全区域的垃圾收集);
  • 关于Region:新生代和老年代不再物理隔离,只是部分Region的集合;G1跟踪各个Region垃圾堆积的价值大小,在后台维护一个优先列表,根据允许的收集时间优先回收价值最大的Region;Region之间的对象引用以及其他收集器中的新生代与老年代之间的对象引用,采用Remembered Set来避免全堆扫描;
  • 分为几个步骤:1.初始标记(标记一下GC Roots能直接关联的对象并修改TAMS值,需要STW但耗时很短)、2.并发标记(从GC Root从堆中对象进行可达性分析找存活的对象,耗时较长但可以与用户线程并发执行)、3.最终标记(为了修正并发标记期间产生变动的那一部分标记记录,这一期间的变化记录在Remembered Set Log里,然后合并到Remembered Set里,该阶段需要STW但是可并行执行)、4.筛选回收(对各个Region回收价值排序,根据用户期望的GC停顿时间制定回收计划来回收);
  • 《深入理解Java虚拟机》读书笔记_第18张图片

理解GC日志

详细的解读,请查阅本书籍。

  • 最前面的数字代表GC发生的时间(虚拟机启动以后的秒杀);
  • “[GC”和“[Full GC”说明停顿类型,有Full代表的是Stop-The-World的;
  • “[DefNew”、“[Tenured”和“[Perm”表示GC发生的区域;
  • 方括号内部的“3324K -> 152K(3712K)” 含义是 “GC前该内存已使用容量 -> GC后该内存区域已使用容量(该区域总容量)”;
  • 方括号之外的“3324K -> 152K(11904)” 含义是 “GC前Java堆已使用容量 -> GC后Java堆已使用容量(Java堆总容量)”;
  • 再往后“0.0025925 secs”表示该内存区域GC所占用的时间;

内存分配与回收策略

  • 对象优先在新生代分配
  • 大对象直接进入老年代
  • 长期存活的对象将进入老年代
  • 动态对象年龄判断:如果在Survivor空间中相同年龄所有对象大小总和大于Survivor空间的一半,大于或等于该年龄的对象直接进入老年代;
  • 空间分配担保:发生Minor GC前,虚拟机会先检查老年代最大可用连续空间是否大于新生代所有对象总空间,如果不成立,虚拟机会查看HandlePromotionFailure设置值是否允许担保失败,如果允许继续检查老年代最大可用的连续空间是否大于历次晋升到老年代的平均大小,如果大于会尝试进行一次Minor GC;如果小于或者不允许冒险,会进行一次Full GC;

第4章 JVM性能监控与故障处理工具

概述

定位问题时,知识和经验是关键基础、数据(运行日志、异常堆栈、GC日志、线程快照、堆转储快照)是依据、工具是运用知识处理数据的手段。

JDK命令行工具

《深入理解Java虚拟机》读书笔记_第19张图片

1.jps: 虚拟机进程状况工具

《深入理解Java虚拟机》读书笔记_第20张图片
image.png
image.png
  • 功能:可以列出正在运行的虚拟机进程,并线上虚拟机执行的主类名称及其本地虚拟机唯一ID(LVMID);
  • 对于本地虚拟机来说,LVMID和操作系统的进程ID是一致的;
  • 其他的工具通常都需要依赖jps获取LVMID;
  • 主要选项:-q(只输出LVMID)、-m(输出传给main函数的参数)、-l(输出主类的全名)、-v(输出虚拟机启动JVM参数);

2.jstat:虚拟机统计信息监视工具

《深入理解Java虚拟机》读书笔记_第21张图片
image.png
《深入理解Java虚拟机》读书笔记_第22张图片
image.png
  • 功能:监视虚拟机各种运行状态信息,包括类装载、内存、垃圾收集、JIT等;
  • 纯文本监控首选

3.jinfo:Java配置信息工具

《深入理解Java虚拟机》读书笔记_第23张图片
image.png
  • 功能:实时地查看虚拟机各项参数。虽然jps -v可以查看虚拟机启动参数,但是无法查看一些系统默认的参数。
  • 支持运行期修改参数的能力,格式为“jinfo -flag name=value pid”;

4.jmap:Java内存映像工具

《深入理解Java虚拟机》读书笔记_第24张图片
image.png
image.png
  • 功能:用于生成堆转储快照(一般称为heapdump或dump文件);
  • 其他可生成heapdump的方式:使用参数-XX:+HeapDumpOnOutOfMemoryError;使用参数-XX:+HeapDumpOnCtrlBreak然后使用Ctrl+Break生成;Linux系统使用kill -3生成;
  • 另外它还可以查询finalize执行队列、Java堆和永久代的详细信息;

5.jhat:虚拟机堆转储快照分析工具

《深入理解Java虚拟机》读书笔记_第25张图片
《深入理解Java虚拟机》读书笔记_第26张图片
  • 功能:用于分析jmap生成的heapdump。其内置了一个微型的HTTP服务器,可以在浏览器查看分析结果;
  • 实际很少用jhat,主要有两个原因:一是分析工程会耗用服务器资源;功能相对BisualVM、IBM HeapAnalyzer较为简陋

6.jstack:Java堆栈跟踪工具

《深入理解Java虚拟机》读书笔记_第27张图片
image.png
《深入理解Java虚拟机》读书笔记_第28张图片
image.png
  • 功能:用于生成虚拟机当前时刻的线程快照(一般称为threaddump或javacore文件)。javacore主要目的是定位线程出现长时间停顿的原因,比如死锁、死循环、请求外部资源响应长等;
  • 另外JDK 1.5后Thread类新增了getAllStackTraces()方法,可以基于此自己增加管理页面来分析

7.HSDIS:JIT生成代码反编译

《深入理解Java虚拟机》读书笔记_第29张图片
《深入理解Java虚拟机》读书笔记_第30张图片
image.png
  • 现代虚拟机的实现慢慢地和虚拟机规范产生差距,如果要分析程序如果执行,最常见的就是通过软件调试工具(GDB、Windbg等)断点调试,但是对于Java来说,很多执行代码是通过JIT动态生成到CodeBuffer中的;
  • 功能:HSDIS是官方推荐的HotSpot虚拟机JIT编译代码的反汇编工具,它包含在HotSpot虚拟机的源码中但没有提供编译后的程序,可以自己下载放到JDK的相关目录里;

JDK可视化工具

1.JConsole:Java监视与管理控制台

  • 是一种基于JMX的可视化监控和管理工具,它管理部分的功能是针对MBean进行管理,由于MBean可以使用代码、中间件服务器或者所有符合JMX规范的软件进行访问,因此这里着重介绍JConsole的监控功能;
  • 通过jconsole命令启动JConsole后,会自动搜索本机所有虚拟机进程。另外还支持远程进程的监控;
  • 进入主界面,支持查看以下标签页:概述、内存、线程、类、VM摘要和MBean;

2.VisualVM:多合一故障处理工具

《深入理解Java虚拟机》读书笔记_第31张图片
image.png
  • 目前为止JDK发布的功能最强调的运行监控和故障处理程序,另外还支持性能分析;
  • VisualVM还有一个很大的优点:不需要被监视的程序基于特殊Agent运行,对应用程序的实际性能影响很小,可直接应用在生成环境中;
  • VisualVM基于NetBeans平台开发,具备插件扩展功能的特性,基于插件可以做到:显示虚拟机进程以及进程配置、环境信息(jps、jinfo)、监视应用程序的CPU、GC、堆、方法区以及线程的信息(jstat、jstack)、dump以及分析堆转储快照(jmap、jhat)、方法级的程序运行性能分析,找出被调用最多运行时间最长的方法、离线程序快照(收集运行时配置、线程dump、内存dump等信息建立快照)、其他plugins的无限可能。
  • 使用jvisualvm首次启动时需要在线自动安装插件(也可手工安装);
  • 特色功能:生成浏览堆转储快照(摘要、类、实例标签页、OQL控制台)、分析程序性能(Profiler页签可以录制一段时间程序每个方法执行次数和耗时)、BTrace动态日志跟踪(不停止目标程序运行的前提下通过HotSwap技术动态加入调试代码);

第7章 虚拟机类加载机制

概述

  • 虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。
  • 在Java语言里面,类型的加载、连接和初始化过程都是在程序运行期间完成,这虽然增量一些性能开销,但是会为Java应用程序提供高度的灵活性。

类加载的时机

  • 类的整个生命周期:加载、验证、准备、解析、初始化、使用和卸载;其中验证、准备和解析统称为连接;
  • 虚拟机规范没有强制约束类加载的时机,但严格规定了有且只有5种情况必须立即对类进行初始化:遇到new、getstatic、putstatic和invokestatic指令;对类进行反射调用时如果类没有进行过初始化;初始化时发现父类还没有进行初始化;虚拟机启动指定的主类;动态语言中MethodHandle实例最后解析结果REF_getStatic等的方法句柄对应的类没有初始化时;

类加载的过程

1.加载

  • 通过一个类的全限定名来获取定义此类的二进制字节流;
  • 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构;
  • 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口;

2.验证

  • 验证是连接阶段的第一步,其目的是确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全;
  • 验证阶段是非常重要的,这个阶段是否严谨决定了Java虚拟机是否能承受恶意代码的攻击;
  • 校验动作:文件格式验证(基于二进制字节流)、元数据验证(对类的元数据语义分析)、字节码验证(对方法体语义分析)、符号引用验证(对类自身以外的信息进行匹配性校验);

3.准备

  • 正式为变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在这个方法区中进行分配;
  • 需要强调两点:这时候内存分配的仅包括类变量,而不包括类实例变量;这里所说的初始化通常情况下是数据类型的零值,真正的赋值是在初始化阶段,如果是static final的则是直接赋值;

4.解析

  • 解析阶段是虚拟机将常量池内的符号引用(如CONSTANT_Class_infoCONSTANT_Fieldref_infoCONSTANT_Methodref_info等7种)替换为直接引用的过程;
  • 符号引用可以是任何形式的字面量,与虚拟机实现的内存布局无关,引用的目标并不一定已经加载到内存中;而直接引用是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄,它和虚拟机实现的内存布局相关,引用的目标必定以及在内存中存在;
  • 对同一个符号引用进行多次解析请求是很常见的事情,虚拟机实现可以对第一次解析的结果进行缓存;

5.初始化

  • 是类加载过程的最后一步,真正开始执行类中定义的Java程序代码(或者说是字节码);
  • 初始化阶段是执行类构造器方法的过程,该方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块中的语句合并产生的;
  • 方法与类的构造函数(或者说是实例构造器方法)不同,它不需要显式地调用父类构造器,虚拟机会保证在子类的方法执行之前,父类的方法已执行完毕;
  • 执行接口的方法不需要先执行父接口的方法,只有当父接口中定义的变量使用时父接口才会初始化,接口的实现类在初始化时也一样不会执行接口的方法;
  • 方法初始化是加锁阻塞等待的,应当避免在方法中有耗时很长的操作;

类加载器

  • 虚拟机设计团队把类加载阶段的“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到虚拟机外部去实现,实现这个动作的代码模块称为类加载器;
  • 这是Java语言的一项创新,也是Java语言流行的重要原因,在类层次划分、OSGI、热部署、代码加密等领域大放异彩

类与类加载器

  • 对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在Java虚拟机的唯一性,每一个类加载器都拥有一个独立的类名称空间;
  • 比较两个类是否相等(如Class对象的equals方法、isAssignableFrom方法、isInstance方法),只有在这两个类是由同一个类加载器加载的前提下才有意义;

双亲委派模型

关于双亲委派模型,这篇文章写得简单易懂:http://www.jianshu.com/p/acc7595f1b9d

《深入理解Java虚拟机》读书笔记_第32张图片
image.png
  • 三种系统提供的类加载器:启动类加载器(Bootstrap ClassLoader)、扩展类加载器(Extension ClassLoader)、应用程序类加载器(Application ClassLoader);
  • 双亲委派模型要求除了顶层的启动类加载器外,其他的类加载器都应当有自己的父类加载器,这里一般不会以继承的关系来实现,而是使用组合的关系来复用父加载器的代码;
  • 其工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,只有父类加载器反馈自己无法完成这个加载请求时(它的搜索范围中没有找到所需的类),子加载器才会尝试自己去加载;
  • 这样的好处是Java类随着它的类加载器具备了一种带有优先级的层次关系,对保证Java程序的稳定运作很重要;
  • 实现双亲委派的代码都集中在java.lang.ClassLoader的loadClass方法中,逻辑清晰易懂;

JVM常用参数调节

内存参数

参数 作用
-Xmx 堆大小的最大值。当前主流虚拟机的堆都是可扩展的
-Xms 堆大小的最小值。可以设置成和 -Xmx 一样的值
-Xmn 新生代的大小。现代虚拟机都是“分代”的,因此堆空间由新生代和老年代组成。新生代增大,相应地老年代就减小。Sun官方推荐新生代占整个堆的3/8
-Xss 每个线程的堆栈大小。该值影响一台机器能够创建的线程数上限
-XX:MaxPermSize= 永久代的最大值。永久代是 HotSpot 特有的,HotSpot 用永久代来实现方法区
-XX:PermSize= 永久代的最小值。可以设置成和 -XX:MaxPermSize 一样的值
-XX:SurvivorRatio= Eden 和 Survivor 的比值。基于“复制”的垃圾收集器又会把新生代分为一个 Eden 和两个 Survivor,如果该参数为8,就表示 Eden 占新生代的80%,而两个 Survivor 各占10%。默认值为8
-XX:PretenureSizeThreshold= 直接晋升到老年代的对象大小。大于这个参数的对象将直接在老年代分配。默认值为0,表示不启用
-XX:HandlePromotionFailure= 是否允许分配担保失败。在 JDK 6 Update 24 后该参数已经失效。
-XX:MaxTenuringThreshold= 对象晋升到老年代的年龄。对象每经过一次 Minor GC 后年龄就加1,超过这个值时就进入老年代。默认值为15
-XX:MaxDirectMemorySize= 直接内存的最大值。对于频繁使用 nio 的应用,应该显式设置该参数,默认值为0

GC参数

垃圾收集器 参数 备注
Serial(新生代) -XX:+UseSerialGC 虚拟机在 Client 模式下的默认值,打开此开关后,使用 Serial + Serial Old 的收集器组合。Serial 是一个单线程的收集器
ParNew(新生代) -XX:+UseParNewGC 强制使用 ParNew,打开此开关后,使用 ParNew + Serial Old 的收集器组合。ParNew 是一个多线程的收集器,也是 server 模式下首选的新生代收集器
-XX:ParallelGCThreads= 垃圾收集的线程数
Parallel Scavenge(新生代) -XX:+UseParallelGC 虚拟机在 Server 模式下的默认值,打开此开关后,使用 Parallel Scavenge + Serial Old 的收集器组合
-XX:MaxGCPauseMillis= 单位毫秒,收集器尽可能保证单次内存回收停顿的时间不超过这个值。
-XX:GCTimeRatio= 总的用于 gc 的时间占应用程序的百分比,该参数用于控制程序的吞吐量
-XX:+UseAdaptiveSizePolicy 设置了这个参数后,就不再需要指定新生代的大小(-Xmn)、 Eden 和 Survisor 的比例(-XX:SurvivorRatio)以及晋升老年代对象的年龄(-XX:PretenureSizeThreshold)了,因为该收集器会根据当前系统的运行情况自动调整。当然前提是先设置好前两个参数。
Serial Old(老年代) Serial Old 是 Serial 的老年代版本,主要用于 Client 模式下的老生代收集,同时也是 CMS 在发生 Concurrent Mode Failure 时的后备方案
Parallel Old(老年代) -XX:+UseParallelOldGC 打开此开关后,使用 Parallel Scavenge + Parallel Old 的收集器组合。Parallel Old 是 Parallel Scavenge 的老年代版本,在注重吞吐量和 CPU 资源敏感的场合,可以优先考虑这个组合
CMS(老年代) -XX:+UseConcMarkSweepGC 打开此开关后,使用 ParNew + CMS 的收集器组合。
-XX:CMSInitiatingOccupancyFraction= CMS 收集器在老年代空间被使用多少后触发垃圾收集
-XX:+UseCMSCompactAtFullCollection 在完成垃圾收集后是否要进行一次内存碎片整理
-XX:CMSFullGCsBeforeCompaction= 在进行若干次垃圾收集后才进行一次内存碎片整理

附图:可以配合使用的收集器组合

《深入理解Java虚拟机》读书笔记_第33张图片
gc_for_hotspot

其他参数

参数 作用
-verbose:class 打印类加载过程
-XX:+PrintGCDetails 发生垃圾收集时打印 gc 日志,该参数会自动带上 -verbose:gc 和 -XX:+PrintGC
-XX:+PrintGCDateStamps / -XX:+PrintGCTimeStamps 打印 gc 的触发事件,可以和 -XX:+PrintGC 和 -XX:+PrintGCDetails 混用
-Xloggc: gc 日志路径
-XX:+HeapDumpOnOutOfMemoryError 出现 OOM 时 dump 出内存快照用于事后分析
-XX:HeapDumpPath= 堆转储快照的文件路径

参考文章

1.Gino Zhang的博客总结的很全面 http://ginobefunny.com

2.关于双亲委派模型,这篇文章写得简单易懂:http://www.jianshu.com/p/acc7595f1b9d

你可能感兴趣的:(《深入理解Java虚拟机》读书笔记)