见识 动脑 品质 - 生活的美好 - Java 相关
type: channel
link: https://www.tianxiaohui.com/index.php/category/Java%E7%9B%B8%E5%85%B3/
description: Java 基础, Java J2EE 开发相关的内容
items: Java 微服务网络连接优化
items: Java 远程 debug
items: jps 无法显示 Java 进程号
items: Java 里面的 PrivilegedAction 和 PrivilegedExceptionAction
items: 由 ServiceLoader 引发的CPU 100%
items: JVM 安全点 Safepoint
items: Java 内存分析工具 MAT 使用技巧
items: 为什么一个简单的 Java 进程的 core dump 那么大
items: Java 应用产生 core dump
items: jhsdb

Java 微服务网络连接优化
type: item
date: 2025-04-20T14:48:31+00:00
description: 在如今的大型互联网络中, 微服务基本是最流行的架构. 采用微服务架构, 自然少不了内部各个服务之间的相互调用, 那么其中的网络连接处理, 自然成了要重点考虑的对象. 下面就一步步仔细看看其中每一步要考虑的细节.基本的网络连接下面的代码使用了 JDK 最基础的代码, 告诉我们如何使用 URL 和 HttpURLConnection 来进行网络连接.import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; public class HttpClientExample { public static void main(String[] args) { String urlString = "https://api.example.com/v1/search"; HttpURLConnection connection = null; try { // 创建 URL 对象 URL url = new URL(urlString); // 打开连接 connection = (HttpURLConnection) url.openConnection(); // 设置请求方法 connection.setRequestMethod("GET"); // 设置请求属性（如有需要） connection.setRequestProperty("Content-Type", "application/json"); // 设置连接超时和读取超时 connection.setConnectTimeout(5000); connection.setReadTimeout(5000); // 发起请求 int responseCode = connection.getResponseCode(); // 检查响应码 if (responseCode == HttpURLConnection.HTTP_OK) { // 正常响应，读取数据 BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream())); StringBuilder response = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { response.append(line); } reader.close(); // 打印响应 System.out.println("Response: " + response.toString()); } else { // 错误处理 System.err.println("Error: Received HTTP code " + responseCode); BufferedReader errorReader = new BufferedReader(new InputStreamReader(connection.getErrorStream())); StringBuilder errorResponse = new StringBuilder(); String errorLine; while ((errorLine = errorReader.readLine()) != null) { errorResponse.append(errorLine); } errorReader.close(); // 打印错误响应 System.err.println("Error Response: " + errorResponse.toString()); } } catch (Exception e) { // 异常处理 System.err.println("Exception occurred: " + e.getMessage()); e.printStackTrace(); } finally { if (connection != null) { // 断开连接 connection.disconnect(); } } } }设置 timeout上面的代码中, 我们可以看到, 连接 timeout 和 read timeout 都是通过 setConnectTimeout 和 setReadTimeout 来设置的. 下面我们就来看看这两个 timeout…

Java 远程 debug
type: item
date: 2025-04-13T15:19:03+00:00
description: 本地 Debug 很容易, 那么远程 debug 是怎么做的, 又有哪些坑呢?一般应用程序 debug这里的一般应用程序就是一个 main 函数, 不是 web 应用程序. 首先, 这个应用在远程某主机上能运行, 只要启动的时候, 添加远程 debug 参数就可以了. 比如有如下代码, 为了让它慢一点可以 debug, 加入了一个循环和睡眠:public class Debugger { public int loop(int count) { int sum = 0; for (int i = 0; i < count; i++) { sum += i; try { System.out.println(i + "/" + count + " current sum: " + sum); Thread.sleep(5L); } catch (Exception e) { e.printStackTrace(); } } return sum; } public static void main(String[] args) { Debugger debugger = new Debugger(); int sum = debugger.loop(10000); System.out.println("sum is " + sum); } }在远程编译, 启动, 并且加入监听端口为了方便远程debug:$ javac Debugger.java $ java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=0.0.0.0:8000 Debugger 这里注意的是, 我们对于地址使用的是: 0.0.0.0:8000, 有些地方你看到的直接是 8000 端口, 没有主机ip部分. 这会导致这个端口只是开在 localhost, 你如果使用 netstat 查看, 能看到 8000 端口是开着的, 但是远程无法连接. 在远程可以使用 telnet 或者 curl 来测试这个端口是不是可以远程连接. 但是注意这个端口只能有一个远端能连接成功, 假如你已经使用 curl 连接上, 那么你远端的其它 IDE 就不能连接了. 我本地的 Eclipse 连接的过程是: 在菜单 Run 里面选择 Debug Configurations, 然后新建 Remote Java Application, 然后输入远程的 IP 和 端口. 如下:连接后, Eclipse 会显示连接成功, 然后点击 暂停, 就会暂停远程的应用了. debug 远程 tomcat 里面的应用在 Tomcat 的 catalina.sh 里面, 添加如下环境变量设置:export JPDA_ADDRESS=0.0.0.0:8000 export JPDA_TRANSPORT=dt_socket然后启动时候, 添加 jdpa 参数, 就可以了./catalina.sh jpda startdebug 远程基于 Spring boot 的应用其实 Spring boot 是做的 fat jar, 它的远程debug 方式跟一般的应用是一样的, 就是在启动参数里面加上. 例子如下:java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=0.0.0.0:8000 -jar myApp.jar 隧道技术有时候远程机器隔着防火墙, 你不能直接连, 但是你可以通过 ssh 登录, 那么就可以用过隧道技术, 使用 ssh 做隧道, 然后远程 debug. 首先, 我们假设远程机器的 debug 开在 8000 端口上:java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=0.0.0.0:8000 -jar myApp.jar 然后在本地机器上建立隧道:ssh -L 5000:localhost:8000 user@remote_ip这里的意思是: 在本地启动5000端口, 它所有的数据原封不动的转到远程 remote_ip 的8000 端口. 我们登录远程 remote_ip 的用户名是 user, 这里它会让你输入 ssh user 的密码. 连接成功后, 隧道建立. 这时候, 在本地和之前的一样, 只是把远程机器的ip 改成本地机器localhost, 端口改成 5000. 就能借助隧道进行远程 debug 了.

jps 无法显示 Java 进程号
type: item
date: 2024-12-23T13:11:00+00:00
description: 对于 Java 应用的诊断, 一直以来都是使用 JDK 自带的 Jps 命令来查看 Java 进程 ID, 最近在一个生产环境的的某个 Kubernetes pod 里面 Jps 一直无法给出对应的进程 ID, 什么输出都没有.I have no name!@mypod:~$ jps I have no name!@mypod:~$初步检查在 JDK 里面, 很多小工具的命令都搬迁到 jcmd 命令, 如果 jcmd 命令不带任何参数, 其实就相当于 Jps 命令. 于是尝试 jcmd 命令, 结果是一样的.I have no name!@mypod:~$ jcmd I have no name!@mypod:~$以前在 VM 时代, 遇到过类似的问题. 原因是当前运行 bash 的用户和对应的 Java 进程不属于同一个用户, 要切换到对应 Java 进程运行的用户才能看到该进程 ID. 但是这次当前 Bash 的用户和对应 Java 进程的用户本来就是同一个用户.I have no name!@mypod:~$ ps aux USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND 84289 53 1.7 1.5 48923744 6226704 ? Sl Dec02 527:44 /prod/app/jre/bin/java -Xms4096m -Xmx4096m myApp I have no name!@mypod:~$ id uid=84289 gid=84677 groups=84677怀疑当前 pod 的 JDK 文件有啥特别设置(为了减小JDK 大小和不必要文件, 他们把 JDK bin 去掉了, 有需要的移动到了 /jre/bin/ 目录去了). 于是从本地复制一个同版本的 JDK 到目标 pod, 并解压使用, 发现 Jps 和 Jcmd 是一样的结果. 但是这时候发现, 如果给 jcmd 命令一个进程ID, 它就能正常运行, 只是无法发现当前 Pod 的其它 Java 进程.I have no name!@mypod:~$ jcmd 53 help 53: The following commands are available: Compiler.CodeHeap_Analytics GC.class_histogram GC.finalizer_info GC.heap_dump GC.heap_info GC.run GC.run_finalization Thread.print ...进一步检查这时候去 Google 了一下, 发现有人说要给 Jps 命令添加一个参数: -J-Djava.io.tmpdir=/tmp/ 告诉它临时文件夹所在的地方. 去试了一下, 发现没有任何改进. https://stackoverflow.com/questions/3805376/jps-returns-no-output-even-when-java-processes-are-running于是想使用源码 debug 一下, 于是找到 Jps 的代码: https://github.com/AdoptOpenJDK/openjdk-jdk11/blob/master/src/jdk.jcmd/share/classes/sun/tools/jps/Jps.java其实就是2个类, 于是合并一下, 并入到一个类里面, 然后复制到到远程 pod 里面, 命名为: JpsTest.javaimport java.util.*; import java.io.*; import java.net.*; import sun.jvmstat.monitor.*; /** * Application to provide a listing of monitorable java processes. * * @author Brian Doherty * @since 1.5 */ public class JpsTest { private static Arguments arguments; public static void main(String[] args) { try { arguments = new Arguments(args); } catch (IllegalArgumentException e) { System.err.println(e.getMessage()); Arguments.printUsage(System.err); System.exit(1); } if (arguments.isHelp()) { A…

Java 里面的 PrivilegedAction 和 PrivilegedExceptionAction
type: item
date: 2024-09-01T15:25:10+00:00
description: 读 JDK 里面的代码, 可能会遇到某些操作需要被封装成 PrivilegedAction 和 PrivilegedExceptionAction 来执行, 比如下面的代码块:AccessControlContext acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null; PrivilegedAction<Boolean> action = new PrivilegedAction<>() { public Boolean run() { return findSomething(); } }; AccessController.doPrivileged(action, acc);为什么需要特权操作(PrivilegedAction)?Java 诞生的初期在浏览器的环境执行(applet), 所以要加很多安全限制, 从 1.0 版本就有了 SecurityManager 的概念, 从最核心的 System 类的 System.getSecurityManager() 你就能得到系统的安全管理器. 只不过, 这个安全管理器默认是没有开启的. 并且从 JDK 17 开始将要被废弃.安全管理器是如何工作的?安全管理器(SecurityManager)是通过policy来限制你能不能做某个操作. 比如: 代码能不能访问网络, 能不能读取磁盘文件, 能不能访问环境变量等. Policy 存放在一些文件里面, 通过改变文件里面policy的内容, 设置安全管理器是允许还是拒绝某些从左. 一个 policy 的例子如下: 下面的 policy 表示在运行时对于代码模块 java.scripting 中的代码, 授予所有的权限.grant codeBase "jrt:/java.scripting" { permission java.security.AllPermission; };你能在 <java.home>/lib/security/default.policy 里面找到系统默认的 policy. 当然你也可以定义自己的policy, 放到 ${java.home}/conf/security/java.policy 里面. 或者放到其它地方, 并且在启动参数里面通过: –Djava.security.policy=/tmp/myPolicy.policy 指定.PrivilegedAction 和 PrivilegedExceptionAction 是如何工作的?PrivilegedAction 和 PrivilegedExceptionAction 都会封装一个操作, 当这个操作会抛出 checked exception 的时候, 就需要用 PrivilegedExceptionAction, 否则就用 PrivilegedAction.封装完这操作, 就使用 AccessController.doPrivileged(action, acc) 去执行, 它会检查是不是有执行权限, 如果有, 就去执行, 否则不执行.代码示例下面的代码先设置一个安全管理器, 然后尝试访问一个URL, 这个时候, 就会报错.import java.io.IOException; import java.net.URL; public class Main { public static void main(String[] args) { System.setSecurityManager(new SecurityManager()); try { new URL("https://www.tianxiaohui.com").openConnection().connect(); } catch (IOException e) { e.printStackTrace(); } } }错误: 拒绝访问:Exception in thread "main" java.security.AccessControlException: access denied ("java.net.SocketPermission" "www.tianxiaohui.com:443" "connect,resolve") at java.base/java.security.AccessControlContext.checkPermission(AccessControlContext.java:485) at java.base/java.security.AccessController.checkPermission(AccessController.…

由 ServiceLoader 引发的CPU 100%
type: item
date: 2024-08-26T12:59:47+00:00
description: 最近遇到2次由于 ServiceLoader 引起的 CPU 100%, 导致业务线程不能正常运行.什么是 Service LoaderSpring 里面有个核心的概念, 就是依赖注入: 我期望有个服务, 但是一开始我并不指定具体的实现类, 等到我真正需要的时候, 这个依赖根据运行时自动注入. 同样, JDK 6 也引入了一个一样的实现框架, 就是 ServiceLoader. 它的实现也很简单. 使用的方法如下:ServiceLoader<ServiceAPI> serviceLoader =ServiceLoader.load(ServiceAPI.class); Iterator<ServiceAPI> iterator = serviceLoader.iterator(); while (iterator.hasNext()) { ServiceAPI impl = iterator.next(); }它的主要作用就是: 你需要那个服务的具体实现, 让我来帮你找, 可能找到一个或多个, 或找不到. 结果返回的是一个 Iterator.如何找到具体的实现的?如果某个 Jar 包提供某个服务的具体实现, 按照 JDK 定义的规则, 它就会在在 Jar 包的 META-INFO/services 文件夹提供一个名为某个service的文件, 文件的内容就是具体的实现类. 比如 xerceslmpl-x.x.x.jar 提供了 javax.xml.datatype.DatatypeFactory 的具体实现:文件的内容就是本 jar 包里面的具体实现类的全名. 所以, 可以通过判断当前 jar 包里面的 META-INFO 文件夹下面是不是包含某个service 文件名来判断是不是有这个实现.如何出问题的?出问题的就是下面这行代码:javax.xml.datatype.DatatypeFactory df = javax.xml.datatype.DatatypeFactory.newInstance();就是要初始化一个xml 转换成 Java对象的类型工厂类, 如果去 JDK 里面查看这个类的源代码, 会发现其实它是一个抽象 Service. 运行时它有4种查找具体实现类的方法. 前2种都是通过配置, 第三种就是通过 ServiceLoader 去查找它的具体实现.出问题的方式就是通过 ServiceLoader 的方式, 这种方式就是通过 ClassLoader 去查找所有的 Jar 包, 一个个去看有没有某个 jar 的 META-INFO/services 文件夹下面包含这么一个 service 的具体实现. 通常的实现的一个具体栈:java.lang.Thread.State: RUNNABLE at java.util.zip.ZipCoder.getBytes(ZipCoder.java:77) at java.util.zip.ZipFile.getEntry(ZipFile.java:325) - locked <0x00000007157ac988> (a java.util.jar.JarFile) at java.util.jar.JarFile.getEntry(JarFile.java:253) at java.util.jar.JarFile.getJarEntry(JarFile.java:236) at sun.misc.URLClassPath$JarLoader.getResource(URLClassPath.java:1084) at sun.misc.URLClassPath$JarLoader.findResource(URLClassPath.java:1062) at sun.misc.URLClassPath$1.next(URLClassPath.java:281) at sun.misc.URLClassPath$1.hasMoreElements(URLClassPath.java:291) at java.net.URLClassLoader$3$1.run(URLClassLoader.java:609) at java.net.URLClassLoader$3$1.run(URLClassLoader.java:607) at java.security.AccessController.doPrivileged(Native Method) at java.net.URLClassLoader$3.next(URLClassLoader.java:606) at java.net.URLClassLoader$3.hasMoreElements(URLClas…

JVM 安全点 Safepoint
type: item
date: 2024-08-03T01:11:00+00:00
description: 最近在看 ZGC 的某些具体的实现, 有篇文章对了从 Serial GC, 到 parallel GC, 再到CMS, 然后到G1, 最后到如今的ZGC, 其中一个重要的差别就是把很多GC 时间(STOP the world)要做的事情移到并发去做的过程. 其实这是一个从简单到复杂的过程, 也是一个从粗放到逐步精细控制的过程. 最终的结果就是在GC的时间点上, 做的事情越来越少. 如果讨论到GC 的时间点, 其中一个重要的事情, 就是安全点(Safepoint), 它是一个让所有业务线程在某个点全部停下来的过程, 由于有很多业务线程, 让它们同时停下来, 就涉及到一个协调机制, 如何让这些线程在不影响业务线程的情况下, 以最快的速度停下来, 就显得非常重要.什么是JVM SafepointJVM（Java虚拟机）中的Safepoint是一种机制，用于确保所有线程在执行某些特定的系统级操作之前达到一个已知且一致的状态。这些系统级操作通常包括垃圾收集（GC）、线程栈的展开、代码重优化以及一些运行时系统的更新等。在Safepoint期间，JVM会暂停所有的Java线程执行（也就是所谓的“Stop-The-World”暂停），直到所有线程都到达Safepoint。这样可以确保在进行这些操作时，不会有任何线程在执行Java字节码，从而避免了潜在的数据不一致和竞争条件。JVM 可以在那些代码区域达到安全点?方法调用边界：当一个方法被调用时，可能会在调用前后插入Safepoint检查。这是因为方法调用是程序执行中的自然中断点，且通常是执行时间较长的操作。循环回边：在循环结构中，循环的末尾（即循环要重新开始的地方）是达到Safepoint的一个常见位置。这样做是为了防止长时间运行的循环阻止系统达到Safepoint。显式的Safepoint检查点：JVM的即时编译器（JIT）可能会在生成的机器代码中的特定位置插入显式的Safepoint检查。这些检查通常会在执行时间较长的代码段中进行。同步操作：当线程尝试进入或退出同步块（synchronized block）或方法时，也可能会进行Safepoint检查，因为这些操作涉及到锁的获取和释放。异常抛出点：当程序抛出异常时，可能会在异常处理之前达到Safepoint，因为异常处理涉及到栈的展开和控制流的改变。线程状态变化：当线程状态发生变化时（例如，从运行状态转为等待或休眠状态），也可能会进行Safepoint检查。其他：JVM实现还可能在其他不那么明显的地方插入Safepoint检查，这些通常是由于特定的实现细节和优化策略。其它Safepoint 在 Java 语言规范里没有涉及, 但是每个 JVM 实现都有 Safepoint;什么时候需要安全点 Safepoint?GC 某些阶段的时候;JVM TI 捕获 stacktrace 的时候;类重新定义的时候(Class redefinition), 比如 BCI 代码 Instrument 的时候;捕获 heap dump 的时候;锁膨胀的时候 (monitor deflation);锁从偏向锁取消的时候(Lock unbiasing);方法逆优化的时候(Method deoptimization);其它...对于 Zing JVM 实现, 分为全局安全点( global Safepoint) 和 线程安全点 (Thread Safepoint), 对于 Hotspot (Oracle/OpenJDK)系列只有全局安全点;所有的 JVM 实现都在某些地方需要全局安全点( global Safepoint);参考: http://psy-lob-saw.blogspot.com/2016/02/why-most-sampling-java-profilers-are.htmlhttp://psy-lob-saw.blogspot.com/2015/12/safepoints.htmlhttps://psy-lob-saw.blogspot.com/2014/03/where-is-my-safepoint.htmlgoogle search: with-gc-solved-what-else-makes-jvm-pause

Java 内存分析工具 MAT 使用技巧
type: item
date: 2024-07-06T08:09:00+00:00
description: 本文列出作者在日常使用 Java 内存分析工具 MAT 的过程中用到的一些技巧导出长字符串有时候我们经常要从 heap 中复制出某些很长的字符串, 来观察它到底有哪些数据. 通常我们通过: 在某个字符串上点击右键 - Copy -> Value. 但是通常这个复制出的内容都有长度限制. 比如下面的例子, 我想从 HTTP request 的 HeapByteBuffer 复制出它已经读取请求的内容, 但是通过上面的方法只能复制出几千的字符:但是如果其内容远超这个数量, 拿到的就是部分数据, 根据部分数据可能得出错误的结论. 如何完全导出其内容? 在上面的菜单中选择 -> Save Value To File. 就能导出全部内容. 不过对于上面例子中的 HeapByteBuffer 要特别注意, 它是通过当前的位置(pos)来标记那里是有效数据的, pos 位置之后可能还有数据, 只不过是无效数据.根据字段值分组统计SQL 里面有 select * from table_0 group by column_0. 可是 OQL 里面却没有这个语句. 但是MAT 却提供了这样的功能. 根据下面的菜单栏, 就能找到 Group By Value 选项. 然后填入你想分组的类名字和要分组的字段. 下面以 java.util.regex.Pattern 的 normalizedPattern 来分组: 最终看到每个 normalizedPattern 的统计个数:

为什么一个简单的 Java 进程的 core dump 那么大
type: item
date: 2024-05-15T15:03:00+00:00
description: 写了一个非常简单的 java main 函数, 然后运行, 然后做 core dump, 竟然发现 core dump 竟然有 6.6G. Java 程序:public class ErrorExample { public static void main(String[] args) throws InterruptedException { Thread.sleep(600000); } }做 core dump 和 2种不同的 heap dump.gcore <pid> $ ls -lah -rw-rw-r-- 1 supra supra 6.6G Apr 30 00:26 core.276268 -rw-rw-r-- 1 supra supra 67M Apr 30 00:26 core.276268.gz -rw-rw-r-- 1 supra supra 1.3M Apr 30 01:02 heap.bin -rw-rw-r-- 1 supra supra 334K Apr 30 01:02 heap.bin.gz -rw------- 1 supra supra 2.8M Apr 30 01:01 heap.hprof -rw------- 1 supra supra 855K Apr 30 01:01 heap.hprof.gz上面分别是使用不同工具得到的不同 dump 及进一步压缩过后的:gcore 276268 得到 core.276268jhsdb jmap --binaryheap --pid 280038 得到 heap.binjcmd 280038 GC.heap_dump heap.hprof 得到 heap.hprof.可以看到 jhsdb 做出的最小, core dump 最大. 这很有可能是 JVM 保留了很多想用但是还没用到的内存. 查看JVM 当前使用的内存, 发现其实使用的很小:$ jcmd 281746 GC.heap_info 281746: garbage-first heap total 258048K, used 1744K [0x0000000707e00000, 0x0000000800000000) region size 1024K, 2 young (2048K), 0 survivors (0K) Metaspace used 155K, capacity 4486K, committed 4864K, reserved 1056768K class space used 6K, capacity 386K, committed 512K, reserved 1048576K然后查看使用的启动参数, 可以看到保留的最大的堆是将近4G(MaxHeapSize=4162846720), 保留的代码缓存区是240M(ReservedCodeCacheSize=251658240), 再加上其它, 比如 JDK 库, 元数据区等, 可能达到6G多.$ jcmd 281746 VM.flags 281746: -XX:CICompilerCount=4 -XX:ConcGCThreads=2 -XX:G1ConcRefinementThreads=8 -XX:G1HeapRegionSize=1048576 -XX:GCDrainStackTargetSize=64 -XX:InitialHeapSize=262144000 -XX:MarkStackSize=4194304 -XX:MaxHeapSize=4162846720 -XX:MaxNewSize=2497708032 -XX:MinHeapDeltaBytes=1048576 -XX:NonNMethodCodeHeapSize=5836300 -XX:NonProfiledCodeHeapSize=122910970 -XX:ProfiledCodeHeapSize=122910970 -XX:ReservedCodeCacheSize=251658240 -XX:+SegmentedCodeCache -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:+UseG1GC如果使用 pmap 查看这个进程的虚拟内存使用情况, 可以发现确实使用了6.6G(最后一行):$ pmap -x 280038 280038: java ErrorExample Address Kbytes RSS Dirty Mode Mapping 0000000707e00000 256000 328 32…

Java 应用产生 core dump
type: item
date: 2024-05-01T02:39:30+00:00
description: 什么是core dump在 Linux 系统中，当一个进程崩溃（例如，由于段错误或其他严重错误），它通常会产生一个称为 "核心转储"（core dump）的文件。核心转储文件包含了进程崩溃时的内存映像和一些执行上下文信息，如寄存器状态、程序计数器位置、内存管理信息等。这些信息对于开发者来说非常有价值，因为它们可以用来调试程序，了解程序崩溃的原因。Java 进程如何产生 core dump对于正在运行的 Java 进程, 它就是一个标准的linux 进程, 可以使用 linux 上的各种工具来产生 core dump, 比如 gcore 或者 kill :$ gcore <pid> $ kill -11 <pid>对于 Java 应用程序自身, 当它奔溃的时候, 默认会产生 core dump, 因为它有如下默认参数-XX:+CreateCoredumpOnCrash:$ java -XX:+PrintFlagsFinal -version | grep Core bool CreateCoredumpOnCrash = true {product} {default}虽然上面的方式都能产生 core dump, 但是很有可能你并不能看到 core dump, 因为有各种其它条件会阻碍 core dump的产生:系统设置了 core size 的大小太小, 查看 ulimit -ccore dump 要写入的文件夹没有权限core dump 被系统设置拦截, 比如 apport, 它产生了 crash report, 却拦截了 core dump的产生.core dump 有什么用?core dump 里有进程当时时间点上的全部内存信息, 寄存器信息, 栈信息, 栈上变量值, 打开文件句柄, 打开的socket 等各种非常有用的信息, 对于诊断应用为什么崩溃具有很大的意义. gdb 可以直接打开core dump 文件, 并读取里面的信息.$ gdb core core.720444` 使用 gdb 去debug Java 程序比较麻烦. 但是可以使用 JDK 自带的 jdb:# java 8 版本 $ jdb -listconnectors $ jdb -connect sun.jvm.hotspot.jdi.SACoreAttachingConnector:javaExecutable=$JAVA_HOME/bin/java,core=core.720444 java 11 版本 $ jhsdb clhsdb --exe ErrorExample --core core.720444Core Dump vs Java Heap DumpCore Dump定义：Core dump 是操作系统在进程异常终止时生成的一个文件，它包含了进程终止时内存中的内容。内容：Core dump 包含了进程的整个内存映像，包括程序计数器、寄存器、堆栈、堆内存、全局变量、打开的文件描述符、环境变量、程序代码、加载的共享库等。用途：主要用于程序崩溃后的调试和故障排查。可以使用调试工具（如 gdb）来分析 core dump 文件，确定程序崩溃的原因。大小：Core dump 文件通常很大，因为它包含了整个进程的内存映像。生成方式：Core dump 通常由操作系统在进程崩溃时自动生成，或者可以使用 gcore 命令手动生成。Java Heap Dump定义：Java heap dump 是 JVM 在某一时刻的堆内存的快照，包含了所有的 Java 对象和类信息。内容：Heap dump 专注于 Java 堆内存，包括对象实例、数组、类实例、垃圾收集器信息等。用途：主要用于分析 Java 应用程序的内存使用情况，如检测内存泄漏、查看对象的分配和引用情况等。大小：Heap dump 文件的大小取决于 Java 堆的大小，通常比完整的 core dump 小。生成方式：Heap dump 可以通过 JVM 提供的工具（如 jmap）、管理接口（如 JMX）生成，或在发生 OutOfMemoryError 时自动生成（如果配置了 -XX:+HeapDumpOnOutOfMemoryError JVM 参数）。heap dump vs crash log file这就类似 core dump -> apport crash reportheap dump -> crash log file (err_log_pid.log)-XX:OnError 选项如果启动参数配置了-XX:OnError选项, 当 fatal error 产生的时候, JVM 就会执行该选项配置的命令. 多个执行命令可以用 ; 分割开. 可以用 %p 指代当前进程号, 因为 % 用作了特…

jhsdb
type: item
date: 2024-04-30T07:27:23+00:00
description: jhsdb（Java HotSpot Debugger）是在JDK 9中引入的命令行实用程序，它是服务性代理（Serviceability Agent）工具的一部分。服务性代理是一个框架，允许对运行中的Java虚拟机（JVM）或崩溃后的核心转储进行深入分析。引入jhsdb的目的有几个：统一的调试工具：在JDK 9之前，开发者需要使用不同的工具，如jmap、jstack、jinfo等来执行各种调试任务。jhsdb将这些工具统一到一个命令行界面下，使得在不切换多个工具的情况下更容易执行广泛的调试和诊断任务。增强的事后诊断：jhsdb提供了分析核心转储和实时进程的能力，这对事后诊断至关重要。当JVM崩溃时，开发者需要了解崩溃时JVM的状态，这一点尤其有用。高级分析能力：使用jhsdb，开发者可以执行高级分析，如检查堆、分析内存使用情况和获取线程堆栈跟踪。它还允许使用内置的命令行调试器（CLD）来调试Java进程，这是一个类似于gdb的基于文本的界面。跨平台一致性：jhsdb旨在在不同平台上保持一致的工作，为在各种操作系统上进行诊断提供了标准化的方法。与HotSpot JVM集成：由于jhsdb是为HotSpot JVM量身定制的，它可以利用HotSpot的内部特性来提供有关JVM内部的详细信息，这对于性能调优和解决复杂问题非常宝贵。可访问性：通过包含在JDK中，jhsdb可以立即供所有Java开发者使用，无需额外下载或安装。可脚本化：作为命令行工具，jhsdb可以轻松地编写脚本并集成到自动化的调试和诊断工作流中，这对于持续集成和部署管道非常有益。总的来说，JDK 9中引入jhsdb的目的是通过提供一个强大而多功能的JVM诊断和调试工具来改善开发者体验，从而增强分析和解决Java应用程序中复杂问题的能力。实践linux 系统现在有一个正在运行的 Java 程序, 进程号是 256487, 却遇到了下面的错误$ jhsdb jmap --pid 256487 Attaching to process ID 256487, please wait... ERROR: ptrace(PTRACE_ATTACH, ..) failed for 256487: Operation not permitted Error attaching to process: sun.jvm.hotspot.debugger.DebuggerException: Can't attach to the process: ptrace(PTRACE_ATTACH, ..) failed for 256487: Operation not permitted sun.jvm.hotspot.debugger.DebuggerException: sun.jvm.hotspot.debugger.DebuggerException: Can't attach to the process: ptrace(PTRACE_ATTACH, ..) failed for 256487: Operation not permitted这是因为 ptrace 权限的问题. 查看 /proc/sys/kernel/yama/ptrace_scope 可以看到里面的值是 1, 改成 0 就好了$ cat /proc/sys/kernel/yama/ptrace_scope 1 $ echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope 0 $ jhsdb jmap --pid 256487 # as pmap output Attaching to process ID 256487, please wait... Debugger attached successfully. Server compiler detected. JVM version is 11.0.22+7-post-Ubuntu-0ubuntu222.04.1 0x0000563f4efe6000 14K /usr/lib/jvm/java-11-openjdk-amd64/bin/java 0x00007fe2671ba000 44K /usr/lib/jvm/java-11-openjdk-amd64/lib/libzip.so 0x00007fe2671c5000 66K /usr/lib/x86_64-linux-gnu/libresolv.so.2 0x00007fe2671d9000 22K /usr/lib/x86_64-linux-gnu/libkeyutils.so.1.9 0x00007fe2671e0000 50K /usr/lib/x…