java.util.concurrent.ReentrantLock 与 synchronized 的对比

两者都可以实现同步, 在有些方面二者还是有很多区别. synchronized 是 java 的一个语法级别的特性, ReentrantLock 是 util 里面的一个辅助类.

相同点:

  1. 二者都可以实现锁;
  2. 二者都可以实现 生产者/消费者模式 wait/notify/notifyAll, await/signal/signalAll

不同点:

  1. synchronized 必须是一对 {} 中间的部分, 2 个 synchronized 的代码块无法交叉, ReentrantLock 的写法更灵活, 可以交叉;
  2. Lock 可以设置是否回应 interrupt;
  3. Lock 可以使用 tryLock() 如果不能获得锁, 立马返回;
  4. Lock 可以使用 tryLock(timeout) 可以设置尝试获得锁的最长等待时间;
  5. 一个 Lock 可以通过 newCondition 设置多个条件队列, 让他们等待不同的事件;
  6. ReentrantLock 可以设置是否是公平锁;
  7. synchronized 在 JDK 6 优化之后, 可以实现锁粗化, 偏向锁, 根据逃逸分析锁消除;

如何生成 java heap dump

Java heap 是某个时间点上 JVM 内存的一个瞬时镜像(snapshot), 通过工具查看内存里面的各种对象以及他们之间的关系, 对于分析内存问题非常有帮助. 常见的 heap dump 都是二进制的 hprof 格式, 获得之后一般通过 jhat, JVisualVM 或者 MAT 分析. 那么第一步, 如何获得 heap dump 呢? 本文将介绍常见的获得 heap dump 的一些方法.

  1. jcmd
    jcmd 是 jdk 自带的一个小工具, 推荐使用. 要通过 jcmd 获得 heap dump, 首先要得到该进程的 ID. 获得进程 ID 之后, 可以通过 jcmd 命令获得 heap dump.
    LM-SHC-16501315:Downloads xiatian$ jcmd
    42596 
    98797 sun.tools.jcmd.JCmd
    LM-SHC-16501315:Downloads xiatian$ jcmd 42596 GC.heap_dump /tmp/heap.hprof
    42596:
    Heap dump file created
  1. jmap
    jmap 是 JDK 从早期开始一直附带的一个小工具, 使用下面的命令来获取 heap dump:
    jmap -dump:[live],format=b,file=<file-path>
    其中的 live 是可选, 代表是否包含即将被 GC 的对象, 若包含 live, 则不包含即将被 GC 的对象.
    在我的使用经验中, 有时候 jmap 会出现不能 attach 到目标进程的问题.
    例子:
LM-SHC-16501315:Downloads xiatian$ jmap -dump:live,format=b,file=/tmp/heapdump.hprof 42596
Dumping heap to /private/tmp/heapdump.hprof ...
Heap dump file created
  1. JVisualVM
    若在桌面环境, 并且已经安装 JDK, 可以使用 JDK 自带 GUI 工具 JVisualVM.
    heap.png

  2. JConsole
    JConsole 也是 JDK 自带 GUI 工具
    jconsole.png

  3. 可以在 JVM 启动时添加如下参数, 当发生 OOM 时候, 自动产生 heap dump:

-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=<file-or-dir-path>
  1. 编程的方式: 可以通过使用 MBean 去操作, 产生 heap dump.
public static void dumpHeap(String filePath, boolean live) throws IOException {
    MBeanServer server = ManagementFactory.getPlatformMBeanServer();
    HotSpotDiagnosticMXBean mxBean = ManagementFactory.newPlatformMXBeanProxy(
      server, "com.sun.management:type=HotSpotDiagnostic", HotSpotDiagnosticMXBean.class);
    mxBean.dumpHeap(filePath, live);
}

有时候, 你会看到 core dump 和 heap dump, 2个不一样的dump, 那么2个区别是什么呢?
Java 常见的三种 dump 文件: Core Dump, heap dump, thread dump

jstack

jstack 用来打印当前 Java 进程的所有 stacktrace 信息, 包括每层栈的类名, 方法名, 行号, 以及线程的状态, 当前线程拥有的锁, 还能打印部分 native stracktrace.

jstack [ option ] pid
jstack [ option ] executable core
jstack [ option ] [server-id@]remote-hostname-or-IP

最常使用的还是跟 pid 去打印当钱进程的栈. 比如:

jstack -l 42596
jstack -l 42596 > /tmp/stack.txt
jstack -m 42596 // 打印包括 native 栈在内的所有栈. 
jstack -F 42956 //Solaris & Linux 上 force 打印栈

这个功能可以通过最新的 jcmd 来实现.

jcmd  42596 Thread.print

jinfo

jinfo 是 JDK 提供的一个查看 JVM 系统配置信息和启动参数的命令, 并且它可以在 JVM 运行状态下修改某些参数. 在 JDK 8 之后, jinfo 的很多功能都可以被新的工具 jcmd 所拥有, 更推荐使用 jcmd.

获取 java 线程 42596 的相关信息:

host:Downloads xiatian$ jinfo 42596
Attaching to process ID 42596, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.141-b15
Java System Properties:

java.vendor = Oracle Corporation
java.vm.info = mixed mode
line.separator = 

java.vm.name = Java HotSpot(TM) 64-Bit Server VM
org.apache.commons.logging.Log = org.apache.commons.logging.impl.NoOpLog
-XX:+UseG1GC
-XX:+UseStringDeduplication
-XstartOnFirstThread
-Dorg.eclipse.swt.internal.carbon.smallFonts
-Dosgi.requiredJavaVersion=1.8
-Xms256m
-Xmx1024m
-Declipse.p2.max.threads=10
java.specification.version = 1.8

VM Flags:
Non-default VM flags: -XX:CICompilerCount=4 -XX:ConcGCThreads=2 -XX:G1HeapRegionSize=1048576 -XX:InitialHeapSize=268435456 -XX:MarkStackSize=4194304 -XX:MaxHeapSize=1073741824 -XX:MaxNewSize=643825664 -XX:MinHeapDeltaBytes=1048576 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:+UseG1GC -XX:+UseStringDeduplication 
Command line:  -Dosgi.requiredJavaVersion=1.8 -Dosgi.instance.area.default=@user.home/eclipse-workspace -XX:+UseG1GC -XX:+UseStringDeduplication -XstartOnFirstThread -Dorg.eclipse.swt.internal.carbon.smallFonts -Dosgi.requiredJavaVersion=1.8 -Xms256m -Xmx1024m -Xdock:icon=../Resources/Eclipse.icns -XstartOnFirstThread -Dorg.eclipse.swt.internal.carbon.smallFonts -Declipse.p2.max.threads=10 

jinfo 还可以设置某些 JVM 参数, 比如:

jinfo -flag +PrintGCDetails  42596

那些参数变量是可以运行时通过 jinfo 更改的呢? 可以通过下面的命令获取当前 JRE 支持的运行时可更改的 flags:

java -XX:+PrintFlagsFinal -version|grep manageable

manageable.png
或者查看官方页面(https://www.oracle.com/technetwork/java/javase/tech/vmoptions-jsp-140102.html), 凡是标明 Manageable 的, 都是可以运行时可更改的.

一个例子是: 如果你发现有 JVM 可能有 OOM 的风险, 那么你可以通过 jinfo 立即设置: HeapDumpOnOutOfMemoryError & HeapDumpPath, 那么当它 OOM 时, 它自动做一个 heap dump.