分类 Java 相关 下的文章

java debug JPDA (JavaTM Platform Debugger Architecture)

Java 是在 VM 里面运行的语言, 同时要做到平台无关性, 所以它有自己的 debug 接口和实现.
官方关于 JPDA 的链接: https://docs.oracle.com/javase/6/docs/technotes/guides/jpda/
jpda.png

上面的图很容易理解, 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:

  1. transport is the only fully required option. It defines which transport mechanism to use. dt_shmem only works on Windows and if both processes run on the same machine while dt_socket is compatible with all platforms and allows the processes to run on different machines
  2. server is not a mandatory option. This flag, when on, defines the way it attaches to the debugger. It either exposes the process through the address defined in the address option. Otherwise, JDWP exposes a default one
  3. suspend defines whether the JVM should suspend and wait for a debugger to attach or not
  4. address is the option containing the address, generally a port, exposed by the debuggee. It can also represent an address translated as a string of characters (like javadebug if we use server=y without providing an address on Windows)

客户端最简陋的可以使用 jdk 自带 jdb.
更多信息参考: https://www.baeldung.com/java-application-remote-debugging

常见的压缩/解压 jar 文件

有关 jar 文件

解压 jar 文件:

    jar xvf xxx.jar 
    unzip xxx.jar -d ./directoryToExtractTo
    jar tf jar-file  # 仅仅查看文件

//使用 jar 命令只能解压到当前目录

压缩为 jar 文件:

    jar cvf xxx.jar .
    jar cf jar-file input-file(s)

如果是使用 Spring boot big jar 的方式, 要制定 manifest 文件

    jar cfm xxx.jar Manifest.txt MyPackage/*.class
    jar cmf jar-file existing-manifest input-file(s)

更新 jar 文件 使用 input-file(s) 添加或更新 jar 里面的文件

  jar uf jar-file input-file(s)
  # 只更新 my.jar 里的这个文件
  jar -uf my.jar BOOT-INF/classes/application-Production.properties 

more info:

  1. https://docs.oracle.com/javase/tutorial/deployment/jar/appman.html
  2. https://docs.oracle.com/javase/tutorial/deployment/jar/index.html
  3. https://docs.oracle.com/javase/8/docs/technotes/tools/unix/jar.html

jar (Java™ Archive) 文件格式是 Java 提供的压缩方式, 和 zip 使用的格式是一样的. 不过里面添加了一些其他特性, 比如使用 MANIFEST.MF 添加了扩展机制, 使使用者能够更方便的扩展.

  1. MANIFEST.MF 作为 jar 文件的元数据, 默认路径是: META-INF/MANIFEST.MF
  2. 可以在 MANIFEST.MF 里添加 Class-Path 来设置依赖的其他 jar 文件, 打包进去;

Tomcat 7 NIO handling request on Linux

围绕这个图, 做几点解说(左边的虚线框表示 Tomcat):
Tomcat_Request_handling.png

  1. 从操作系统层面来说, 收到 TCP 的 SYN 之后, 放到 TCP SYN backlog 队列, 然后发送 (syn + ack) 包, 等待对方的 ack 包;
  2. SYN backlog 长度由 /proc/sys/net/ipv4/tcp_max_syn_backlog 设置, 还有文章说最终值有几个因素共同决定, 不过在 Linux Kernel 4.3 之后,这个 syn backlog 不在由 net.ipv4.tcp_max_syn_backlog 决定, 而是由 net.core.somaxconn 决定

    eric@host:~$ sysctl net.core.somaxconn
    net.core.somaxconn = 4096
    eric@host:~$ sysctl net.ipv4.tcp_max_syn_backlog
    net.ipv4.tcp_max_syn_backlog = 4096
  3. 如何查看一个监听端口的 SYN backlog 当前的队列长度?
    没有直接看当前长度的命令, 不过可以自己手工计算:

    # 查看连到当前 host 8080 端口上并且处于 sync-recv 状态的连接
    eric@host:~$ ss -n state syn-recv sport = :8080
  4. 如果 SYN backlog 已经满了, 那么操作系统层面会自动丢弃接收到的 syn 包, 那么客户端以为对端没收到, 会继续尝试.
  5. 当操作系统回复 syn+ack 之后, 对端返回 ack, 完成三次包握手, 这时, 这个连接变成 Established 的状态, 该连接从 syn backlog 进入 listen backlog;
  6. listen backlog 的长度由 listen 系统调用的参数决定, 在 Java 里由 ServerSocket(int port, int backlog)的构造函数的 backlog 参数决定. java 默认是 50. Tomcat 7 里面由 Connector 参数 acceptCount 决定, 默认是 100;
  7. 如果这个 listen backlog 满了, OS 收到对端为了完成握手的 ack 时, 选择 ignore 这个 ack, 那么客户端会以为丢包, 会继续尝试发送 ack;
  8. 如何查看一个监听端口的 listen backlog 队列的当前长度? Linux -> ss -ltn 看 Send-Q 列
    xiatian_lvsbastion200_____ssh_.png
  9. Tomcat 7 NIO 处理这些 socket 连接有 2 类 background 线程, 分别是 Acceptor 线程和 Poller 线程;
  10. Acceptor 线程 通过 serverSock.accept() 方法接受新连接, 就是 listen backlog 里面已经建立的连接;
  11. Acceptor 接受之后, 就把这个接受的 socket 送给 Poller 线程去处理;
  12. Poller 线程把 socket 封装之后放入 TaskQueue;
  13. Poller 线程还负责 从 socket 的 Channel 搬运数据到对应的 Buffer, 也就是负责 NIO 的 Selector 的处理任务;
  14. Acceptor 线程和 Poller 线程的数量分别由 Tomcat Connector 的 acceptorThreadCount 和 pollerThreadCount 决定;
  15. Tomcat 的 TaskQueue 是一个 LinkedBlockingQueue;
  16. TaskQueue队列里的 Task 由 Executor 里的线程读取并执行;
  17. TaskQueue队列里的 Task 由 Poller 放入;
  18. TaskQueue的长度默认是 Int 的最大值, 不过基本不会放这么多, 如果TaskQueue的任务和在处理的超过 maxConnection 的值, Poller 就会决绝新的任务;

参考: https://blog.cloudflare.com/syn-packet-handling-in-the-wild/

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/