2019年12月

诊断由于 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);
      }
   }
}