诊断由于忘记调用 CountDownLatch.countdown() 方法引起的服务器卡住(stuck)
正常情况下, 这些 countdown() 方法都不会被忘记调用的, 很多情况都是由于考虑不周全, 对于异常的情况处理不正确引起的. 这和各种连接泄漏(数据库连接泄漏, 外部服务连接池) 是一样的问题, 只不过这是对于关键资源的泄漏.
正常情况下, 这些 countdown() 方法都不会被忘记调用的, 很多情况都是由于考虑不周全, 对于异常的情况处理不正确引起的. 这和各种连接泄漏(数据库连接泄漏, 外部服务连接池) 是一样的问题, 只不过这是对于关键资源的泄漏.
最近在处理某个Java 应用的 UnknownHostException 异常的时候, 发现公司的应用框架团队提供了一个 MXBean, 这个 MXBean 可以提供该应用的 DNS 查询记录, 包括成功的记录, 失败的记录, 记录的失效时间等信息.
DNS 的查询信息都在 InetAddress 这个 Java 类的 2 个私有的静态字段里面. addressCache 和 negativeCache 分别表示成功的 DNS 查询和失败的 DNS 查询.
private static Cache addressCache = new Cache(Cache.Type.Positive);
private static Cache negativeCache = new Cache(Cache.Type.Negative);
JVM 层面默认缓存 DNS 查询结果的, 有些 JVM 会一直缓存这些结果. 所以可以通过设置 2 个参数来配置缓存多久. 这个 2 个参数分别是:
networkaddress.cache.ttl=60
networkaddress.cache.negative.ttl (default: 10)
可以通过修改这个配置文件 $JAVA_HOME/jre/lib/security/java.security 来修改, 或者通过启动 JVM 的时候 给参数来设置.
更多相关信息, 可以参考:
https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/java-dg-jvm-ttl.html
https://docs.oracle.com/javase/7/docs/technotes/guides/net/properties.html
Google search "Java read DNS cache", 第一个结果就给出了很好的代码, 就是这个问答:
https://stackoverflow.com/questions/1835421/java-dns-cache-viewer
它通过不断的反射, 去拿到里面那些私有的数据.
import java.lang.reflect.Field;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
public class DNSCache {
public static void main(String[] args) throws Exception {
// put some values in the internal DNS cache
// good DNS name
InetAddress.getByName("stackoverflow.com");
InetAddress.getByName("www.google.com");
InetAddress.getByName("www.rgagnon.com");
try {
// bad DNS name
InetAddress.getByName("bad.rgagnon.com");
}
catch (UnknownHostException e) {
// do nothing
}
// dump the good DNS entries
String addressCache = "addressCache";
System.out.println("---------" + addressCache + "---------");
printDNSCache(addressCache);
// dump the bad DNS entries
String negativeCache = "negativeCache";
System.out.println("---------" + negativeCache + "---------");
printDNSCache(negativeCache);
}
/**
* By introspection, dump the InetAddress internal DNS cache
*
* @param cacheName can be addressCache or negativeCache
* @throws Exception
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
private static void printDNSCache(String cacheName) throws Exception {
Class<InetAddress> iaclass = InetAddress.class;
Field acf = iaclass.getDeclaredField(cacheName);
acf.setAccessible(true);
Object addressCache = acf.get(null);
Class cacheClass = addressCache.getClass();
Field cf = cacheClass.getDeclaredField("cache");
cf.setAccessible(true);
Map<String, Object> cache = (Map<String, Object>) cf.get(addressCache);
for (Map.Entry<String, Object> hi : cache.entrySet()) {
Object cacheEntry = hi.getValue();
Class cacheEntryClass = cacheEntry.getClass();
Field expf = cacheEntryClass.getDeclaredField("expiration");
expf.setAccessible(true);
long expires = (Long) expf.get(cacheEntry);
Field af = cacheEntryClass.getDeclaredField("addresses"); // JDK 1.7, older version maybe "address"
af.setAccessible(true);
InetAddress[] addresses = (InetAddress[]) af.get(cacheEntry);
List<String> ads = new ArrayList<String>(addresses.length);
for (InetAddress address : addresses) {
ads.add(address.getHostAddress());
}
System.out.println(hi.getKey() + " "+new Date(expires) +" " +ads);
}
}
}
很多时候, 我们看到 Java 应用程序的 CPU 使用率很高, 或者是 GC 导致的, 或者是死循环导致的, 或者是执行某些特耗 CPU 的操作, 比如使用正则表达式匹配. 那么如何找这个使用率特别高的线程呢?
在 Linux 操作系统下, 使用 top -i 命令很容易找到CPU 使用率最高的进程, 如果使用 top -i -H 就很容易找到这个线程.
使用 Linux 系统自带的 strace, ftrae, ltrace 等, 可以查看 OS 级别的系统调用, 函数, 库等调用, 可是无法查看用户空间的更多信息.
通常有 2 种方法.
在 Java 里, 我们可以通过程序的方式列出当前应用里面的所有正在运行的 Java 线程, 同时通过 MBean API 可以知道任何一个线程在开始之后占用的 CPU 时间. 如果我们在某个时间间隔的开始记录所有线程已经使用的时间, 在间隔的结尾再次记录, 那么就可以知道任何线程在这段时间内使用 CPU 的占用情况. 在这个间隔中间终止的线程, 不能给出正确的结果. 幸好大部分 Java 应用程序大部分使用线程池. 同时在间隔结尾处, 可以记录下线程栈的, 能大概给出一些信息. 如果一个线程长时间使用 CPU 很高, 可以做多次, 然后大概看出那部分代码比较耗 CPU.
Java 11开始, 引入了一个新的实验性质的垃圾收集器 Z Garbage Collector. 它能处理几 GB 到几 TB 的 heap, 保证 stop the world 的时间不超过10ms, 并且这个时间不随着 heap 的大小而增加.
Java 是在 VM 里面运行的语言, 同时要做到平台无关性, 所以它有自己的 debug 接口和实现.
官方关于 JPDA 的链接: https://docs.oracle.com/javase/6/docs/technotes/guides/jpda/
上面的图很容易理解, VM 具体实现和 backend 接口直接使用 JVM TI 来作为通信接口.
Debugger 和 Debuggee 之间定义了协议: JDWP (这类似于 HTTP).
Debugger 自己的和后端通信以及自己的 UI 使用 JDI(这类似与 HTML)
如何远程 debug:
从 Java 9 开始:
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=127.0.0.1:8000 myApp
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:8000 myApp
Java 9 之前:
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000 OurApplication
Java 5 之前:
java -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000 myApp
参数列表:
a list of options:
客户端最简陋的可以使用 jdk 自带 jdb.
更多信息参考: https://www.baeldung.com/java-application-remote-debugging