分类 Java 相关 下的文章

jcmd 命令

jps 首先做到 pid
jcmd help
jcmd $pid help
jcmd $pid help GC.heap_dump
//列出 JVM flags
jcmd $pid help VM.flags
jcmd $pid help VM.flags -all // 列出所有

jcmd $pid GC.heap_dump heap.hprof
可能是最新的 JVM 版本 支持使用 -all 选项, 不做full GC, dump 所有对象. 类似于 jmap 不用 live 选项
jcmd $pid GC.heap_dump -all
//-all : [optional] Dump all objects, including unreachable objects (BOOLEAN, false)

JVM 中的 system 和 main 线程组 ThreadGroup

Java有 Thread 和 ThreadGroup. ThreadGroup 又可以嵌套. 今天在查看 线程 dump 的时候, 看到2个系统的 thread group: System 和 main. 仔细google 了一下, 貌似JVM 自身的, 比如 GC Daemon, Finalizer, Reference Handler, RMI TCP Accept-0 这些都是 JVM 自带的, 都在 system group 下. main group 是 system group 下的子 group, 它包含: DefaultThreadPool%d%d.

refer: https://www.javaworld.com/article/2074481/java-concurrency/java-101--understanding-java-threads--part-4---thread-groups--volatility--and-threa.html

关于 http 1.1 的 Keep-Alive 的持久连接 以及客户端和服务器端的处理

关于 http 1.1 的 Keep-Alive 有以下几点要注意:

  1. default 行为
  2. 可设置timeout 和 max request 数: Keep-Alive: timeout=5, max=1000

那么若是 Keep-Alive 的长连接, 如何区分一个 request payload 数据是不是已经结束呢?

server端:

以 tomcat8 为例: 在 tomcat8 的代码里面,
org.apache.coyote.http11.Http11Processor 的 prepareRequest() 方法里,
可以看到处理 HTTP header: transfer-encoding 的逻辑: 根据是 identity 或者 chunked
来使用不同的 filter 类来判断 payload 有没有结束, 所以在 servelet 的 service() 或 doPost
方法里, 拿到 inputStream 的时候, 已经不用担心 payload 结束的问题. 具体的 Filter 类在 package
org.apache.coyote.http11.filters里面, 比如 处理 input 的就有:
ChunkedInputFilter, IdentityInputFilter.

client 端

以 apache httpClient 5 为例: 因为 Client 端不像服务器端有过滤处理, 所以 client 端要自己处理,
所以在 httpClient 的代码里面, 就有专门处理 payload 是否结束的代码. 有个类:
DefaultContentLengthStrategy, 它有个方法 determineLength(final HttpMessage
message), 用来判断是使用 chunked 还是 content-length. 然后根据这个返回结果,
使用不同的解码器或者使用不同的 Stream 来封装 payload. stream 如:
org.apache.hc.core5.http.impl.io.ChunkedInputStream 和
org.apache.hc.core5.http.impl.io.IdentityInputStream; 解码器如:
org.apache.hc.core5.http.impl.nio.LengthDelimitedDecoder 和
org.apache.hc.core5.http.impl.nio.ChunkDecoder

JVM 网络连接设置的一些属性

  1. java.net.preferIPv4Stack (default: false) 有 IPv6 就用 IPv6;
  2. java.net.preferIPv6Addresses (default: false) 为了向前兼容性;
  3. networkaddress.cache.ttl 缓存成功的 lookup 多少秒. -1=永远缓存. 若security manager被安装,则永远缓存, 若没安装, 则缓存固定时间;
  4. networkaddress.cache.negative.ttl (default: 10) 失败的 lookup 会缓存多少秒. 0=不缓存, -1=永远缓存;
  5. http.agent (default: Java1.4.0) 当发出 http request 的时候,在 header 中显示的 agent;
  6. http.keepAlive (default: true) 是否使用 http 持久连接;
  7. http.maxConnections (default: 5) 同一地址, 在 keep-live 情况下,保持多少 idle 的连接数;
    其他:
    http.proxyHost (default: )
    http.proxyPort (default: 80 if http.proxyHost specified)
    http.nonProxyHosts (default:

ftp.proxyHost (default: )
ftp.proxyPort (default: 80 if ftp.proxyHost specified)
ftp.nonProxyHosts (default: )

官方解释在这里: https://docs.oracle.com/javase/7/docs/technotes/guides/net/properties.html

关于 JVM 回收中 跨代之间的引用 card marking, dirty card, write barrier

JVM GC 回收对象的时候, 都是先找出 Root 对象, 然后按照引用关系作出引用关系图, 得到live 对象, 剩下的都是 dead 对象, 继而回收掉这些 dead 对象.

分代回收的时候, 先回收年轻代, 为什么回收年轻代那么快? 仅仅因为年轻代比较小吗? 回收年轻代的时候, 要扫描所有的老年代对象吗? 这里就涉及到 dirty card.
年轻代回收的时候, 首先找出 root 对象, 然后对于从老年代到年轻代的引用, 可以有下面几种方式来发现老年代到年轻代的引用:

  1. 从 root 对象开始找到从老年代到年轻代的引用关系 - 比较耗时, 相当于整个 heap 都做引用关系图;
  2. 忽略 root 对象, 扫描所有老年代对象, 然后找到从老年代到年轻代的引用 - 也挺多的.

openJDK 采用的是上面第二种方式的一个变种. 对老年代 heap 设立一个 dirty card 表. 例如对老年代每256K 映射到 dirty card 中的一个 bit, 如果老年代有对年轻代的引用的时候(老年代对象字段值的修改), 把这个 bit 修改代表 dirty.

一个过程如下:
假如刚做完一次 full GC, 年轻代都是空的, 那么如下:

  1. 如果有新分配对象, 它一定在年轻代, 如果有老年代对象指向这个新对象(字段赋值), 那么这个老年代对象所对应内存的对应
    dirty card bit 被修改为 dirty;
  2. 等到 minor GC 发生的时候, GC 把老年代 dirty card 里面 dirty bit 对应的内存部分的老年代对象和加入到 root 里面, 就可以了. 这样避免了扫描全部. 一旦年轻代 mark 阶段结束, 整个 dirty card 全部恢复为空;
  3. 一旦年轻代开始从 eden 和 from survival copy 到 to survival 或者 promote 部分对象到老年代, 那么 dirty card 的 dirty bit 就开始工作了. 有指针引用的就会变成 dirty bit;

同时年轻代比较小, 所以一次能容纳的对象比较少, 所以 dirty card 里面 dirty bit 不会太多.

随便摘要, 更详细信息可以从这里看:
https://www.ibm.com/developerworks/library/j-jtp11253/http://blog.ragozin.info/2011/06/understanding-gc-pauses-in-jvm-hotspots.html