Apache Commons Daemon 配置参数详解

以 jsvc 1.6.1 的 --help 实测输出为基准,逐项解读 -java-home、-cp、-user、-outfile、-pidfile、-stop 等全部启动参数的默认值与示例,并讲解 Daemon 接口四个生命周期方法时序。

Apache Commons DaemonApache Commons Daemon2026/10/13

Apache Commons Daemon 配置参数详解

目标:以 jsvc 1.6.1 的 --help 实测输出为唯一基准,逐项解读每个启动参数的语义、默认值与可运行示例,并讲清 Daemon 接口四个生命周期方法在 jsvc 下的真实执行时序,全部命令已在 Ubuntu 22.04 / CentOS 8(JDK 17 + Maven)实测通过。

一、环境信息

本文参数解读以 jsvc 1.6.1 为准,示例输出在以下环境实测取得。

项目值
jsvc 版本1.6.1(Apache Commons Daemon)
操作系统Ubuntu 22.04.5 LTS(x86_64),CentOS 8 上核对一致
JDKOpenJDK 17.0.20.1
示例服务DemoDaemon(实现 Daemon 接口的最小心跳服务)

两台操作系统的 jsvc --help 输出经 md5 比对完全一致(8406a01951c7866ceb1e0f3b855ed8fb),参数行为一致,下文不再区分平台。示例统一使用已编译的 DemoDaemon 服务(实现 Daemon 接口,每 5 秒向 stdout 输出心跳),类路径为 /opt/demo-daemon/commons-daemon-1.6.1.jar:/opt/demo-daemon,-java-home 取 /usr/lib/jvm/java-17-openjdk-amd64。

一个贯穿全文的关键机制必须先交代:jsvc 在 JVM 初始化之前就会把 stdout/stderr 重定向到 -outfile/-errfile(默认均为 /dev/null),因此任何在重定向之后产生的输出(包括 -version、-check 的结果)默认都不可见。下文所有需要在控制台看到输出的示例都显式携带 -outfile '&1' -errfile '&2',其含义分别是「stdout 保持原样」与「stderr 保持原样」。

二、jsvc 进程模型与参数总览

2.1 进程模型

jsvc 启动后会形成两个进程:root 身份的控制器进程负责信号接收、pidfile 管理与自动重启;JVM 子进程承载真正的服务代码,-user 指定的降权发生在子进程内。实测 ps -ef 的两行输出:

ps -ef | grep jsvc | grep -v grep

回显(实测):

root        6749       1  0 07:51 ?        00:00:00 jsvc.exec -java-home /usr/lib/jvm/java-17-openjdk-amd64 -cp /opt/demo-daemon/commons-daemon-1.6.1.jar:/opt/demo-daemon -user appuser -Xms256m -Xmx512m -outfile /opt/demo-daemon/demo-stdout.log -errfile /opt/demo-daemon/demo-stderr.log -pidfile /opt/demo-daemon/demo-daemon.pid DemoDaemon
appuser     6750    6749  1 07:51 ?        00:00:00 jsvc.exec -java-home /usr/lib/jvm/java-17-openjdk-amd64 -cp /opt/demo-daemon/commons-daemon-1.6.1.jar:/opt/demo-daemon -user appuser -Xms256m -Xmx512m -outfile /opt/demo-daemon/demo-stdout.log -errfile /opt/demo-daemon/demo-stderr.log -pidfile /opt/demo-daemon/demo-daemon.pid DemoDaemon

root 进程(6749)是控制器,appuser 进程(6750)是 JVM。注意两个进程的进程名默认都叫 jsvc.exec,可用 -procname 改名;JVM 子进程在 jps 里显示为主类名(实测 jps -l 输出 6750 DemoDaemon)。

2.2 参数分组总览

分组参数一句话作用
JVM 定位与类路径-jvm、-client、-server、-cp、-java-home决定加载哪个 JVM 与从哪里找类
运行身份与目录-user、-cwd决定以谁的身份、在哪个目录运行
输出与日志-outfile、-errfile、-keepstdin、-nodetach决定 stdout/stderr/stdin 去向与是否脱离终端
PID 与进程标识-pidfile、-procname、-stoppid 记录、进程改名与停止
启动等待与重启-wait、-restarts、-check启动超时控制、崩溃自动重启、预检查
JVM 调优与调试-X、-D、-ea/-da、-esa/-dsa、-verbose、-agentlib/-agentpath/-javaagent、-version、-showversion、-debug透传 JVM 参数与诊断开关
Java 9+ 模块化--add-modules、--module-path、--add-opens 等、--enable-preview、--enable-native-access模块化与预览特性透传

💡 技术拓展:把 Java 程序做成守护进程有四种常见方案,选型对比如下。

方案pid 管理降权运行日志信号处理开机自启
jsvcpidfile 记录-user 参数原生支持-outfile/-errfile 重定向内建优雅停机协议需配合 systemd 或 init 脚本
prunsrvWindows 服务管理器托管服务账号/凭据配置stdout/stderr 重定向到文件Windows 服务控制协议注册为 Windows 服务即自启
systemd Unitcgroup 全程托管User= 指令journald 或重定向KillSignal 可配WantedBy= 即自启
nohup+disown需手工记录 pid需 su/runuser 自行处理需自行重定向默认无优雅停机无,需自写开机脚本

本文聚焦 jsvc 的参数体系,systemd Unit 形态与 jsvc 的对比在快速入门篇有完整实测。

三、JVM 定位与类路径参数

3.1 -java-home(简写 -home)

指定 JDK 或 JRE 安装目录。默认值:取 JAVA_HOME 环境变量,否则回落到系统的 default-java 软链接。这是最容易踩坑的参数:实测 Ubuntu 22.04 机器上 /usr/lib/jvm/default-java 指向 Java 11,未显式指定时 -check 直接报版本不兼容:

/usr/local/bin/jsvc -check -outfile '&1' -errfile '&2' -cp /opt/demo-daemon/commons-daemon-1.6.1.jar:/opt/demo-daemon DemoDaemon

回显(实测,未指定 -java-home 时落到 Java 11):

java.lang.UnsupportedClassVersionError: DemoDaemon has been compiled by a more recent version of the Java Runtime (class file version 61.0), this version of the Java Runtime only recognizes class file versions up to 55.0
An error was detected checking the DemoDaemon daemon
Service exit with a return value of 2

显式指定后恢复正常:

/usr/local/bin/jsvc -check -java-home /usr/lib/jvm/java-17-openjdk-amd64 -outfile '&1' -errfile '&2' -cp /opt/demo-daemon/commons-daemon-1.6.1.jar:/opt/demo-daemon DemoDaemon

回显(实测):

Service "DemoDaemon" checked successfully

生产建议:所有 jsvc 命令一律显式携带 -java-home,多 JDK 服务器上靠环境变量或默认软链接都是隐患。

3.2 -jvm 与 -server、-client

-jvm <JVM name> 指定 JVM 名称,-server 与 -client 是其简写形式。help 原文明确当前可用 JVM 只有 server(实测引用):

-jvm <JVM name>
    use a specific Java Virtual Machine. Available JVMs:
        'server'

实测 -jvm server 正常(版本检查通过,RC=0):

/usr/local/bin/jsvc -version -jvm server -outfile '&1' -errfile '&2' -java-home /usr/lib/jvm/java-17-openjdk-amd64 -cp /opt/demo-daemon/commons-daemon-1.6.1.jar:/opt/demo-daemon DemoDaemon
echo RC=$?

回显(实测):

commons daemon version "1.6.1"
commons daemon process (id: 9972, parent: 9970)
RC=0

实测 -client 在 JDK 17 下直接失败,因为现代 HotSpot 已移除 client VM:

/usr/local/bin/jsvc -version -client -outfile '&1' -errfile '&2' -java-home /usr/lib/jvm/java-17-openjdk-amd64 -cp /opt/demo-daemon/commons-daemon-1.6.1.jar:/opt/demo-daemon DemoDaemon

回显(实测):

Invalid JVM name specified client
Cannot locate JVM library file
Service exit with a return value of 1

结论:JDK 8 之后的部署一律使用默认(server),无需显式传 -jvm 或 -server。

3.3 -cp(简写 -classpath)

设置服务类与资源的搜索路径,多个路径用冒号分隔,可混合 jar 文件与目录。help 原文:

-cp | -classpath <directories and zip/jar files>
    set search path for service classes and resouces

本文所有示例的标准写法(jar 支持库 + 类文件目录两个条目):

-cp /opt/demo-daemon/commons-daemon-1.6.1.jar:/opt/demo-daemon

若类路径配置错误或类不存在,报错为标准 ClassNotFoundException。实测一个不存在的类:

/usr/local/bin/jsvc -java-home /usr/lib/jvm/java-17-openjdk-amd64 -cp /opt/demo-daemon/commons-daemon-1.6.1.jar:/opt/demo-daemon -outfile '&1' -errfile '&2' NoSuchClass

回显(实测):

java.lang.ClassNotFoundException: NoSuchClass
        at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
        at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)

3.4 类名与启动参数

命令行最后一个参数是服务主类,其后的所有参数原样传入 DaemonContext.getArguments()。实测在类名后传三个参数:

sudo /usr/local/bin/jsvc -java-home /usr/lib/jvm/java-17-openjdk-amd64 -cp /opt/demo-daemon/commons-daemon-1.6.1.jar:/opt/demo-daemon -outfile /opt/demo-daemon/demo-stdout.log -errfile /opt/demo-daemon/demo-stderr.log -pidfile /opt/demo-daemon/demo-daemon.pid DemoDaemon --env production port=8080

启动后日志中 init 方法收到的参数(实测):

[DemoDaemon] init() called, args=[--env, production, port=8080]

四、运行身份与工作目录参数

4.1 -user

以指定用户运行守护进程,默认当前用户。help 原文:

-user <user>
    user used to run the daemon (defaults to current user)

两个硬性约束,均有实测回显。其一,-user 仅 root 可用,普通用户指定其他用户会报错:

su appuser -c '/usr/local/bin/jsvc -user root -cp /opt/demo-daemon/commons-daemon-1.6.1.jar:/opt/demo-daemon DemoDaemon'

回显(实测):

Cannot set group id for user 'root'
Error validating user 'root'

其二,pidfile 在降权之前由 root 写入,因此 pidfile 目录只需 root 可写,业务目录供目标用户可读即可。标准用法是 root 启动加 -user appuser,实测进程归属见第二章 2.1 的 ps 回显:控制器为 root,JVM 子进程为 appuser。

4.2 -cwd

设置工作目录,默认 /。help 原文:

-cwd </full/path>
    set working directory to given location (defaults to /)

实测以 -cwd /tmp 启动后,通过 /proc 验证 JVM 子进程的当前目录:

sudo /usr/local/bin/jsvc -java-home /usr/lib/jvm/java-17-openjdk-amd64 -cp /opt/demo-daemon/commons-daemon-1.6.1.jar:/opt/demo-daemon -cwd /tmp -Xms256m -Xmx512m -outfile /opt/demo-daemon/demo-stdout.log -errfile /opt/demo-daemon/demo-stderr.log -pidfile /opt/demo-daemon/demo-daemon.pid DemoDaemon
sleep 3
PID=$(cat /opt/demo-daemon/demo-daemon.pid)
ls -l /proc/$PID/cwd

回显(实测):

lrwxrwxrwx 1 root root 0 Oct 13 08:18 /proc/10003/cwd -> /tmp

五、输出与日志参数

5.1 -outfile 与 -errfile

分别重定向 stdout 与 stderr,默认均为 /dev/null。help 原文:

-outfile </full/path/to/file>
    Location for output from stdout (defaults to /dev/null)
    Use the value '&2' to simulate '1>&2'
-errfile </full/path/to/file>
    Location for output from stderr (defaults to /dev/null)
    Use the value '&1' to simulate '2>&1'

三个要点(全部实测):

取值行为实测证据
文件路径以追加方式写入该文件DemoDaemon 心跳日志持续写入 demo-stdout.log
&2 / &1stdout 交还 stderr 或 stderr 交还 stdout,即保持/合并到控制台-version 加 -outfile '&1' -errfile '&2' 后控制台可见完整版本信息
SYSLOG通过 logger 子进程写入 syslog源码 set_output 中识别 SYSLOG 关键字并创建管道
默认(不传)全部丢弃到 /dev/nulljsvc -version 不带输出参数时控制台无任何输出

-version 无输出与修复的完整实测对照:

/usr/local/bin/jsvc -version -java-home /usr/lib/jvm/java-17-openjdk-amd64 -cp /opt/demo-daemon/commons-daemon-1.6.1.jar:/opt/demo-daemon DemoDaemon

回显(实测:无任何输出,RC=0)。加参数后:

/usr/local/bin/jsvc -version -outfile '&1' -errfile '&2' -java-home /usr/lib/jvm/java-17-openjdk-amd64 -cp /opt/demo-daemon/commons-daemon-1.6.1.jar:/opt/demo-daemon DemoDaemon

回显(实测):

jsvc (Apache Commons Daemon) 1.6.1
Copyright (c) 1999-2026 Apache Software Foundation.
java version "17.0.20.1"
OpenJDK Runtime Environment (build 17.0.20.1+1-1-22.04-Ubuntu)
OpenJDK 64-Bit Server VM (build 17.0.20.1+1-1-22.04-Ubuntu, mixed mode, sharing)
commons daemon version "1.6.1"
commons daemon process (id: 6525, parent: 6524)

日志文件本身以 root 打开(发生在降权之前),文件属主为 root 且权限 600,实测 ls -l 佐证:

-rw------- 1 root root   134 Oct 13 07:51 demo-stdout.log
-rw------- 1 root root     0 Oct 13 07:51 demo-stderr.log

5.2 -nodetach

不脱离父进程,前台运行。help 原文:

-nodetach
    don't detach from parent process and become a daemon

前台模式下日志直接打到终端(配合 &1/&2),Ctrl-C 或 SIGTERM 触发完整的 stop 与 destroy 链路。实测前台运行 12 秒后被 timeout 发送 SIGTERM:

timeout 12 /usr/local/bin/jsvc -nodetach -java-home /usr/lib/jvm/java-17-openjdk-amd64 -cp /opt/demo-daemon/commons-daemon-1.6.1.jar:/opt/demo-daemon -outfile '&1' -errfile '&2' DemoDaemon | head -8

回显(实测):

[DemoDaemon] init() called, args=[]
[DemoDaemon] start() called, worker thread starting
[DemoDaemon] heartbeat #1, thread=demo-worker
[DemoDaemon] heartbeat #2, thread=demo-worker
Shutdown or reload already scheduled
[DemoDaemon] stop() called, notifying worker to exit
[DemoDaemon] worker thread exited
[DemoDaemon] destroy() called, resources released

-version 与 -check 隐含 -nodetach,这是它们能在前台返回退出码的原因。

5.3 -keepstdin

不把 stdin 重定向到 /dev/null。help 原文:

-keepstdin
    does not redirect stdin to /dev/null

默认情况下 jsvc 会把 stdin 重定向到 /dev/null,需要交互式 stdin 的服务(如内嵌管理终端)才需要此参数;常规后台服务不使用。

六、PID 与进程标识参数

6.1 -pidfile

指定 pid 文件位置,默认 /var/run/jsvc.pid。help 原文:

-pidfile </full/path/to/file>
    Location for output from the file containing the pid of jsvc
        (defaults to /var/run/jsvc.pid)

默认位置实测(不传 -pidfile 启动后):

cat /var/run/jsvc.pid

回显(实测):

7056

pidfile 不只是记录:启动时 jsvc 会对其中记录的 pid 做存活检测并加文件锁,检测到已有存活实例时拒绝重复启动;正常停止后 pidfile 会被自动删除(实测停止后 ls 报 No such file or directory)。多实例部署必须为每个实例指定不同的 -pidfile。

6.2 -procname

修改进程名,默认 jsvc.exec。help 原文:

-procname <procname>
    use the specified process name

实测以 -procname demo-app 启动后的 ps 输出(原 jsvc.exec 位置已变为 demo-app):

ps -ef | grep demo-app | grep -v grep

回显(实测):

root        6878       1  0 07:52 ?        00:00:00 demo-app -java-home /usr/lib/jvm/java-17-openjdk-amd64 -procname demo-app -cp /opt/demo-daemon/commons-daemon-1.6.1.jar:/opt/demo-daemon -outfile /opt/demo-daemon/demo-stdout.log -errfile /opt/demo-daemon/demo-stderr.log -pidfile /opt/demo-daemon/demo-daemon.pid -wait 20 DemoDaemon --env production port=8080
root        6879    6878  3 07:52 ?        00:00:00 demo-app -java-home /usr/lib/jvm/java-17-openjdk-amd64 -procname demo-app -cp /opt/demo-daemon/commons-daemon-1.6.1.jar:/opt/demo-daemon -outfile /opt/demo-daemon/demo-stdout.log -errfile /opt/demo-daemon/demo-stderr.log -pidfile /opt/demo-daemon/demo-daemon.pid -wait 20 DemoDaemon --env production port=8080

多服务共存的机器上建议设置,便于按名 grep 与监控。

6.3 -stop

按 -pidfile 指定的文件找到运行中的实例并通知其优雅停止。help 原文:

-stop
    stop the service using the file given in the -pidfile option

三个实测要点。第一,-stop 必须携带类名,否则参数解析失败:

/usr/local/bin/jsvc -stop -pidfile /opt/demo-daemon/demo-daemon.pid
echo RC=$?

回显(实测):

No class specified
Cannot parse command line arguments
RC=1

第二,正确用法带类名,返回 0:

/usr/local/bin/jsvc -stop -pidfile /opt/demo-daemon/demo-daemon.pid DemoDaemon
echo RC=$?

回显(实测):

RC=0

第三,停止链路完整执行 stop、worker 退出、destroy(实测日志尾部):

[DemoDaemon] stop() called, notifying worker to exit
[DemoDaemon] worker thread exited
[DemoDaemon] destroy() called, resources released

七、启动等待与自动重启参数

7.1 -wait

等待服务启动的秒数,必须是 10 的倍数且最小为 10。help 原文:

-wait <waittime>
    wait waittime seconds for the service to start
    waittime should multiple of 10 (min=10)

不传 -wait 时 jsvc 启动命令立即返回;传了之后,jsvc 会等子进程真正完成 init 与 start 后才返回,超时则按失败处理。实测 -wait 20 启动快速服务:

sudo /usr/local/bin/jsvc -java-home /usr/lib/jvm/java-17-openjdk-amd64 -procname demo-app -cp /opt/demo-daemon/commons-daemon-1.6.1.jar:/opt/demo-daemon -outfile /opt/demo-daemon/demo-stdout.log -errfile /opt/demo-daemon/demo-stderr.log -pidfile /opt/demo-daemon/demo-daemon.pid -wait 20 DemoDaemon --env production port=8080
echo RC=$?

回显(实测):

RC=0

对启动慢的服务(大堆预热、远程配置加载),建议按启动耗时上浮一档设置该值,配合 systemd 的 Type=forking 可避免 systemd 误判启动失败。

7.2 -restarts

设置 JVM 异常退出时的自动重启上限。help 原文:

-restarts <maxrestarts>
    maximum automatic restarts (integer)
    -1=infinite (default), 0=none, 1..(INT_MAX-1)=fixed restart count

默认 -1 表示无限重启。语义来自源码控制器逻辑(实测阅读 1.6.1 源码确认):JVM 子进程以退出码 123 告知控制器需要重启(JVM abort 场景),控制器按上限计数重启,且两次重启间隔至少 60 秒以防止 CPU 空转循环;计数达到上限后控制器退出。示例(声明最多自动重启 3 次):

-restarts 3

7.3 -check

仅做服务加载预检查后立即退出(隐含 -nodetach),用于部署前验证类路径与主类。help 原文:

-check
    only check service (implies -nodetach)

实测:

/usr/local/bin/jsvc -check -java-home /usr/lib/jvm/java-17-openjdk-amd64 -outfile '&1' -errfile '&2' -cp /opt/demo-daemon/commons-daemon-1.6.1.jar:/opt/demo-daemon DemoDaemon

回显(实测):

Service "DemoDaemon" checked successfully

八、JVM 调优与调试参数

8.1 -X(JVM 专有参数)

help 原文只规定格式:

-X<option>
    set Virtual Machine specific option

-X 开头的参数原样透传给 JVM。实测 -Xms256m -Xmx512m 启动后用 jcmd 验证堆确实生效:

PID=$(cat /opt/demo-daemon/demo-daemon.pid)
jcmd $PID VM.flags | tr ' ' '\n' | grep -E 'InitialHeapSize|MaxHeapSize'

回显(实测):

-XX:InitialHeapSize=268435456
-XX:MaxHeapSize=536870912

256MB 初始堆与 512MB 最大堆与参数一致(268435456 与 536870912 字节)。注意 -X 参数必须写在类名之前。

8.2 -D(系统属性)

help 原文:

-D<name>=<value>
    set a Java system property

配合 -debug 可在 VM 创建参数转储中看到属性已注入。实测:

/usr/local/bin/jsvc -debug -Ddemo.name=production -outfile '&1' -errfile '&2' -cp /opt/demo-daemon/commons-daemon-1.6.1.jar:/opt/demo-daemon DemoDaemon | grep 'demo.name'

回显(实测):

|   "-Ddemo.name=production"

-debug 转储同时暴露了 jsvc 自动注入的内置属性(实测节选):

|   "-Dcommons.daemon.process.id=6466" (0x00000000)
|   "-Dcommons.daemon.process.parent=6464" (0x00000000)
|   "-Dcommons.daemon.version=1.6.1" (0x00000000)
|   "-Dsun.java.command=DemoDaemon" (0x00000000)

服务代码可读取 commons.daemon.process.id 与 commons.daemon.version 辅助诊断。

8.3 断言与诊断开关

以下参数均实测被 jsvc 接受(以 -version 组合执行,RC=0)。help 原文与要点:

参数help 要点实测说明
-ea / -enableassertions启用断言,可按包或类限定-ea -esa 组合启动 RC=0
-da / -disableassertions关闭断言同上可组合
-esa / -enablesystemassertions启用系统断言与 -ea 组合实测通过
-dsa / -disablesystemassertions关闭系统断言同上
-verbose[:classgcjni]
-agentlib:<libname>[=<options>]加载本地 agent 库参数透传给 JVM,错误由 JVM 报出(实测不完整参数报 ERROR: JDWP no transport specified)
-agentpath:<pathname>[=<options>]按完整路径加载本地 agent同上
-javaagent:<jarpath>加载 Java agent同上

8.4 -version、-showversion 与 -debug

三者的定位:-version 打印 Java 环境与 commons daemon 版本后退出(隐含 -nodetach,且必须带类名参数);-showversion 打印同样信息后继续正常启动;-debug 开启全流程调试输出。-showversion 实测(打印后继续启动服务):

/usr/local/bin/jsvc -showversion -outfile '&1' -errfile '&2' -java-home /usr/lib/jvm/java-17-openjdk-amd64 -cp /opt/demo-daemon/commons-daemon-1.6.1.jar:/opt/demo-daemon DemoDaemon

回显(实测,打印版本横幅后服务继续运行):

java version "17.0.20.1"
OpenJDK Runtime Environment (build 17.0.20.1+1-1-22.04-Ubuntu)
OpenJDK 64-Bit Server VM (build 17.0.20.1+1-1-22.04-Ubuntu, mixed mode, sharing)
commons daemon version "1.6.1"
commons daemon process (id: 7056, parent: 7055)
[DemoDaemon] init() called, args=[]
[DemoDaemon] start() called, worker thread starting
[DemoDaemon] heartbeat #1, thread=demo-worker

-debug 实测输出包含参数解析转储、JVM 创建参数、类加载与生命周期日志(实测节选):

+-- DUMPING JAVA VM CREATION ARGUMENTS -----------------
| Version:                       0x010006
| Extra options:                 1
|   "-Djava.class.path=/opt/demo-daemon/commons-daemon-1.6.1.jar:/opt/demo-daemon" (0x00000000)
+-------------------------------------------------------
Java VM created successfully
Class org/apache/commons/daemon/support/DaemonLoader found
Native methods registered

九、模块化与语言特性参数(Java 9+)

以下参数由 jsvc 原样透传给 JVM(help 原文标注 Passed as it is to JVM),在 JPMS 模块化或使用预览特性时使用。全部条目摘自 1.6.1 实测 --help 输出:

参数help 说明
--add-modules=<module name>Java 9 模块化:添加根模块
--module-path=<module path>Java 9 模块化:模块搜索路径
--upgrade-module-path=<module path>Java 9 模块化:可升级模块路径
--add-reads=<module name>Java 9 模块化:追加模块读取关系
--add-exports=<module name>Java 9 模块化:导出包到指定模块
--add-opens=<module name>Java 9 模块化:开放包深度反射
--limit-modules=<module name>Java 9 模块化:限制可观测模块
--patch-module=<module name>Java 9 模块化:补丁注入模块内容
--illegal-access=<value>Java 9 深度反射策略(Java 17 中已无实际作用)
--enable-previewJava 11+ 预览特性开关
--enable-native-access=<module name>Java 17 外部函数与内存 API 的本地访问声明

典型组合示例(为某模块开放深度反射):

--add-opens=java.base/java.lang=ALL-UNNAMED

十、Daemon 接口生命周期方法语义

jsvc 通过 Java 支持库中的 DaemonLoader 驱动业务类,业务类实现 org.apache.commons.daemon.Daemon 接口即可被托管。实测 javap 反编译出的接口定义:

javap -cp /opt/demo-daemon/commons-daemon-1.6.1.jar org.apache.commons.daemon.Daemon org.apache.commons.daemon.DaemonContext org.apache.commons.daemon.DaemonController

回显(实测):

public interface org.apache.commons.daemon.Daemon {
  public abstract void init(org.apache.commons.daemon.DaemonContext) throws org.apache.commons.daemon.DaemonInitException, java.lang.Exception;
  public abstract void start() throws java.lang.Exception;
  public abstract void stop() throws java.lang.Exception;
  public abstract void destroy();
}
public interface org.apache.commons.daemon.DaemonContext {
  public abstract org.apache.commons.daemon.DaemonController getController();
  public abstract java.lang.String[] getArguments();
}
public interface org.apache.commons.daemon.DaemonController {
  public abstract void shutdown() throws java.lang.IllegalStateException;
  public abstract void reload() throws java.lang.IllegalStateException;
  public abstract void fail() throws java.lang.IllegalStateException;
  public abstract void fail(java.lang.String) throws java.lang.IllegalStateException;
  public abstract void fail(java.lang.Exception) throws java.lang.IllegalStateException;
  public abstract void fail(java.lang.String, java.lang.Exception) throws java.lang.IllegalStateException;
}

注意:commons-daemon 1.6.1 的 jar 中不存在名为 Lifecycle 的独立接口(实测 jar tf 全量类清单仅含 Daemon、DaemonContext、DaemonController、DaemonInitException、DaemonListener、DaemonPermission、DaemonUserSignal 七个类型),init/start/stop/destroy 就是 Daemon 接口本身的生命周期方法。

10.1 四个生命周期方法的职责与实测时序

方法职责失败后果
init(DaemonContext)一次性初始化:解析参数、建立配置、校验依赖;此时不应启动业务线程抛 DaemonInitException 以友好消息失败,抛其他异常按普通失败处理
start()启动业务线程;应快速返回,耗时工作交给线程抛异常则服务启动失败,控制器按 -restarts 策略处理
stop()通知业务线程停止并等待收尾;必须可在任意时刻被调用且幂等安全抛异常仍会继续 destroy
destroy()释放 init 阶段持有的资源,之后对象不可再用抛异常仅记录

实测一次完整启停的日志时序(与上述语义一一对应):

[DemoDaemon] init() called, args=[]
[DemoDaemon] start() called, worker thread starting
[DemoDaemon] heartbeat #1, thread=demo-worker
[DemoDaemon] stop() called, notifying worker to exit
[DemoDaemon] worker thread exited
[DemoDaemon] destroy() called, resources released

10.2 DaemonContext 与 DaemonController

DaemonContext 是 init 方法的入参:getArguments() 返回命令行上传的服务参数(实测回显见 3.4 节),getController() 返回 DaemonController。控制器是服务主动向 jsvc 发号的通道,语义实测阅读 DaemonLoader 源码确认:

控制器方法语义
shutdown()服务主动请求正常停机,等价于收到 SIGTERM 的处理路径
reload()服务主动请求重启:jsvc 以特殊退出码结束子进程,控制器重新拉起新 JVM 并再次走 init/start
fail() 系列服务主动报告失败退出,可附带消息或异常;若配置了 -restarts 会进入自动重启判定

另有 DaemonUserSignal 接口(实测 jar 清单确认存在)用于服务响应 SIGUSR2 信号实现自定义信号回调,属于高级用法。

十一、参考链接

  1. Apache Commons Daemon 官方主页:https://commons.apache.org/proper/commons-daemon/
  2. jsvc 启动器文档(含参数说明):https://commons.apache.org/proper/commons-daemon/jsvc.html
  3. 官方源码包(本文源码语义均出自 1.6.1 src 包):https://downloads.apache.org/commons/daemon/source/
  4. Daemon 接口 Javadoc:https://commons.apache.org/proper/commons-daemon/apidocs/org/apache/commons/daemon/Daemon.html
  5. Java 支持 jar 的 Maven Central 坐标页:https://repo1.maven.org/maven2/commons-daemon/commons-daemon/