分类 Java 相关 下的文章

IBM J9 JDK thread dump analysis

工作中偶尔遇到一些基于 IBM J9 的 JDK 应用, 由于大部分时间都是和 Linux + Hotspot JDK 打交道, 对于 IBM J9 JDK 不是很熟悉, 不过 IBM 有很多工具可以分析这些东西, 记录一下, 以备以往.

首先, 它的 thread dump 在一个叫 Java core dump 的文件里, 这里并不是 *nix 的一般意义上的 core dump, 只是 java 的 thread 相关的一些 dump 内容. 如何 获得一个 thread dump, 参看这里: https://www.ibm.com/support/knowledgecenter/SS3JSW_5.2.0/com.ibm.help.performance_mgmt.doc/SIPM_ThreadDump.html

# 获得文件名以 javacore*** 开头
kill -QUIT <java_pid>
find /app -name javacore*

分析工具使用:
IBM Thread and Monitor Dump Analyzer for Java, 下载地址: ftp://public.dhe.ibm.com/software/websphere/appserv/support/tools/jca/jca457.jar

官方地址: https://www.ibm.com/support/pages/java-core-debugging-using-ibm-thread-and-monitor-dump-analyzer-java

关于 JVM TLAB

  1. 什么是 TLAB?
    每个线程在 Eden 区申请的属于该线程的一块空间, 该线程在自己的 TLAB 申请空间, 不需要同步. 但是申请 TLAB 需要锁同步, 在 TLAB 之外申请空间, 需要锁同步.
  2. 关于 TLAB 的参数
    使用如下命令可以看到所有跟 TLAB 相关的参数:
$java -XX:+PrintFlagsFinal -version | grep TLAB

比如我在 JDK 11 上, 可以看到如下有关 TLAB 的参数:

   size_t MinTLABSize                              = 2048                                      {product} {default}
     bool ResizeTLAB                               = true                                   {pd product} {default}
    uintx TLABAllocationWeight                     = 35                                        {product} {default}
    uintx TLABRefillWasteFraction                  = 64                                        {product} {default}
   size_t TLABSize                                 = 0                                         {product} {default}
     bool TLABStats                                = true                                      {product} {default}
    uintx TLABWasteIncrement                       = 4                                         {product} {default}
    uintx TLABWasteTargetPercent                   = 1                                         {product} {default}
     bool UseTLAB                                  = true                                   {pd product} {default}
     bool ZeroTLAB                                 = false                                     {product} {default}
  1. 什么是 PLAB?

关于 Java String 拼接

假设我有如下代码:

String result = "!";
for (int i = 0; i < 1000; i++) {
    result += "abc";
}
blackHole.consume(result);

可以看到里面有很多次字符串拼接, 那么这个 String 会被自动优化为 StringBuilder 类型, 使用 append(String) 方法. 具体实现是使用其父类 AbstractStringBuilder 的 append(String) 方法:

    public AbstractStringBuilder append(String str) {
        if (str == null)
            return appendNull();
        int len = str.length();
        ensureCapacityInternal(count + len);
        str.getChars(0, len, value, count);
        count += len;
        return this;
    }

这里的 str.getChars() 就是字符串复制的方法, 就是 String.getChars() 方法, 里面就这么一句重要的:

System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);

System.arraycopy 是 native 实现:

    public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);

这个 native 实现具体就在 openJDK 的 src/share/vm/prims/jvm.cpp 里面:

JVM_ENTRY(void, JVM_ArrayCopy(JNIEnv *env, jclass ignored, jobject src, jint src_pos,
                               jobject dst, jint dst_pos, jint length))
  JVMWrapper("JVM_ArrayCopy");
  // Check if we have null pointers
  if (src == NULL || dst == NULL) {
    THROW(vmSymbols::java_lang_NullPointerException());
  }
  arrayOop s = arrayOop(JNIHandles::resolve_non_null(src));
  arrayOop d = arrayOop(JNIHandles::resolve_non_null(dst));
  assert(s->is_oop(), "JVM_ArrayCopy: src not an oop");
  assert(d->is_oop(), "JVM_ArrayCopy: dst not an oop");
  // Do copy
  s->klass()->copy_array(s, src_pos, d, dst_pos, length, thread);
JVM_END

在具体下去, 就是在 src/share/vm/oops/objArrayKlass.cpp 里面的 copyarray 方法:

void ObjArrayKlass::copy_array(arrayOop s, int src_pos, arrayOop d,
            int dst_pos, int length, TRAPS) {...}

上面这个方法调用了 do_copy 方法, 里面会根据具体的类型去选择具体的 copy 方法. 如果我们只是上面例子的字母, 应该会选择到 src/share/vm/utilities/copy.hpp 里面的各种具体的 copy 方法:

// Assembly code for platforms that need it.
extern "C" {
  void _Copy_conjoint_words(HeapWord* from, HeapWord* to, size_t count);
  void _Copy_disjoint_words(HeapWord* from, HeapWord* to, size_t count);

  void _Copy_conjoint_words_atomic(HeapWord* from, HeapWord* to, size_t count);
  void _Copy_disjoint_words_atomic(HeapWord* from, HeapWord* to, size_t count);

  void _Copy_aligned_conjoint_words(HeapWord* from, HeapWord* to, size_t count);
  void _Copy_aligned_disjoint_words(HeapWord* from, HeapWord* to, size_t count);

  void _Copy_conjoint_bytes(void* from, void* to, size_t count);

  void _Copy_conjoint_bytes_atomic  (void*   from, void*   to, size_t count);
  void _Copy_conjoint_jshorts_atomic(jshort* from, jshort* to, size_t count);
  void _Copy_conjoint_jints_atomic  (jint*   from, jint*   to, size_t count);
  void _Copy_conjoint_jlongs_atomic (jlong*  from, jlong*  to, size_t count);
  void _Copy_conjoint_oops_atomic   (oop*    from, oop*    to, size_t count);

  void _Copy_arrayof_conjoint_bytes  (HeapWord* from, HeapWord* to, size_t count);
  void _Copy_arrayof_conjoint_jshorts(HeapWord* from, HeapWord* to, size_t count);
  void _Copy_arrayof_conjoint_jints  (HeapWord* from, HeapWord* to, size_t count);
  void _Copy_arrayof_conjoint_jlongs (HeapWord* from, HeapWord* to, size_t count);
  void _Copy_arrayof_conjoint_oops   (HeapWord* from, HeapWord* to, size_t count);
}

============== 从底向上看 ========
最终这些字符走的是(这里是 X86 架构,也有其它架构的) src/cpu/x86/vm/stubGenerator_x86_64.cpp 的 jshort_disjoint_arraycopy 方法, 那么这些 stub 是定义在:

StubRoutines::_jshort_disjoint_arraycopy

上面这个函数是定义在: src/share/vm/runtime/stubRoutines.hpp 文件里的.

Apache HttpClient 连接池泄漏诊断思路

经常在线上看到一些应用直接因为连接池无法获得连接, 导致整个应用不在响应任何请求. 常见的有数据库连接池连接泄漏, Http 连接池泄漏. 对于这种连接泄漏的问题, 一般是应用没有考虑到某些特殊情况, 特殊异常的处理导致不能用完之后返回连接到连接池. 这里就针对 Apache HttpClient 连接池泄漏这种清楚, 分析一下基本的求解思路.

- 阅读剩余部分 -

诊断由 Apache HttpAsyncClient 引起的内存泄漏

异步 IO 的使用, 使得线程不再 block 在 IO 上面, 可以做更多的事情, 所以 Java 的 NIO 在很多地方都使用起来了. 同时由于微服务的广泛普及, 企业内部各种服务直接的相互调用更多了. 之前很多都是使用 Apache 社区的 HttpClient 来相互调用, 如今更多的代码转向了 HttpAsyncClient. 这里就记录一个由于 HttpAsyncClient 的错误使用引起的内存泄漏的案例.

- 阅读剩余部分 -