分类 Java 相关 下的文章

Java NIO & NIO2 里面的 attachment 是怎么回事?

在 Java NIO 和 NIO2 里面我们经常看到attach 和 attachment 相关的 API, 如:

Objcet SelectionKey.attch(Object obj)
Object SelectionKey.attachment()
SelectionKey AbstractSelector.register(AbstractSelectableChannel ch,int ops, Object att);

这里的 Attachment 是一个 Object 对象, 也就是说它可以 attach 任何对象, 为什么要 attach 一个对象呢?
我们从 Blocking IO 说起, 当 Blocking IO 的时候, 读字节流或字符流的的线程一直 block 在读操作上, 不去干其它事情, 当来一个字节/字符的时候, 它就读一个, 直到返回 -1 代表读完(EOF). 可是当时 NIO 的时候, 读线程直接返回, 是由一个专门的 IO 线程操作 Selector 去读取的, 当有部分数据到达的时候, 它就帮你读取, 读完来的数据之后, 还没读取到 EOF, 那么要继续监听这个 socket, 再次等待它接下来的数据, 那么刚才读取的数据放哪里呢? 之后来的数据怎么拼接到原来的数据之后呢? 那么就需要有一个容器放置还没有读完的半成品, 在下次同一个 socket 来数据的时候, 再次拿出这个容器, 继续放数据, 那么这个容器怎么可以容易的每次都有数据的时候拿到呢? 那就放到 SelectionKey 上面吧, 反正每次都是通过 Selectionkey 知道有数据的, 一旦 SelectionKey 有数据, 就通过它拿到之前装有半成品的容器, 那么可以继续放了.

每次读完通过 attach(Object) 方法附在上面, 下次当有数据来的时候, 通过 attachment 方法拿到之前的容器.

参考: https://jfarcand.wordpress.com/2006/07/06/tricks-and-tips-with-nio-part-ii-why-selectionkey-attach-is-evil/

使用MAT 分析heap dump

  1. 下载及设置
    官方下载: https://www.eclipse.org/mat/
    JDK 最好用最新的 JDK, 因为最新的基本优化最多.
    根据你分析的 heap dump 的大小, 有时候需要调整 MAT 的 heap 的大小. 这个参数在 MAT 根目录的 MemoryAnalyzer.ini 文件里面. 我经常分析 30G 以下的 heap, 基本设置为 27G, 是 JVM 使用压缩指针来加速.
    sjc-sreop-001.png

    另外, 对于 HPROF 的 dump 来说, 我经常设置为非严格 parse, 因为有时候有点错误, 不影响分析:
    sjc-sreop-002.png

    文档: MAT 自带文档在 Help -> Help Contents 菜单里面, 或者在线版本的文档

  2. 分析
    正常情况下当你打开一个 heap dump 之后, 它会问你是否自动诊断内存泄漏, 如果你不是为了诊断内存泄漏, 可以取消这步.
    Histogram: 是按照类的实例数量聚集, 能很快发现包含大量实例的类. 一般情况下 char[] 或者 String 都在最上面, 这基本没有问题.
    Dominator Tree: 对于诊断内存泄漏非常有用, 如果能抓到一个对象 dominate 很多实例, 基本你找到了问题所在.
    OQL: 就像查询数据库的 SQL 语言, 非常方便的查找任何对象, 实例;
    Threads: 查看当前heap的所有线程, 对于发现某个对象是怎么被创建, 或引用的非常有帮助.

更多关于如何使用 OQL: Java heap dump OQL samples - where
------ 未完待续 -------

jmap & jhat

jmap 用来输出 JVM 的heap 相关信息, 或者生成 heap dump. 可以对一个正在运行的 JVM 进程使用这个命令, 也可以对一个 core dump. 它的功能基本都被 jcmd 命令所替代

jmap -histo <pid>  //jcmd <pid> help GC.class_histogram
jmap -histo:live <pid>   //jcmd <pid> help GC.class_histogram -all
jmap -clstats <pid>   //jcmd <pid> GC.class_stats
jmap -finalizerinfo <pid>  //jcmd <pid> GC.finalizer_info
jmap -dump:format=b,file=/tmp/heapdump.hprfo <pid>  //jcmd <pid> GC.heap_dump
jmap -dump:live,format=b,file=/tmp/heapdump.hprfo <pid>  //jcmd <pid> GC.heap_dump -all

jhat 对 heap dump 分析, 然后起一个本地 web 服务器, 开 7000 端口, 使用户可以在浏览器通过 OQL 查看 heap 里面的信息. 因为功能不够强大, 基本都用其他工具, 比如 MAT 或 Java VisualVM.

java.util.concurrent.ReentrantLock 与 synchronized 的对比

两者都可以实现同步, 在有些方面二者还是有很多区别. synchronized 是 java 的一个语法级别的特性, ReentrantLock 是 util 里面的一个辅助类.

相同点:

  1. 二者都可以实现锁;
  2. 二者都可以实现 生产者/消费者模式 wait/notify/notifyAll, await/signal/signalAll

不同点:

  1. synchronized 必须是一对 {} 中间的部分, 2 个 synchronized 的代码块无法交叉, ReentrantLock 的写法更灵活, 可以交叉;
  2. Lock 可以设置是否回应 interrupt;
  3. Lock 可以使用 tryLock() 如果不能获得锁, 立马返回;
  4. Lock 可以使用 tryLock(timeout) 可以设置尝试获得锁的最长等待时间;
  5. 一个 Lock 可以通过 newCondition 设置多个条件队列, 让他们等待不同的事件;
  6. ReentrantLock 可以设置是否是公平锁;
  7. synchronized 在 JDK 6 优化之后, 可以实现锁粗化, 偏向锁, 根据逃逸分析锁消除;