CMS GC

1. 概述

CMS,Most concurrently,是一款 采用 并发-标记-清除(Concurrent-Mark-Sweep)算法、针对老年代的 GC,其目的是最小化暂停时间。

CMS GC 在 JDK 9 被标记为 deprecated。

2. 执行过程

CMS 的标记过程采用指针可达性分析方法,对于存活的对象,在对象头上进行标记。

2.1 初始标记

从 GC Roots、新生代(Eden、From、To)出发标记直接关联的对象。

需要 STW 。

2.2 并发标记

该阶段进行 GC Root Tracing,从初始标记阶段 标记过的对象出发,标记所有可达的对象。

与应用线程并发执行,可以采用多个标记线程进行并发标记。

2.3 预清理

此阶段从新生代晋升的对象、新分配到老年代的对象以及在并发阶段修改了的对象 出发进行标记。
在并发标记阶段,如果老年代中有对象内部引用发生变化,会把所在的 card 标记为 dirty,通过扫描这些 card,重新标记那些在并发阶段引用被更新的对象。

通过 -XX:-CMSPrecleaningEnabled 参数选择关闭该阶段,默认开启。

目的是减少重新标记阶段的暂停时间。

CMS 提供了参数 CMSScavengeBeforeRemark 在 重新标记之前强制进行一次 minor GC,这样做的好处是缩短了重新标记阶段的暂停时间,坏处是 Minor GC 后紧跟着一个 重新标记的 暂停,这样使停顿时间也比较久。

继续阅读

JVM 堆外内存泄漏分析(二)

关于 堆外内存的组成可以看上一篇文章 JVM 堆外内存泄漏分析(一)

1. NMT

NMT(Native Memory Tracking)是 HotSpot JVM 引入的跟踪 JVM 内部使用的本地内存的一个特性,可以通过 jcmd 工具访问 NMT 数据。NMT 目前不支持跟踪第三方本地代码的内存分配和 JDK 类库。

NMT 不跟踪非 JVM 代码的内存分配,本地代码里的内存泄露需要使用操作系统支持的工具来定位。

1.1 开启 NMT

启用 NMT 会带来 5-10% 的性能损失。NMT 的内存使用率情况需要添加两个机器字 word 到 malloc 内存的 malloc 头里。NMT 内存使用率也被 NMT 跟踪。

启动命令: -XX:NativeMemoryTracking=[off | summary | detail]

off:NMT 默认是关闭的;
summary:只收集子系统的内存使用的总计数据;
detail:收集每个调用点的内存使用数据。

1.2 jcmd 访问 NMT 数据

命令: jcmd <pid> VM.native_memory [summary | detail | baseline | summary.diff | detail.diff | shutdown] [scale= KB | MB | GB]

option desc
summary 按分类打印汇总数据
detail 按分类打印汇总数据
打印虚拟内存映射
按调用点打印内存使用汇总
baseling 创建内存使用快照用于后续对比
summary.diff 基于最新的基线打印一份汇总报告
detail.diff 基于最新的基线打印一份明细报告
shutdown 关闭 NMT

在 NMT 启用的情况下,可以通过下面的命令行选项在 JVM 退出时输出最后的内存使用数据:
-XX:+UnlockDiagnosticVMOptions -XX:+PrintNMTStatistics

继续阅读

JVM 堆外内存泄漏分析(一)

1. JVM 感知容器资源

Java 应用部署在 Kubernetes 集群里,每个容器只运行一个进程, JVM 的启动命令是打包在镜像文件里的。

常规的方式是采用 -Xmx4g -Xms2g 这样的参数来指定 JVM 堆的最大、最小尺寸,如果需要调整堆大小就需要重新打包镜像。

为了避免因为修改堆大小而重新打包,从 JDK 8u191 版本开始支持 JVM 感知容器资源限制,这样在调整 JVM 内存分配时就不需要重新打包镜像文件,采用下面的参数来使 JVM 在启动时感知到容器的资源限制,并设定堆的大小:

-XX:+UseCGroupMemoryLimitForHeap
-XX:InitialRAMPercentage=60.00
-XX:MaxRAMPercentage=80.00
-XX:MinRAMPercentage=60.00 

假如分配给容器的内存上限是 4G,那么上述配置,JVM 堆的初始大小和最小尺寸是 4G * 0.6 即 2.4G,最大尺寸是 4G * 0.8 即 3.2G。

2. JVM 被 oomkill

上面的配置运行一段时间后发现容器自动重启了,在 linux 下通过 dmesg 命令查看系统日志,可以看到类似下面的日志:

Aug  8 15:32:40 H-LDOCKER-01 kernel: [ pid ]   uid   tgid   total_vm      rss        nr_ptes   nr_pmds   swapents   oom_score_adj  name
Aug  8 15:32:40 H-LDOCKER-01 kernel: [33775]   1001  33775  7624373       2036828    4476      32        0          -998           java
Aug  8 15:32:40 H-LDOCKER-01 kernel: Memory cgroup out of memory: Kill process 33775 (java) score 0 or sacrifice child
Aug  8 15:32:40 H-LDOCKER-01 kernel: Killed process 33775 (java) total-vm:30497492kB, anon-rss:8134056kB, file-rss:13256kB

继续阅读

Java Flight Recordings (JFR) — part 2 用 JFR 定位性能问题

内容目录

一、JFR 概览

二、找出瓶颈

不同的应用有不同的瓶颈。对于有些应用,瓶颈可能是等待 I/O 或网络,可能是线程之间的同步,或者是实际的 CPU 使用。对于其他,瓶颈可能是 GC 时间。很可能应用有不止一个瓶颈。

找出应用瓶颈的一个方法是查看 Events 选项卡。这是一个高级选项卡,可以做不少事情。如下图,可以选择自己感兴趣的事件:
jfr-event-types

继续阅读

Java Flight Recordings (JFR) — Java 飞行记录器 – part 1

一、JFR 飞行记录器

Java Flight Recorder(JFR)是一个商业特性,用在生产服务器上是需要商业许可的。

JFR 记录了关于 Java 运行时及运行在其内的 Java 应用程序的详细信息,记录用少量的开销完成。数据是作为时间上的数据点(称为事件)记录的。典型的事件可以是线程等待锁、GC、CPU 周期使用数据等。

在创建飞行记录时,你可以选择哪些事件应当保存,这叫做记录模板。有些模板只保存基本事件,对性能几乎没有影响。其他模板可能有轻微的性能开销,还可能触发 GC 来收集更多信息。通常,超过百分之几的开销是很罕见。

飞行记录可用于调试很大范围的问题,从性能问题到内存泄漏或严重的锁竞争。

1、记录类型

1.1、连续录制

一个连续记录是指记录总是开着并保存,例如,过去六小时的数据。如果应用程序陷入问题,你可以转储(dump)这些数据,例如,从过去一小时的,看看出生问题时发生了什么。

连续记录的默认设置是使用记录 profile,开销极低。这个 profile 不收集堆统计信息或(内存)分配性能分析,但仍然收集了很多有用数据。

保持持续录制一直允许是很好的,对于调试非常罕见的问题时非常有用。记录可以用 jcmdJMC 手工转储。你也可以在 JMC 里设置触发器在一些特定的条件被满足后转储飞行记录。

1.2、性能分析录制

性能分析录制是指记录开着,运行一定时间,然后停止。通常, 性能分析录制允许更多的时间,有可能对性能有较大的冲击。被开启的时间可以被修改,取决于你的 性能分析录制。

使用 性能分析录制 的典型场景如下:

  • 剖析运行最频繁的方法和创建对象最多的地方。
  • 查找使用了越来越多内存的类,暗示着内存泄漏。
  • 查找因为同步而导致的瓶颈,更多类型的情景。

性能分析录制 将给出很多信息,即使你不是在定位特定的问题。 性能分析录制 将给你关于应用程序的很好的视图,可以帮助你找出瓶颈或需要提高的地方。

继续阅读

JVM CPU 使用率高 问题两则

最近有两个系统先后被恶意扫描,出现 CPU 使用率高的现象。很好,把问题暴露出来解决掉。

CPU 使用率很高,首先是要找出 CPU 在执行什么样的代码,然后在分析这些代码有什么问题。

一、问题定位

1、 用命令 “ps aux | grep APP” 找出应用的进程 id:

801       84703  5.6 28.8 4627436 1132100 pts/2 Sl   11:08  11:19 /usr/jdk1.6.0_38/bin/java APP

2、 找出耗CPU的线程,在Linux系统下用命令:“ top –H –p pid ”, pid 就是前面找出来的应用进程 ID 。这个命令会显示出当前占用CPU高的线程。

Tasks: 426 total,   0 running, 426 sleeping,   0 stopped,   0 zombie
Cpu(s):  0.5%us,  0.6%sy,  0.0%ni, 72.3%id, 26.6%wa,  0.0%hi,  0.0%si,  0.0%st
Mem:   3924912k total,  3308088k used,   616824k free,      768k buffers
Swap:  8388600k total,  3236720k used,  5151880k free,    12304k cached

   PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
84784 appdeplo  20   0 4518m 1.1g 3816 S  0.4 29.0   0:00.85 java
84903 appdeplo  20   0 4518m 1.1g 3816 S  0.4 29.0   0:34.66 java
84983 appdeplo  20   0 4518m 1.1g 3816 S  0.4 29.0   0:00.99 java
85091 appdeplo  20   0 4518m 1.1g 3816 S  0.4 29.0   0:02.69 java
85164 appdeplo  20   0 4518m 1.1g 3816 S  0.4 29.0   0:04.92 java
84703 appdeplo  20   0 4518m 1.1g 3816 S  0.0 29.0   0:00.00 java
84704 appdeplo  20   0 4518m 1.1g 3816 S  0.0 29.0   0:00.42 java
84705 appdeplo  20   0 4518m 1.1g 3816 S  0.0 29.0   0:02.52 java
84706 appdeplo  20   0 4518m 1.1g 3816 S  0.0 29.0   0:02.64 java
84707 appdeplo  20   0 4518m 1.1g 3816 S  0.0 29.0   0:02.46 java
84708 appdeplo  20   0 4518m 1.1g 3816 S  0.0 29.0   0:02.39 java
84709 appdeplo  20   0 4518m 1.1g 3816 S  0.0 29.0   0:33.99 java

这里的 PID 比如 84784 是十进制的,需要转换为十六进制,用windows的计算器就可以转换了,转换为十六进制是:14B30

继续阅读

Java 对象内存布局

本文来自:http://www.ibm.com/developerworks/cn/java/j-codetoheap/

寻址能力与用户空间

进程能够处理的位数取决于处理器能寻址的内存范围,处理器的寻址能力取决于处理器的位数,比如 32 位能寻址 2^32,也就是 4G。

处理器提供的部分可寻址范围由 OS 本身用,供操作系统内核以及 C 运行时。OS 和 C 运行时占用的内存数量取决于所用的 OS,比如 Windows 默认占用 2GB。剩余的可寻址空间是供运行的实际进程使用的内存(用户空间)。

对于 Java 应用程序,用户空间是 Java 进程占用的内存,实际上包含两个池:Java 堆和本机(非 Java)堆。Java 堆的大小由 JVM 的 Java 堆设置控制:-Xms 和 -Xmx 分别设置最小和最大 Java 堆。在按照最大的大小设置分配了 Java 堆之后,剩下的用户空间就是本机堆。
32bit JVM

可寻址范围总共有 4GB,OS 和 C 运行时大约占用了其中的 1GB,Java 堆占用了将近 2GB,本机堆占用了其他部分。请注意,JVM 本身也要占用内存,就像 OS 内核和 C 运行时一样,而 JVM 占用的内存是本机堆的子集。

继续阅读

Java SE 6 故障排除指南 – 4、系统崩溃故障排除

崩溃或致命错误导致进程异常终止。有各种可能的理由导致崩溃。例如,崩溃可能是由于HotSpot VM、系统库、Java SE 库或API、程序本地代码、甚至操作系统里的 bug。极端因素如操作系统资源耗尽也可以导致崩溃。

因 HotSpot VM 或 Java SE库代码导致的崩溃是罕见的。本章提供如何检查崩溃的建议。有时候可以变通崩溃直到导致崩溃的源被诊断和修复(也就是可以避开崩溃)。

通常,崩溃的第一步是定位致命错误日志。这是HotSpot VM生成的文本文件。附录C-致命错误日志 解释了如何定位文件和文件的详细描述。

4.1 崩溃样本

本节展示一些样本来说明错误日志是如何用于启发崩溃原因的。

4.1 测定哪里发生崩溃

错误日志头显示了有问题的帧。见 C.3 格式头。

如果顶层帧是本地帧且不是操作系统本地帧,这表明问题可能发生在本地库,而不是在JVM里。解决崩溃的第一步是研究本地库发生崩溃的源。有三个选择,取决于本地库的源。

如果本地库是由你的程序提供,研究你的本地库的源代码。选项 -Xcheck:jni 可以帮助查找本地 bug。见 B.2.1 -Xcheck:jni 选项。

如果你的程序使用的本地库是由其他供应商提供,报告bug,提供致命错误日志信息。

继续阅读

Java SE 6 故障排除指南 – 5、挂起或循环进程故障排除

本章为挂起或循环进程的故障排除在特定程序上提供了信息和指导。

问题在涉及挂起或循环进程时发生。挂起可能因为多种原因发生,但经常是源于程序代码、API代码或库代码里的死锁。挂起甚至是因为 HotSpot VM的bug。

有时候,一个表面上是挂起的可能是个循环。例如,VM进程里的bug导致一个或多个线程进入死循环,会消耗掉所有可得CPU周期。

诊断挂起的最初步骤是找出VM进程是空闲还是消耗了所有可得CPU周期,为做这个要求使用操作系统工具。如果进程表现为繁忙且消耗了所有可得CPU周期,那么问题很可能是循环线程而不是死锁。

继续阅读

Java SE 6 故障排除指南 – 3、内存泄露

内存泄露故障排除

如果你的应用程序执行的时间越来越长,或如果操作系统执行越来越慢,这可能是内存泄露的指示。换句话说,虚拟内存被分配但在不需要时没有归还。最终应用程序或系统没有可用内存,应用程序非正常终止。

这篇文章提供了一些涉及内存泄露的问题诊断的建议。

3.1 OutOfMemoryError 的含义

一个最常见的内存泄露的指示是 java.lang.OutOfMemoryError 错误。这个错误在Java堆或堆的特定区域没有足够空间用于分配对象时抛出。垃圾收集器不能创造更多可用空间来容纳一个新的对象,堆也不能扩展。

当 java.lang.OutOfMemoryError 错误抛出时,栈轨迹也会被打印。

java.lang.OutOfMemoryError 也可以被本地库代码抛出,当本地分配不能满足时,例如,交换空间很低。

诊断 java.lang.OutOfMemoryError 的一个早期步骤是确定错误的含义。它是否意味着Java堆满了,或意味着本地堆满了?为帮助你回答这个问题,下面的子章节解释了一些可能的错误信息,还有连接到更详细信息的链接:

继续阅读