Eric 发布的文章

Linux core dump

什么是 core dump

https://man7.org/linux/man-pages/man5/core.5.html

如何获得 core dump

由于 1) core dump 会占用大量的磁盘空间, 2) core dump 可能包含内存的里面某些敏感数据, 所以 Linux 尽管在得到要 trigger core dump 的信号情况下, 也由于 ulimit 的控制, 默认不产生 core dump. 因此, 如果要捕获 core dump, 首先要选择打开 core dump limit 设置.
使用 ulimit -a 命令查看当前 ulimit 设置值:

eric@host:~$ ulimit -a
core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 31660
max locked memory       (kbytes, -l) 16384
max memory size         (kbytes, -m) unlimited
open files                      (-n) 1024
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) 31660
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited
修改 ulimit 设置

在 /etc/security/ 目录下, 有个 limits.conf 和 limits.d 目录(如果有 limits.d 则 limits.conf 不生效, 可以认为 limits.conf 是模板). 在 limits.d 目录下新建一个以 .conf 结尾的文本文件, 添加要改的一行.

eric@host:~$ ls /etc/security/ | grep limit
limits.conf
limits.d

如何分析 core dump

关于 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?

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