惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Spread Privacy
Spread Privacy
P
Palo Alto Networks Blog
P
Proofpoint News Feed
AI
AI
Help Net Security
Help Net Security
S
Securelist
T
Troy Hunt's Blog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
C
Cisco Blogs
Scott Helme
Scott Helme
Hacker News - Newest:
Hacker News - Newest: "LLM"
Vercel News
Vercel News
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
B
Blog
GbyAI
GbyAI
Recent Commits to openclaw:main
Recent Commits to openclaw:main
D
Darknet – Hacking Tools, Hacker News & Cyber Security
P
Proofpoint News Feed
S
Security Affairs
Cisco Talos Blog
Cisco Talos Blog
AWS News Blog
AWS News Blog
T
Tenable Blog
H
Help Net Security
NISL@THU
NISL@THU
F
Fortinet All Blogs
博客园_首页
G
GRAHAM CLULEY
L
LINUX DO - 最新话题
P
Privacy International News Feed
G
Google Developers Blog
博客园 - Franky
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Security Archives - TechRepublic
Security Archives - TechRepublic
The Register - Security
The Register - Security
L
LangChain Blog
aimingoo的专栏
aimingoo的专栏
T
Tor Project blog
P
Privacy & Cybersecurity Law Blog
量子位
C
Cyber Attacks, Cyber Crime and Cyber Security
Forbes - Security
Forbes - Security
S
Secure Thoughts
Simon Willison's Weblog
Simon Willison's Weblog
D
Docker
Recorded Future
Recorded Future
博客园 - 三生石上(FineUI控件)
L
Lohrmann on Cybersecurity
T
Tailwind CSS Blog

暗无天日

读:AI Agent 安全日志——从可见性与隐私的两难说起 - 暗无天日 AI写作的语言指纹——如何让文字不那么像机器 - 暗无天日 读:50 条 Claude Code 技巧——一个工程经理的六个月使用心得 读:AI 辅助开发为什么让 E2E 测试更有价值 - 暗无天日 读:在Emacs中使用Claude Code(Spacemacs适配版) - 暗无天日 Claude Code 背后的工程哲学——读 Agent Harness Engineering 读:Agent Harness Engineering——AI 智能体不只是模型,还有套件 - 暗无天日 browser-harness:让 AI 直接接管你的浏览器 - 暗无天日 读:Security-First CI/CD —— DevSecOps 自动化实践指南 TIL: 数字小键盘的小数点陷阱与行内算术求值 - 暗无天日 读:Immutability 不是万能药,它是一种权衡 - 暗无天日 Conducty:给 Claude Code 加上项目记忆和并行执行能力 - 暗无天日 读 — GitHub Trending 里的 Claude Code 技能包 读 — Prompt Caching 省钱指南 TIL: Emacs 中那些跟鼠标配合的冷门快捷键 - 暗无天日 读:Anvil——把 Emacs 变成 AI 的工具服务器 读:Emacs 代码折叠终极指南 - 暗无天日 读:Clojure 搭车客指南 - 暗无天日 git推送失败后恢复仓库损坏的完整记录 - 暗无天日 多智能体系统的两个有效模式——以及对 Claude Code 用户的启示 - 暗无天日 用 Org Babel 写 Literate 博文:扩展执行 + 定制导出 proced:Emacs 内置的进程查看器 - 暗无天日 从 proced 定制中学到的 Elisp 模式 读:让 Emacs proced 在 macOS 上显示 CPU 和内存 异步编程的函数着色税 - 暗无天日 链式调用的代价:JavaScript 和 Clojure 的共同教训 - 暗无天日 hyperfine:命令行基准测试工具 - 暗无天日 管道中的变量去哪了?——子 shell 作用域陷阱 - 暗无天日 开源包装器的信任陷阱:四个危险信号 - 暗无天日 程序员愿意为 AI 写文档,却不愿为同事写 - 暗无天日 mktemp: Shell 脚本中临时文件的安全陷阱与最佳实践 - 暗无天日 WSL9x —— 在 Windows 9x 里跑 Linux 内核 6.19 用 ox.el 做你想做的事 —— org-export 高级编程指南 读:Hot-wiring the Lisp Machine —— 用纯 Elisp 构建零依赖的 Org 静态站点生成器 Elisp 性能优化的六个实战教训 - 暗无天日 fcitx5 下 Emacs 无法切换输入法的排查 - 暗无天日 ERT 测试交互命令的三种方式 - 暗无天日 SEM Assistant: 当 Elisp 守护进程遇上 LLM 用 dmsg 给 Elisp 加上结构化调试日志 用 org-habit 追踪非每日习惯 - 暗无天日 Clojure X-Men:当编程语言特性变成超能力 - 暗无天日 TIL: 用 diff-hl 在 fringe 中显示 git 变更 读:llm-test —— 用 LLM agent 驱动 Emacs 测试 TIL: AI 时代的橡皮鸭调试 - 暗无天日 fcitx 启动后键盘输入卡顿的排查 - 暗无天日 TIL: 早期网页的图片热区导航 - 暗无天日 读 Seeing the Whole System 用 Emacs 自动生成每周链接推荐 - 暗无天日 读:ASCII control characters in my terminal 读 What to learn - 暗无天日 Lisp 的括号之痛——一个愚人节玩笑揭开的老伤疤 - 暗无天日 一本书该"线性读"还是"并行读" - 暗无天日 读 How to Monetize a Blog:一篇伪装成变现指南的讽刺文 Python Mock 第三方依赖的四种策略 - 暗无天日 Emacs Lisp 热重载实用指南 - 暗无天日 Prot 的 Emacs 配置哲学 - 暗无天日 TIL: 从直播对谈中学到的三个 Emacs 技巧 - 暗无天日 TIL: 自动使用项目虚拟环境的 Python - 暗无天日 TIL: 让 Help buffer 自动获得焦点 一条命令让本地开发用上 HTTPS —— slim 工具介绍 用 fsck 检查和修复 Linux 文件系统 排查Linux进程"卡死"实战:从strace到gdb全流程 - 暗无天日 PostgreSQL 索引:从基础到你可能不知道的高级用法 - 暗无天日 用 .pdbrc 自定义 Python 调试器 ANSI 转义码的标准化现状 - 暗无天日 终端程序的潜规则 - 暗无天日 PARA Org-mode 测试配置 - 暗无天日 AI越强越辣鸡?控制论说这是必然的 - 暗无天日 AI 越强越需要你盯着——反馈循环实操指南 - 暗无天日 你的AI代理正在偷你的密钥——四种你没想到的泄露通道 - 暗无天日 LLM 在 DevOps 中的三种角色 - 暗无天日 写作风格的反建议 - 暗无天日 反驳本质复杂性——Dan Luu 论为什么《没有银弹》错了 - 暗无天日 文件充满了危险——Dan Luu 谈文件系统的可靠性陷阱 - 暗无天日 AI 时代的 PARA 方法:用 Org-mode 和 AI 打造个人知识管理系统 Linux 数据去重学习笔记 - 暗无天日 创建跨平台 ZIP 文件的隐藏陷阱:Extra Field - 暗无天日 X11 Forwarding 排障指南 - 暗无天日 IP欺骗端口扫描:当别人冒充你去扫描别人 - 暗无天日 Linux 输入栈全景解析:从硬件按键到屏幕响应 - 暗无天日 Unix 系统中那些被埋没的配置开关——以 FontConfig 为例 - 暗无天日 在Linux上限制儿童使用电脑 - 暗无天日 GIF不仅仅是一种图片格式——用GIF流做些奇怪的事 - 暗无天日 Leiningen 学习笔记:Clojure 项目构建与管理从入门到实战配置 - 暗无天日 Google SRE Book 读书笔记 - 暗无天日 yes 管道 head 发生了什么 - 暗无天日 为什么 nohup 在 crontab 中不起作用 Bash中的Indirection与Nameref - 暗无天日 Linux PAM 简介 - 暗无天日 从Linux ISO文件启动计算机 - 暗无天日 用 Bash 打造一个Screen Locker 用GitHub Actions自动构建EGO博客 - 暗无天日 blocking I/O 的作用 - 暗无天日 mobileog 手机端同步提示Error:2 No such file 的解决方法 回收 WSL2 VHDX 文件占用空间 使用 org-mode columnview 生成任务列表 - 暗无天日 Emacs 作为 MPD 客户端 - 暗无天日 移动文件路径却不破坏org file link的方法 - 暗无天日 如何合理的导出help link 成HTML - 暗无天日 笑话理解之Biology - 暗无天日
TIL: 用进程树展开定位被脚本包装的 JVM 进程 - 暗无天日
2026-05-07 · via 暗无天日

jstack 调试被脚本包装的 JVM 进程(比如 lein test 启动的 Java 进程)时, jps 经常显示不出有用的信息:你看到的是一串主类名( mainGradleDaemon ),但没法把这些名字和你的启动命令对应起来。 jps 只告诉你"现在有哪些 JVM 进程在跑",不告诉你"它们分别是谁启动的"。Kyle Kingsbury 在 博客 上分享了解法:用 pgrep 递归展开进程树,再和 jps 取交集定位目标进程。

是什么

运行 lein run 时程序卡住了,想用 jstack 看线程栈,但 jps 的输出是这样的:

136086 Jps
2777 NutstoreGUI
136062 main
121004 GradleDaemon

main 是 Leiningen 启动 Java 时用 -m 指定的主类名。你从这两个字完全看不出"这是 lein run 启动的那个"。 jps 能列出所有 Java 进程,但没法把进程和你的启动命令关联起来。

为什么值得关注

传统做法是在 jps 输出里猜,或者用 ps aux | grep java 人工辨认。但当进程链很长(脚本 → Java → Java)时,这些方法都不可靠。

Kyle 的思路:从匹配命令行的根进程开始,用 pgrep -P <pid> 递归展开整棵进程树,再和 jps 列出的 JVM 进程取交集,取最大 PID(通常是最后启动的)执行 jstack

怎么用

Kyle 写的是 Ruby 脚本,这里翻译成 Shell:

bash
expand() {
    local pid=$1
    echo "$pid"
    for child in $(pgrep -P "$pid" 2>/dev/null); do
        expand "$child"
    done
}

roots=$(pgrep -f "$*")
[ -z "$roots" ] && { echo "No process matching: $*"; exit 1; }

all_pids=""
for root in $roots; do
    all_pids="$all_pids $(expand "$root")"
done

jvm_pids=$(jps -q 2>/dev/null)
fav=""
for pid in $all_pids; do
    [ -z "$pid" ] && continue
    for jpid in $jvm_pids; do
        [ "$pid" = "$jpid" ] && { [ -z "$fav" ] || [ "$pid" -gt "$fav" ]; } && fav=$pid
    done
done

[ -z "$fav" ] && { echo "No JVMs with that command line in their ancestry"; exit 1; }

echo "jstack $fav ($(tr '\0' ' ' < /proc/$fav/cmdline))"
echo
exec jstack "$fav"

pattern 是 pgrep -f 的参数,匹配进程的完整命令行。用项目目录名或启动命令中的关键词都行,关键是这个词要出现在进程的命令行里。

使用效果:

$ jstack+ "jstack-test"
jstack 141649 (java -classpath /tmp/jstack-test/test:/tmp/jstack-test/src:... clojure.main ...)

2026-05-06 23:20:42
Full thread dump OpenJDK 64-Bit Server VM (25.492-b09 mixed mode):

"Attach Listener" #9 daemon prio=9 os_prio=0 ...
   java.lang.Thread.State: RUNNABLE

"Service Thread" #8 daemon prio=9 os_prio=0 ...
   java.lang.Thread.State: RUNNABLE
...

这里有个坑:PID 回绕的时候(系统 PID 用完了,重新从低编号分配),最大的 PID 不一定是最后启动的。这种情况可以改成按启动时间选: /proc/<pid>/stat 的第 22 字段是进程启动时的 clock ticks,值越大越晚。

bash    
  expand() {
      local pid=$1
      echo "$pid"
      for child in $(pgrep -P "$pid" 2>/dev/null); do
          expand "$child"
      done
  }

  roots=$(pgrep -f "$*")
  [ -z "$roots" ] && { echo "No process matching: $*"; exit 1; }

  all_pids=""
  for root in $roots; do
      all_pids="$all_pids $(expand "$root")"
  done

  jvm_pids=$(jps -q 2>/dev/null)
  fav="" fav_st=0
  for pid in $all_pids; do
      [ -z "$pid" ] && continue
      for jpid in $jvm_pids; do
          if [ "$pid" = "$jpid" ]; then
              st=$(awk '{print $22}' /proc/$pid/stat)
              [ "$st" -gt "$fav_st" ] && fav=$pid fav_st=$st
          fi
      done
  done

  [ -z "$fav" ] && { echo "No JVMs with that command line in their ancestry"; exit 1; }

  echo "jstack $fav ($(tr '\0' ' ' < /proc/$fav/cmdline))"
  echo
  exec jstack "$fav"

验证:搭一个 Clojure 测试实例

要验证 jstack+ 能不能找到被脚本包装的 JVM 进程,可以搭一个简单的测试用例:让 lein run 启动后挂住,然后用 jstack+ 定位它。

创建一个 Leiningen 项目:

mkdir -p /tmp/jstack-test/src/jstack_demo
cd /tmp/jstack-test

cat > project.clj <<'CLJ'
(defproject jstack-demo "0.1.0-SNAPSHOT"
  :description "jstack+ 测试项目"
  :main jstack-demo.core
  :dependencies [[org.clojure/clojure "1.11.1"]])
CLJ

cat > src/jstack_demo/core.clj <<'CLJ'
(ns jstack-demo.core)

(defn -main [& args]
  (println "挂住 300 秒,另开终端跑 jstack+ ...")
  (Thread/sleep (* 300 1000)))
CLJ

然后在终端 1 启动:

cd /tmp/jstack-test && lein run
挂住 300 秒,另开终端跑 jstack+ ...

终端 2 运行 jstack+

bash jstack+.sh "jstack-test"

应该能看到 jstack 的线程 dump 输出,说明脚本在进程树中找到了被 lein run 启动的 JVM 进程。