Troubleshooting 整理
- kill -3 pid
- 发送一个SIGQUIT信号给Java应用, 通常会有当前的Thread Dump输出
- 假定这个程序在JVM初始化之后没有别的代码注册了新的SIGQUIT的signal handler,那么HotSpot VM在收到SIGQUIT之后会在一个专门的signal handler thread处理。该线程的入口函数为signal_thread_entry()
- http://hg.openjdk.java.net/jdk7u/jdk7u/hotspot/file/tip/src/share/vm/runtime/os.cpp
- 打印线程栈的动作由VM_PrintThreads实现,在VM thread上执行。于是问题就来了:如果这个JVM实例已经hang了,那它将无法响应任何外部请求,对它发SIGQUIT当然也得不到处理。所以有时候kill -3看不到线程栈是正常的。
- 至于时效性,所有VM operation都是先被放到一个队列里,然后由VM thread逐个处理。如果当前该队列是空的,那kill -3就可以几乎“实时”执行爬栈动作,否则得等前面的VM operation先完成,那就会延迟一会儿。
- Linux core dump
- concept
- A core dump is the recorded state of the working memory of a computer program at a specific time, generally when the program has terminated abnormally (crashed). In practice, other key pieces of program state are usually dumped at the same time, including the processor registers, which may include the program counter and stack pointer, memory management information, and other processor and operating system flags and information. The name comes from the once-standard memory technology core memory. Core dumps are often used to diagnose or debug errors in computer programs.
- On many operating systems, a fatal error in a program automatically triggers a core dump, and by extension the phrase "to dump core" has come to mean, in many cases, any fatal error, regardless of whether a record of the program memory is created.
- 检查是否可以core dump
- ulimit -a (or unlimit -c只查看core file size)
core file size (blocks, -c) 0
... - 修改限制
- 临时 可以使用参数unlimited,取消该限制ulimit -c unlimited
- 永久 echo "ulimit -c 1024" >> /etc/profile (1024 限制产生的 core 文件的大小不能超过 1024kb)
- ulimit -a (or unlimit -c只查看core file size)
- 在一个程序崩溃时,它一般会在指定目录下生成一个 core 文件。 core 文件仅仅是一个内存映象 ( 同时加上调试信息 ) ,主要是用来调试的。
- 设置 Core Dump 的核心转储文件目录和命名规则
- /proc/sys/kernel/core_uses_pid 可以控制产生的 core 文件的文件名中是否添加 pid 作为扩展 ,如果添加则文件内容为 1 ,否则为 0
- /proc/sys/kernel/core_pattern 可以设置格式化的 core 文件保存位置或文件名 ,比如原来文件内容是 core-%e 可以这样修改 : echo "/corefile/core-%e-%p-%t" > core_pattern; 将会控制所产生的 core 文件会存放到 /corefile 目录下,产生的文件名为 core- 命令名 -pid- 时间戳
- kill -l 查看信号
- 看到SIGSEGV在其中,一般数组越界或是访问空指针都会产生这个信号
- 指示进程进行了一次无效的存储访问。名字SEGV表示“段违例(segmentation violation)
- gdb core调试
- HSDB R大-HSDB
- concept
- JVM调优-标准参数-的一些陷阱
- 各参数的默认值
- 1.参考HotSpot VM里的各个globals.hpp文件 本帖子列出一些
- 2.-XX:+PrintCommandLineFlags
- 显示出VM初始化完毕后所有跟最初的默认值不同的参数及它们的值
- 3.-XX:+PrintFlagsFinal (这个参数本身只从JDK 6 update 21开始才可以用)
- 前一个参数只显示跟默认值不同的,而这个参数则可以显示所有可设置的参数及它们的值
- 可以设置的参数默认是不包括diagnostic或experimental系的。要在-XX:+PrintFlagsFinal的输出里看到这两种参数的信息,分别需要显式指定-XX:+UnlockDiagnosticVMOptions / -XX:+UnlockExperimentalVMOptions
- 4,-XX:+PrintFlagsInitial
- 这个参数显示在处理参数之前所有可设置的参数及它们的值,然后直接退出程序。“参数处理”包括许多步骤,例如说检查参数之间是否有冲突,通过ergonomics调整某些参数的值,之类的
- 结合-XX:+PrintFlagsInitial与-XX:+PrintFlagsFinal,对比两者的差异,就可以知道ergonomics对哪些参数做了怎样的调整
- $ java -XX:+PrintFlagsInitial | grep UseCompressedOops
bool UseCompressedOops = false {lp64_product}
$ java -XX:+PrintFlagsFinal | grep UseCompressedOops
bool UseCompressedOops = true {lp64_product}
Oracle JDK从6 update 23开始在64位系统上会默认开启压缩指针
- 5.jinfo -flag 可以用来查看某个参数的值,也可以用来设定manageable系参数的值
- PrintFlagsInitial->ergonomics->命令指定->PrintFlagsFinal->jinfo
- 各参数的默认值
- 陷阱1
- -XX:+DisableExplicitGC 与 NIO的direct memory
- System.gc()的默认效果是引发一次stop-the-world的full GC,对整个GC堆做收集。有几个参数可以改变默认行为
- 关键点是,用了-XX:+DisableExplicitGC参数后,System.gc()的调用就会变成一个空调用,完全不会触发任何GC,可以看看native源码, Runtime.c里面;
- 为什么要关闭显示调用呢?
- 避免滥用System.gc()
- 防止一些第三方库滥调用System.gc()
- NIO的DirectByteBuffer里面有用到System.gc(),用来触发回收DirectByteBuffer对象,从而间接回收堆外内存(reference handler里调用了Cleaner.cleanup() ),如果System.gc()被关闭了,导致DirectByteBuffer对象回收不了,最后直接内存也回收不了; java.lang.OutOfMemoryError: Direct buffer memory
public class DisableExplicitGCDemo { public static void main(String[] args) { for (int i = 0; i < 100000; i++) { ByteBuffer.allocateDirect(128); } System.out.println("Done"); } }
- java -XX:MaxDirectMemorySize=10m -XX:+PrintGC -XX:+DisableExplicitGC DisableExplicitGCDemo
Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory
- DirectByteBuffer有几处值得注意的地方
- DirectByteBuffer没有finalizer,它的native memory的清理工作是通过sun.misc.Cleaner自动完成的
- sun.misc.Cleaner是一种基于PhantomReference的清理工具,比普通的finalizer轻量些
- A cleaner tracks a referent object and encapsulates a thunk of arbitrary cleanup code. Some time after the GC detects that a cleaner's referent has become phantom-reachable, the reference-handler thread will run the cleaner
- Oracle/Sun JDK 6中的HotSpot VM只会在old gen GC(full GC/major GC或者concurrent GC都算)的时候才会对old gen中的对象做reference processing,而在young GC/minor GC时只会对young gen里的对象做reference processing; 死在young gen中的DirectByteBuffer对象会在young GC时被处理; 也就是说,做full GC的话会对old gen做reference processing,进而能触发Cleaner对已死的DirectByteBuffer对象做清理工作。而如果很长一段时间里没做过GC或者只做了young GC的话则不会在old gen触发Cleaner的工作,那么就可能让本来已经死了的、但已经晋升到old gen的DirectByteBuffer关联的native memory得不到及时释放
- 为DirectByteBuffer分配空间过程中会显式调用System.gc(),以期通过full GC来强迫已经无用的DirectByteBuffer对象释放掉它们关联的native memory
- 这几个实现特征使得Oracle/Sun JDK 6依赖于System.gc()触发GC来保证DirectByteMemory的清理工作能及时完成。如果打开了-XX:+DisableExplicitGC,清理工作就可能得不到及时完成,于是就有机会见到direct memory的OOM
- 教训是:如果你在使用Oracle/Sun JDK 6,应用里有任何地方用了direct memory,那么使用-XX:+DisableExplicitGC要小心。如果用了该参数而且遇到direct memory的OOM,可以尝试去掉该参数看是否能避开这种OOM。如果担心System.gc()调用造成full GC频繁,可以尝试下面提到 -XX:+ExplicitGCInvokesConcurrent 参数 结合笨神的堆外内存完全解读
- -XX:+DisableExplicitGC 与 NIO的direct memory
- 陷阱2
- -XX:+DisableExplicitGC 与 Remote Method Invocation (RMI) 与 -Dsun.rmi.dgc.{server|client}.gcInterval=
- 实际上这里在做的是分布式GC。Sun JDK的分布式GC是用纯Java实现的,为RMI服务
- 前面gcCauses整理有整理这个dgc
- 陷阱3
- -XX:+ExplicitGCInvokesConcurrent 或 -XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses
- product(bool, ExplicitGCInvokesConcurrent, false, "A System.gc() request invokes a concurrent collection;" " (effective only when UseConcMarkSweepGC)")
- product(bool, ExplicitGCInvokesConcurrentAndUnloadsClasses, false, "A System.gc() request invokes a concurrent collection and also unloads classes during such a concurrent gc cycle " "(effective only when UseConcMarkSweepGC)")
- 跟上面的第一个例子的-XX:+DisableExplicitGC一样,这两个参数也是用来改变System.gc()的默认行为用的;不同的是这两个参数只能配合CMS使用(-XX:+UseConcMarkSweepGC),而且System.gc()还是会触发GC的,只不过不是触发一个完全stop-the-world的full GC,而是一次并发GC周期
- CMS GC周期中也会做reference processing。所以如果用这两个参数的其中一个,而不是用-XX:+DisableExplicitGC的话,就避开了由full GC带来的长GC pause,同时NIO direct memory的OOM也不会那么容易发生
- 陷阱4
- -XX:+GCLockerInvokesConcurrent
- product(bool, GCLockerInvokesConcurrent, false, "The exit of a JNI CS necessitating a scavenge also kicks off a bkgrd concurrent collection")
- jni critical release时, 触发gc; gcCauses整理有详细
- 陷阱5
- MaxDirectMemorySize 与 NIO direct memory 的默认上限
- product(intx, MaxDirectMemorySize, -1, "Maximum total size of NIO direct-buffer allocations")
- 但如果不配置它的话,direct memory默认最多能申请多少内存呢?这个参数默认值是-1,显然不是一个“有效值”。所以真正的默认值肯定是从别的地方来的
- 当MaxDirectMemorySize参数没被显式设置时它的值就是-1,在Java类库初始化时maxDirectMemory()被java.lang.System的静态构造器调用,走的路径就是这条;
- 结论:MaxDirectMemorySize没显式配置的时候,NIO direct memory可申请的空间的上限就是-Xmx减去一个survivor space的预留大小
- 这里建议读读源码看看
- 陷阱6
- -verbose:gc 与 -XX:+PrintGCDetails
- 经常能看到在推荐的标准参数里这两个参数一起出现。实际上它们有啥关系?
在Oracle/Sun JDK 6里,"java"这个启动程序遇到"-verbosegc"会将其转换为"-verbose:gc",将启动参数传给HotSpot VM后,HotSpot VM遇到"-verbose:gc"则会当作"-XX:+PrintGC"来处理。
也就是说 -verbosegc、-verbose:gc、-XX:+PrintGC 三者的作用是完全一样的。
而当HotSpot VM遇到 -XX:+PrintGCDetails 参数时,会顺带把 -XX:+PrintGC 给设置上。
也就是说 -XX:+PrintGCDetails 包含 -XX:+PrintGC,进而也就包含 -verbose:gc。
既然 -verbose:gc 都被包含了,何必在命令行参数里显式设置它呢?
- -XX:+UseFastEmptyMethods 与 -XX:+UseFastAccessorMethods
- 这个用的少, 只看下结论就行了
- 为了适应多层编译模式,JDK 7里这两个参数的默认值就被改为false了
- -XX:+UseCMSCompactAtFullCollection
- CMSFullGCsBeforeCompaction
- CMSParallelRemarkEnabled
- CMSScavengeBeforeRemark
- Attempt scavenge before the CMS remark step
- 如果一个应用统计到的young GC时间都比较短而CMS remark的时间比较长,那么可以试试打开这个参数,在做remark之前先做一次young GC。是否能有效缩短remark的时间视应用情况而异,所以开这个参数的话请一定做好测试
- -Xss 与 -XX:ThreadStackSize
- -Xmn 与 -XX:NewSize、-XX:MaxNewSize
- 如果同时设置了-XX:NewSize与-XX:MaxNewSize遇到“Could not reserve enough space for object heap”错误的话,请看看是不是这帖所说的问题。早期JDK 6似乎都受这问题影响,一直到JDK 6 update 14才修复
- -Xmn 与 -XX:NewRatio
- -XX:NewRatio 与 -XX:NewSize、-XX:OldSize
- jmap -heap看到的参数值与实际起作用的参数的关系?
- jmap -heap显示的部分参数是以MB为单位来显示的,而MaxNewSize的单位是byte
- 要注意的是,HotSpot VM有大量可调节的参数,并不是所有参数在某次运行的时候都有效。
- -XX:MaxTenuringThreshold 的默认值?
- Oracle/Sun JDK 6中,选择CMS之外的GC时,MaxTenuringThreshold(以下简称MTT)的默认值是15;而选择了CMS的时候,MTT的默认值是4而不是15。设定是在 Arguments::set_cms_and_parnew_gc_flags() 里做的
- 在Sun JDK 6之前(1.4.2、5),选择CMS的时候MTT的默认值则是0,也就是等于设定了-XX:+AlwaysTenure——所有eden里的活对象在经历第一次minor GC的时候就会直接晋升到old gen,而survivor space直接就没用了
- java -XX:+PrintFlagsFinal | egrep 'MaxTenuringThreshold|UseParallelGC|UseConcMarkSweepGC' (15)
- java -XX:+PrintFlagsFinal -XX:+UseConcMarkSweepGC | egrep 'MaxTenuringThreshold|UseParallelGC|UseConcMarkSweepGC' (本机是6)
- 建议这样查看vm参数值:java 我们的vm配置参数 -XX:PrintFlagsFinal | grep ''selective parameter (原因在于有的参数的值,会受到其他参数影响导致与自己的默认值不一样!)
- -XX:+CMSClassUnloadingEnabled
- CMS remark暂停时间会增加,所以如果类加载并不频繁、String的intern也没有大量使用的话,这个参数还是不开的好
- -XX:+UseCompressedOops 有益?有害?
- 我能做的建议是如果在64位Oracle/Sun JDK 6/7上,那个参数不要显式设置
- 有些库比较“聪明”,会自行读取VM参数来调整自己的一些参数,例如Berkeley DB Java Edition。但这些库实现得不好的时候反而会带来一些麻烦:BDB JE要求显式指定-XX:+UseCompressedOops才能有效的调整它的缓存大小。所以在用BDB JE并且Java堆+PermGen大小小于32GB的时候,请显式指定-XX:+UseCompressedOops吧
- -XX:+AlwaysPreTouch
- 会把commit的空间跑循环赋值为0以达到“pretouch”的目的。开这个参数会增加VM初始化时的开销,但后面涉及虚拟内存的开销可能降低
- -XX:+ParallelRefProcEnabled
- 这个功能可以加速reference processing,但在JDK6u25和6u26上不要使用,有bug:
Bug ID 7028845: CMS: 6984287 broke parallel reference processing in CMS
- 这个功能可以加速reference processing,但在JDK6u25和6u26上不要使用,有bug:
- -XX:+UseConcMarkSweepGC 与 -XX:+UseAdaptiveSizePolicy
- 这两个选项在现有的Oracle/Sun JDK 6和Oracle JDK 7上都不要搭配在一起使用——CMS用的adaptive size policy还没实现完,用的话可能会crash。
目前HotSpot VM上只有ParallelScavenge系的GC才可以配合-XX:+UseAdaptiveSizePolicy使用;也就是只有-XX:+UseParallelGC或者-XX:+UseParallelOldGC。Jon Masamitsu在邮件列表上提到过。
题外话:开着UseAdaptiveSizePolicy的ParallelScavenge会动态调整各空间的大小,有可能会造成两个survivor space的大小被调整得不一样大。Jon Masamitsu在这封邮件里解释了原因。
追加:JDK9里CMS终于要彻底不支持adaptive size policy了:https://bugs.openjdk.java.net/browse/JDK-8034246
- 这两个选项在现有的Oracle/Sun JDK 6和Oracle JDK 7上都不要搭配在一起使用——CMS用的adaptive size policy还没实现完,用的话可能会crash。
- -XX:HeapDumpPath 与 -XX:+HeapDumpBeforeFullGC、-XX:+HeapDumpAfterFullGC、-XX:+HeapDumpOnOutOfMemoryError
- 但很多人都会疑惑:做出来的heap dump存到哪里去了?
如果不想费神去摸索到底各种环境被配置成什么样、“working directory”到底在哪里的话,就在VM启动参数里加上 -XX:HeapDumpPath=一个绝对路径 吧
- 但很多人都会疑惑:做出来的heap dump存到哪里去了?