解决 JVM AttachNotSupportedException 的问题

对于 JVM GC overhead 的问题, 通常要在 overhead 很高的时候做 heap dump, 这样捕获的 dump 文件才更有意义. 可是在 GC overhead 很高的时候, 它消耗的 CPU 也很高, 通常把机器的 CPU 全占满. 这个时候尝试去使用 jcmd, jmap 去做 heap dump, 很高的概率会得到这个异常:

com.sun.tools.attach.AttachNotSupportedException: Unable to open socket file: target process not responding or HotSpot VM not loaded
    at sun.tools.attach.LinuxVirtualMachine.<init>(LinuxVirtualMachine.java:106)
    at sun.tools.attach.LinuxAttachProvider.attachVirtualMachine(LinuxAttachProvider.java:63)
    at com.sun.tools.attach.VirtualMachine.attach(VirtualMachine.java:213)
    at sun.tools.jcmd.JCmd.executeCommandForPid(JCmd.java:140)
    at sun.tools.jcmd.JCmd.main(JCmd.java:129)

那么如何解决这个问题呢?

- 阅读剩余部分 -

诊断由于 blocking socket 未设置 socket timeout 而引起的线程卡住( thread stuck)

Java 既有早期的 blocking IO (面向流的Input/output stream, 面向字符的 Reader/Writer), 又有之后加入的 NIO/ NIO 2 (Channel & Buffer). 很多应用都是使用的 早期的 blocking 的 IO. 不论是 blocking IO 还是 NIO 都要注意 socket timeout 问题, 如果不设置, 都会引起应用卡在 IO 的问题.

- 阅读剩余部分 -

诊断运行时 java.lang.NoClassDefFoundError

一般看到 java.lang.NoClassDefFoundError 时, 下一步的操作很明确: 去看看这个类为什么不存在? 不在编译路径上? 这个类文件被损坏? 真的缺少这个类? 或者缺少包含这个类的包? 基本按照这个思路都能找到解决方法. 今天遇到这个情况是, 明明这个类完好无损的就在那里, 却始终抛出这个错误.

- 阅读剩余部分 -

读取 Java 应用的 DNS 查询缓存 (Query Cache)

最近在处理某个Java 应用的 UnknownHostException 异常的时候, 发现公司的应用框架团队提供了一个 MXBean, 这个 MXBean 可以提供该应用的 DNS 查询记录, 包括成功的记录, 失败的记录, 记录的失效时间等信息.

这些信息有什么用呢?

  1. 根据这些 DNS 查询信息, 大概了解我们的 Java 应用访问了哪些应用, 有没有不是期望的查询;
  2. 哪些 FQDN 查询是失败的? 为什么失败? 是不是不应该访问这些 FQDN;
  3. 根据这些失效时间, 以及多次刷新, 判断是不是一直调用其他服务;

如何获得这些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);

Java 里面的 DNS TTL 设置

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);
      }
   }
}