分类 Java 相关 下的文章

为什么Java CMS GC 使用单线程做 Full GC?

虽然今天 CMS GC 已经不被推荐, 可是还经常被用来和G1 GC 做对比. 其中导致CMS 不被推荐的重要原因是: CMS 的full GC 是单线程的, 在如今多核统治天下的情况下, 单线程的 full GC 肯定比多线程的 full GC 慢很多.

CMS 什么情况下会产生full GC

  1. 并发模式失败(concurrent mode failures) 老年代并发回收的速度跟不上年轻代晋升的速度;
  2. 碎片化, 老年代虽然有足够的空间, 可是都是碎片化的, 没有一个连续的可容纳的空间;
  3. 元数据区(metaspace)/老年代没有空间;
  4. 明确的 System.gc() 或 Runtime.gc() 调用

为什么不把 parallel old 的算法引入CMS, 使之可以多线程

二者的数据结构不一样, 如果引入需要大量修改

为什么不重新为了 CMS 写一个多线程的 full gc?

据说开发工程师把更多的时间都用去写 G1 了, 并且 G1 各方面的期望都比CMS 好, 没有必要再写要给给CMS.

其它

google 内部有一个CMS 的老年代GC 实现, 并且曾经尝试patch到openJDK, 不过最后没有做到:
http://openjdk.java.net/jeps/8130200

最好还是用G1

JDBC 无法建立到 Oracle 数据库的连接

A 程序使用和 B 程序一样的数据库访问用户名, 密码, 主机名, 端口等配置, 可是 A 程序无法建立到数据库的连接, B 程序却可以.

A 程序的代码在建立连接的时候, 只是打印了一句: 无法建立连接, 再也没任其它信息.

首先, 怀疑是不是防火墙问题 -> 使用 telnet 和 curl 测试了一下端口, 发现端口完全可以建立正常的 tcp 连接;

虽然可以建立 tcp 连接, 可是并不一定不是防火墙问题, 因为防火墙可能根据协议规则等建立, 允许基本的 tcp 端口连接;

下一步, 在建立连接的时候, 进行 tcp 抓包, 重要找到了问题所在: 原来在建立连接之后的协商阶段, 发生了错误->

...AUTH_PID...1234....AUTH_SID.  /ORA-28040: No matching authentication protocol

原来是认证协议不匹配, 还给出了 Oracle 官方的错误 ID.

根据这个错误 ID, 我们搜索一下, 发现基本是客户端JDBC 版本和服务器版本不一致造成的.

最近Oracle 服务器升级到 19c, 可是客户端还是用的适配 11c 的 JDBC 连接, 所以导致出现了这个错误.

为什么程序 B 没问题呢? 程序 B 使用的是 JDK 8, 它的 jdbc 驱动也升级了, 所以完全没问题. 然而程序 A 是一个基于 JDK 7 的程序, 它无法升级到最新的 JDBC 驱动, 因为 Oracle 最新的 JDBC 驱动只支持最低 JDK 8.

所以, 为了解决这个问题, 程序 A 应该升级 JDK 最低为 JDK8.

关于 Java 应用的 线程栈内存大小

对于 Java 应用, 对于内存的占用, 主要是Java堆, 通常我们为了性能考虑, 会对堆设置最大值, 否则 JVM 就会根据它锁了解的系统参数, 设置一个可能的最大值. 除了堆之外, 其实还有好几部分, 也可能占用不少内存. 比如元数据区(老年代), 线程栈, 编译后的代码, GC 程序, 编译器, JVM符号表之类的. 这里可以通过添加启动参数: -XX:NativeMemoryTracking=summary or -XX:NativeMemoryTracking=detail 来追踪原生内存的占用. 比如:
nmt.png

比如上面的截图中, kiyomi 看到 Thread 部分 reserve 了 1052M, 已经提交占用了 1052M, 查看 thread dump, 发现现在大概有 1003 个线程, 基本每个线程占用 1M.

另外我们知道, 可以使用 Xss 参数设置每个线程栈的大小, 比如:
-Xss1m
-Xss1024k
-Xss1048576
上面的参数等同于:
-XX:ThreadStackSize

在 Linux 64 bit 上, 默认是 1MB. 从 Oracle 的官方文档: https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html
可以看到具体的说明.

从上面的文档可以看出, 每个栈在 64bit 的 Linux 上使用 1M, 可是我从另外一个机器上看到大概有 20K 个线程, 可是占用的内存并不是 20G. 于是我们看到 对于 IBM JDK 和 OpenJ9 JDK 它们都用 3 个参数:

  1. -Xiss<size> Set initial Java thread stack size 2 KB
  2. -Xss<size> Set maximum Java thread stack size 320 KB (31/32-bit); 1024 KB (64-bit)
  3. -Xssi<size> Set Java thread stack size increment 16 KB
    这里它们对 Xss 的解读是最大的可用栈大小. 所以, 就有个疑问, 对于 HotSpot JDK, 是每个栈必须占用 1M, 还是这也是最大值???

更新:
根据这个 Bug 描述: https://bugs.openjdk.java.net/browse/JDK-8191369
应该是最大值, 之前的 NMT 简单的把 reserved 和 commited 置成 线程数 * Xss. 其实真正的 committed 可能并么有这么多, 虽然声明使用 1M 虚拟内存, 可能实际使用的物理内存页比较少. 所以 commited 比较少.

比如下面这个例子, 该 JVM 在一个 container 里面, 设置的最大可用内存是 16G, 已经使用 13.58G. 当前线程数是 11266 个, 堆大小设置的是 8G, 可以看到 Thread 通过 NMT 显示的是大概 10G, 总的 commit 已经大约 20G. 其实这个 Thread 占用的都是根据线程数 * Xss 算出来的, 都是根据最大值算出来的, 所以其实最后算出的 commit 是不准的. 根据 top 可得到该进程的 RSS 大概是 10G. 所以可以推算 Thread 占用少于 2 个 G.

nmt1.png

修复之后的版本:
newNMT.png

Java 调用 native 代码真的有那么慢吗?

今天看到一个应用在测试环境下 Tomcat thread 全忙, 做了一个 CPU profiling 的火焰图, 看到基本所有的 Tomcat 线程都是 Runnable 状态, 并且都在下面 2 行代码.

"DefaultThreadPool-25" daemon prio=10 tid=0x00007f80b0108000 nid=0x2450 runnable [0x00007f8088ac3000]
   java.lang.Thread.State: RUNNABLE
    at sun.reflect.Reflection.getCallerClass(Native Method)
    at java.lang.Class.getConstructor(Class.java:1730)

没有看到死锁, 没有看到其他耗 CPU 的操作. 所以怀疑从 Java stack 到 Native stack 这步非常消耗 CPU. 于是研究了一下大概这种 Native 栈的 CPU 消耗.
从 performance 角度看, 在 Java 里面调用 Native 代码会有以下问题.

  1. 不能对短的方法做 inline;
  2. 要新建一个 native 栈;
  3. 不能对 native 方法做运行时优化;
  4. 要复制参数到 native 栈;

另外从一个 Stack Overflow 的问答中看到 有人测试 Native 代码可能导致 10 倍以上的性能降级, 不过我自己没有测试.

不过对于我这个例子, 我找到对应的生产应用, 并没有发现这种问题, 虽然能看到部分线程在做 thread dump 或者 CPU profiling 火焰图的时候停留在上面的 2 行. 并且生产环境中对应的请求是测试环境的 100 倍左右. 所以, 基本断定这个 Tomcat 线程全忙的问题, 并不是 Native 代码的性能问题导致的.

那么问题出在哪呢? 因为不管是做 Thread dump 还是 CPU 火焰图, 都是 CPU 运行栈的剪影, 并不能反映数据的情况. 真实的情况是: 上面 2 行代码处于一个循环中, 在生产环境中, 这个循环大概 100 以内, 可是测试环境下, 它要循环 5万次以上, 所以在 Thread dump 和 CPU profiling 中看到都是这块在运行.

参考: https://stackoverflow.com/questions/13973035/what-is-the-quantitative-overhead-of-making-a-jni-call