2020年7月

关于 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}
  3. 什么是 PLAB?

tmp 目录文件无法执行, 报 Permission denied

今天在执行 aysnc-profiler 的时候, 遇到无法执行的问题: 为了方便文件清除, 把解压后的文件放到了 /tmp 目录, 然后把 owner 和 权限都加好, 之后切换java 进程的用户去执行, 报 Permission denied.

mkdir /tmp/profiler
tar -C /tmp/profiler -xvf async-profiler-1.7.1-linux-x64.tar.gz
sudo chmod -R 755 /tmp/profiler/*
sudo chown -R appuser /tmp/profiler
sudo su appuser
/tmp/profiler/profile.sh -d 60 -o tree -e cpu -f profile0717.log.html 
/tmp/profile/profiler.sh: Permission denied

如果使用 bash 去执行, 则报:

bash /tmp/profiler/profile.sh -d 60 -o tree -e cpu -f profile0717.log.html 
/tmp/profile/profiler.sh: line 67: /tmp/profile/build/jattach: Permission denied

既然文件 owner 和 读和执行权限都加好了, 为什么还报错呢?
原来在 /tmp 目录的挂载方式:

appsuer@test-host:/home/appuser$ mount | grep /tmp
tmpfs on /tmp type tmpfs (rw,nosuid,nodev,noexec)

它设置了 noexec 属性. 所以问题就知道出在那里了, 既然这样, 换个目录就解决了.

关于 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 文件里的.

为什么 CPU 不能到达100%

有时候为了让 CPU 贡献最大的能力, 我们会想尽办法让 CPU 在最高频上达到100%使用率, 可是无论怎么优化都达不到100%, 到底问题出在哪里?

  1. 各种 IO 拖慢了 CPU (waiting), 导致 CPU 在等取指令, 从网络到本地, 从磁盘加载到内存, 从内存加载到缓存, 从缓存到寄存器, 在内核态和用户态直接复制;
  2. 各种锁 线程/进程被 block;
  3. 线程数不够多;