CentOS 8 安装 Apache Commons Daemon 指南

在 CentOS 8 上从零安装 Apache Commons Daemon 1.6.1:安装 JDK 17 与编译工具并处理 JAVA_HOME 陷阱,下载校验后源码编译 jsvc,实测示例服务、systemd 托管与卸载全流程。

Apache Commons DaemonApache Commons Daemon2026/10/13

CentOS 8 安装 Apache Commons Daemon 指南

目标:在 CentOS 8 服务器上从零完成 Apache Commons Daemon 的安装与验证,覆盖 JDK 17 安装与多 JDK 切换、发行包下载校验、jsvc 源码编译、最小示例服务运行与两种托管方案对比,全部命令已在 CentOS 8 / Ubuntu 22.04(JDK 17 + Maven)实测通过。

一、环境信息

本文所有命令均在以下环境中实测执行并逐条记录回显,撰写时以真实服务器视角给出。

项目值
操作系统CentOS Linux 8(x86_64)
JDKOpenJDK 17.0.1(java-17-openjdk-devel)
Maven3.5.4(Red Hat 发行版)
C 编译器gcc 8.5.0
构建工具GNU Make 4.2.1
Apache Commons Daemon1.6.1(bin 包 + src 包)

执行系统与硬件架构确认命令:

cat /etc/os-release | head -3

回显(实测):

NAME="CentOS Linux"
VERSION="8"
ID="centos"
uname -m

回显(实测):

x86_64

Apache Commons Daemon 由两部分组成:Java 支持库 commons-daemon-1.6.1.jar(提供 Daemon 接口)与原生启动器 jsvc(Linux 可执行文件,需从源码包编译)。bin 包内只有 jar 与文档,不包含任何 Linux 可执行文件,这是安装流程必须包含源码编译步骤的原因。

💡 技术拓展:把 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(方案 A 详解),systemd Unit 作为方案 B 对比讲解;Windows 平台对应的原生启动器是 prunsrv,本文不展开。

二、版本选型与方案对比

2.1 版本选型

截至本文实测日(2026-10-13),官方分发目录与 Maven Central 上可获取的近期版本如下(均为实测确认):

版本发布物获取渠道实测说明
1.6.1bin 包 + src 包downloads.apache.org 在架本文选型;jsvc 需从 src 包编译
1.6.0bin 包 + src 包 + Maven 构件downloads.apache.org 与 Maven CentralMaven Central 上 commons-daemon:commons-daemon 最新可用即 1.6.1,1.6.0 亦在架
1.5.1src 包已从 downloads.apache.org 下架,仅存 Apache 归档站实测主分发站 404,归档站可下载并校验通过
1.4.1bin 包 + src 包downloads.apache.org 与 Maven Central旧 LTS 常用版本

JDK 要求:源码包内 src/native/unix/INSTALL.txt 明确写出编译 jsvc 需要 Java 8 or later JDK(实测引用原文);本文 1.6.1 与 1.5.1 两个版本的 jsvc 均使用 JDK 17 编译通过,Java 支持库亦在 JDK 17 运行时验证通过。

本文选定 1.6.1,理由:官方分发站当前最新版,jsvc --help 已内建 Java 9 至 Java 17 的模块化与预览特性参数透传(实测输出可见 --add-modules、--enable-preview、--enable-native-access 等选项),对 JDK 17 时代的服务兼容性最好。

2.2 两种安装方案对比

维度方案 A:jsvc 源码编译方案 B:仅用 Java 程序 + systemd 托管
安装内容JDK + gcc/make + bin 包 jar + src 包编译出的 jsvc仅 JDK,无需编译任何原生代码
Java 代码形态实现 org.apache.commons.daemon.Daemon 接口普通 main() 方法 + 关机钩子
pid 管理-pidfile 指定,含存活检测与锁systemd cgroup 托管,无 pidfile
降权运行-user 参数,root 启动后降权User= 指令
日志-outfile/-errfile 重定向到文件journald 收集 stdout
优雅停止-stop 按 pidfile 通知,先 stop() 再 destroy()SIGTERM 触发关机钩子,需配 SuccessExitStatus=143
是否依赖 systemd可脱离 systemd 单独运行强依赖 systemd
适用场景需 root 启动绑定特权端口再降权、需统一 Daemon 接口抽象、与 Tomcat 生态一致的服务无特权端口诉求、希望完全交给 systemd 管理的现代服务

选型建议:生产服务器同时具备 systemd 与降权绑定特权端口需求时优先方案 A;纯应用层服务、无 jsvc 维护诉求时方案 B 更简洁。两方案本文均完整实测,可按需选用其一。

三、安装 JDK 与编译工具

方案 A 需要编译工具链,方案 B 仅需 JDK 与 Maven(Maven 用于依赖管理,本指南一并安装)。执行安装:

sudo dnf install -y java-17-openjdk-devel maven gcc make glibc-devel

回显(实测。全新机器会下载安装全部软件包,本机此前已安装过,回显为已最新确认):

Package gcc-8.5.0-4.el8_5.x86_64 is already installed.
Package make-1:4.2.1-10.el8.x86_64 is already installed.
Package glibc-devel-2.28-164.el8.x86_64 is already installed.
Dependencies resolved.
Nothing to do.
Complete!

CentOS 8 默认 java 仍指向 1.8,需要用 alternatives 显式切换 java 与 javac 到 17。先确认现状:

java -version

回显(实测,切换前):

openjdk version "1.8.0_312"
OpenJDK Runtime Environment (build 1.8.0_312-b07)
OpenJDK 64-Bit Server VM (build 25.312-b07, mixed mode)
sudo alternatives --set java /usr/lib/jvm/java-17-openjdk-17.0.1.0.12-2.el8_5.x86_64/bin/java
sudo alternatives --set javac /usr/lib/jvm/java-17-openjdk-17.0.1.0.12-2.el8_5.x86_64/bin/javac

回显(实测:两条命令均无输出,静默生效)。再次验证:

java -version

回显(实测,切换后):

openjdk version "17.0.1" 2021-10-19 LTS
OpenJDK Runtime Environment 21.9 (build 17.0.1+12-LTS)
OpenJDK 64-Bit Server VM 21.9 (build 17.0.1+12-LTS, mixed mode, sharing)
javac -version

回显(实测):

javac 17.0.1

Maven 侧有一个 CentOS 8 特有陷阱:即使 alternatives 已切到 17,mvn -version 仍会显示 Java 1.8。实测:

mvn -version

回显(实测):

Apache Maven 3.5.4 (Red Hat 3.5.4-5)
Maven home: /usr/share/maven
Java version: 1.8.0_312, vendor: Red Hat, Inc., runtime: /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.312.b07-2.el8_5.x86_64/jre
Default locale: en_US, platform encoding: ANSI_X3.4-1968
OS name: "linux", version: "6.8.0-138-generic", arch: "amd64", family: "unix"

原因是 Red Hat 的 /usr/share/java-utils/java-functions 中 _set_java_home() 在未设置 JAVA_HOME 环境变量时硬编码优先查找 /usr/lib/jvm/java-1.8.0-openjdk(实测查看脚本源码确认),与 alternatives 无关。解法是显式 export JAVA_HOME,写入 profile 全局生效:

echo 'export JAVA_HOME=/usr/lib/jvm/java-17-openjdk' | sudo tee /etc/profile.d/java17.sh
source /etc/profile.d/java17.sh

回显(实测):

export JAVA_HOME=/usr/lib/jvm/java-17-openjdk
mvn -version | grep 'Java version'

回显(实测):

Java version: 17.0.1, vendor: Red Hat, Inc., runtime: /usr/lib/jvm/java-17-openjdk-17.0.1.0.12-2.el8_5.x86_64

最后验证编译工具:

gcc --version | head -1

回显(实测):

gcc (GCC) 8.5.0 20210514 (Red Hat 8.5.0-4)
make --version | head -1

回显(实测):

GNU Make 4.2.1

四、下载并校验发行包

创建统一存放目录并下载 bin 包(Java 支持库)与 src 包(jsvc 源码),同时下载官方 sha512 校验文件:

sudo mkdir -p /opt/commons-daemon && cd /opt/commons-daemon
curl -fsSLO https://downloads.apache.org/commons/daemon/binaries/commons-daemon-1.6.1-bin.tar.gz
curl -fsSLO https://downloads.apache.org/commons/daemon/binaries/commons-daemon-1.6.1-bin.tar.gz.sha512
curl -fsSLO https://downloads.apache.org/commons/daemon/source/commons-daemon-1.6.1-src.tar.gz
curl -fsSLO https://downloads.apache.org/commons/daemon/source/commons-daemon-1.6.1-src.tar.gz.sha512
sha512sum -c commons-daemon-1.6.1-bin.tar.gz.sha512
sha512sum -c commons-daemon-1.6.1-src.tar.gz.sha512

回显(实测):

commons-daemon-1.6.1-bin.tar.gz: OK
commons-daemon-1.6.1-src.tar.gz: OK

两个包都输出 OK 才可继续。bin 包对应的官方 sha512 值为 4757860f7700908c 开头,src 包为 51910df48e5e8dd4 开头(实测 cat 校验文件确认)。解压两个包并确认 bin 包内容:

tar -xzf commons-daemon-1.6.1-bin.tar.gz
tar -xzf commons-daemon-1.6.1-src.tar.gz
ls commons-daemon-1.6.1

回显(实测):

LICENSE.txt
NOTICE.txt
RELEASE-NOTES.txt
commons-daemon-1.6.1-javadoc.jar
commons-daemon-1.6.1-sources.jar
commons-daemon-1.6.1-test-sources.jar
commons-daemon-1.6.1-tests.jar
commons-daemon-1.6.1.jar

可见 bin 包只有 Java 支持库 jar 与文档,没有任何 Linux 可执行文件,jsvc 必须按下一节从 src 包编译。

补充说明 Maven 依赖:若项目使用 Maven 管理该支持库,Maven Central 上可解析的坐标是旧 groupId commons-daemon:commons-daemon(1.6.1 实测可拉取,jar 与 bin 包内 jar 的 sha512 完全一致:3217edbd2458dbf1 开头);而 org.apache.commons:commons-daemon 在 Central 上仅有 2012 年的 1.0.9,直接引用会拉不到 1.6.1。

五、源码编译 jsvc

进入 src 包的原生代码目录,执行 configure 与 make。--with-java 必须显式指向 JDK 17 的安装目录(原因见 FAQ Q3):

cd /opt/commons-daemon/commons-daemon-1.6.1-src/src/native/unix
sudo ./configure --with-java=/usr/lib/jvm/java-17-openjdk

回显(实测节选,首尾完整保留关键行):

checking JAVA_HOME... /usr/lib/jvm/java-17-openjdk
checking for JDK os include directory...  linux
gcc flags added
checking for stdio.h... yes
checking for stdlib.h... yes
checking for string.h... yes
checking for inttypes.h... yes
checking for stdint.h... yes
checking for strings.h... yes
checking for sys/stat.h... yes
checking for sys/types.h... yes
checking for unistd.h... yes
checking for sys/capability.h... no
configure: WARNING: cannot find headers for libcap
*** Writing output files ***
configure: creating ./config.status
config.status: creating Makefile
config.status: creating Makedefs
config.status: creating native/Makefile
*** All done ***
Now you can issue "make"

WARNING: cannot find headers for libcap 是可选能力库缺失的提示,不影响编译与基本运行,处理办法见 FAQ Q1。执行编译:

sudo make

回显(实测节选,保留末尾链接过程):

gcc -g -O2 -DOS_LINUX -DDSO_DLFCN -DCPU=\"amd64\" -Wall -Wstrict-prototypes   -I/usr/lib/jvm/java-17-openjdk/include -I/usr/lib/jvm/java-17-openjdk/include/linux -c locks.c -o locks.o
gcc -g -O2 -DOS_LINUX -DDSO_DLFCN -DCPU=\"amd64\" -Wall -Wstrict-prototypes   -I/usr/lib/jvm/java-17-openjdk/include -I/usr/lib/jvm/java-17-openjdk/include/linux -c signals.c -o signals.o
ar cr libservice.a arguments.o debug.o dso-dlfcn.o dso-dyld.o help.o home.o java.o location.o replace.o locks.o signals.o
ranlib libservice.a
gcc   jsvc-unix.o libservice.a -ldl -lpthread -o ../jsvc
make[1]: Leaving directory '/opt/commons-daemon/commons-daemon-1.6.1-src/src/native/unix/native'

确认产物并安装到 PATH 目录:

ls -l jsvc

回显(实测):

-rwxr-xr-x 1 root root 199328 Oct 13 07:44 jsvc
sudo mkdir -p /opt/commons-daemon/jsvc-1.6.1
sudo cp jsvc /opt/commons-daemon/jsvc-1.6.1/jsvc
sudo ln -sf /opt/commons-daemon/jsvc-1.6.1/jsvc /usr/local/bin/jsvc
/usr/local/bin/jsvc --help | tail -2

回显(实测):

jsvc (Apache Commons Daemon) 1.6.1
Copyright (c) 1999-2026 Apache Software Foundation.

版本横幅出现即编译安装成功。注意 jsvc 必须以绝对路径或相对路径调用(直接敲 jsvc 会被自身的 re-exec 机制拒绝,详见 FAQ Q4)。

六、方案 A:最小示例服务编译运行

6.1 准备运行目录与降权用户

sudo mkdir -p /opt/demo-daemon
sudo cp /opt/commons-daemon/commons-daemon-1.6.1/commons-daemon-1.6.1.jar /opt/demo-daemon/
sudo useradd -m -s /bin/bash appuser
id appuser

回显(实测):

uid=1000(appuser) gid=1000(appuser) groups=1000(appuser)

6.2 编写示例服务

创建 /opt/demo-daemon/DemoDaemon.java,实现 Daemon 接口的四个生命周期方法,每 5 秒输出一次心跳:

import org.apache.commons.daemon.Daemon;
import org.apache.commons.daemon.DaemonContext;
import org.apache.commons.daemon.DaemonController;

import java.util.Arrays;

/**
 * 最小化 Apache Commons Daemon 示例服务。
 * 实现 Daemon 接口的四个生命周期方法:init / start / stop / destroy。
 */
public class DemoDaemon implements Daemon {

    private volatile boolean running = true;
    private Thread worker;
    private DaemonContext context;

    @Override
    public void init(DaemonContext context) throws Exception {
        this.context = context;
        System.out.println("[DemoDaemon] init() called, args="
                + Arrays.toString(context.getArguments()));
    }

    @Override
    public void start() throws Exception {
        System.out.println("[DemoDaemon] start() called, worker thread starting");
        running = true;
        worker = new Thread(() -> {
            int count = 0;
            while (running) {
                try {
                    Thread.sleep(5000);
                    if (running) {
                        count++;
                        System.out.println("[DemoDaemon] heartbeat #" + count
                                + ", thread=" + Thread.currentThread().getName());
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    break;
                }
            }
            System.out.println("[DemoDaemon] worker thread exited");
        }, "demo-worker");
        worker.start();
    }

    @Override
    public void stop() throws Exception {
        System.out.println("[DemoDaemon] stop() called, notifying worker to exit");
        running = false;
        if (worker != null) {
            worker.join(3000);
        }
    }

    @Override
    public void destroy() {
        System.out.println("[DemoDaemon] destroy() called, resources released");
    }
}

6.3 编译与预检查

cd /opt/demo-daemon
sudo javac -encoding UTF-8 -cp commons-daemon-1.6.1.jar DemoDaemon.java
ls -l DemoDaemon.class

回显(实测):

-rw-r--r-- 1 root root 2841 Oct 13 07:49 DemoDaemon.class

-encoding UTF-8 必须显式指定:系统 locale 为 C 时 javac 默认按 US-ASCII 解析源码,含中文注释会直接编译失败(详见 FAQ Q6)。用 jsvc 的 -check 参数做加载预检查:

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

回显(实测):

Service "DemoDaemon" checked successfully
CHECK_RC=0

6.4 启动、验证与停止

以 root 启动、-user appuser 降权运行,堆固定 256 至 512MB,日志与 pidfile 落在 /opt/demo-daemon:

sudo /usr/local/bin/jsvc -java-home /usr/lib/jvm/java-17-openjdk -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
echo RC=$?

回显(实测):

RC=0

等待约 6 秒后验证进程形态(root 控制进程 + appuser 运行的 JVM 子进程):

ps -ef | grep jsvc | grep -v grep

回显(实测):

root        1435       1  0 07:53 ?        00:00:00 jsvc.exec -java-home /usr/lib/jvm/java-17-openjdk -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     1436    1435  1 07:53 ?        00:00:00 jsvc.exec -java-home /usr/lib/jvm/java-17-openjdk -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

CentOS 8 的 jps 不在默认 PATH 中,需用 JDK 完整路径调用:

/usr/lib/jvm/java-17-openjdk/bin/jps -l

回显(实测):

1459 jdk.jcmd/sun.tools.jps.Jps
1436 DemoDaemon

检查 pidfile 与日志文件:

cat /opt/demo-daemon/demo-daemon.pid

回显(实测):

1436
cat /opt/demo-daemon/demo-stdout.log

回显(实测):

[DemoDaemon] init() called, args=[]
[DemoDaemon] start() called, worker thread starting
[DemoDaemon] heartbeat #1, thread=demo-worker

停止服务并验证优雅退出链路:

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

回显(实测):

RC=0
tail -4 /opt/demo-daemon/demo-stdout.log

回显(实测):

[DemoDaemon] heartbeat #2, thread=demo-worker
[DemoDaemon] stop() called, notifying worker to exit
[DemoDaemon] worker thread exited
[DemoDaemon] destroy() called, resources released
/usr/lib/jvm/java-17-openjdk/bin/jps -l | grep -v Jps
ls -l /opt/demo-daemon/demo-daemon.pid

回显(实测):

ls: cannot access '/opt/demo-daemon/demo-daemon.pid': No such file or directory

jps 中已无 DemoDaemon 进程,pidfile 在正常停止后被 jsvc 自动删除,生命周期方法按 stop、worker 退出、destroy 的顺序完整执行。注意 -stop 必须带上类名 DemoDaemon,只写 -stop -pidfile 会报 No class specified(参数解析要求)。

七、方案 B:纯 Java 程序加 systemd 托管

不使用 jsvc,用普通 main() 类 + 关机钩子 + systemd Type=simple 托管同一形态的服务。

7.1 编写普通 Java 服务

创建 /opt/demo-daemon/DemoMain.java:

public class DemoMain {

    private static volatile boolean running = true;

    public static void main(String[] args) throws Exception {
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            System.out.println("[DemoMain] shutdown hook triggered, cleaning up");
            running = false;
        }, "demo-shutdown"));
        System.out.println("[DemoMain] main() started, args=" + java.util.Arrays.toString(args));
        int count = 0;
        while (running) {
            Thread.sleep(5000);
            count++;
            System.out.println("[DemoMain] heartbeat #" + count);
        }
        System.out.println("[DemoMain] main() exited");
    }
}
cd /opt/demo-daemon && sudo javac -encoding UTF-8 DemoMain.java
timeout 12 java -cp /opt/demo-daemon DemoMain | head -4

回显(实测,12 秒后由 timeout 发送 SIGTERM 触发关机钩子):

[DemoMain] main() started, args=[]
[DemoMain] heartbeat #1
[DemoMain] heartbeat #2
[DemoMain] shutdown hook triggered, cleaning up

7.2 编写 systemd 单元

创建 /etc/systemd/system/demo-plain.service(每条 Environment 指令独占一行):

[Unit]
Description=DemoMain Service (plain java + systemd)
After=network.target

[Service]
Type=simple
Environment=JAVA_HOME=/usr/lib/jvm/java-17-openjdk
ExecStart=/usr/lib/jvm/java-17-openjdk/bin/java -Xms256m -Xmx512m -cp /opt/demo-daemon DemoMain
SuccessExitStatus=143
Restart=on-failure

[Install]
WantedBy=multi-user.target

逐项说明:

配置项说明
Type=simplejava 进程本身不 fork,systemd 直接跟踪主进程
Environment=JAVA_HOME=/usr/lib/jvm/java-17-openjdk为 JVM 提供明确运行时,避免系统默认 JVM 漂移
ExecStart直接运行 java,堆 256 至 512MB
SuccessExitStatus=143JVM 收到 SIGTERM 走完关机钩子后以退出码 143 结束,不声明该值单元会进入 failed 状态(实测踩坑)
Restart=on-failure异常退出时自动拉起

启动并验证:

sudo systemctl daemon-reload
sudo systemctl start demo-plain.service
sleep 6
systemctl is-active demo-plain.service

回显(实测):

active
journalctl -u demo-plain.service --no-pager -n 3 | tail -3

回显(实测):

Oct 13 08:10:45 7a86e1bc5e94 systemd[1]: Started DemoMain Service (plain java + systemd).
Oct 13 08:10:45 7a86e1bc5e94 java[3026]: [DemoMain] main() started, args=[]
Oct 13 08:10:50 7a86e1bc5e94 java[3026]: [DemoMain] heartbeat #1

停止并确认状态干净(不带 SuccessExitStatus=143 时实测 systemctl is-failed 会返回 failed):

sudo systemctl stop demo-plain.service
sleep 2
echo is_active=$(systemctl is-active demo-plain.service)
echo is_failed=$(systemctl is-failed demo-plain.service)

回显(实测):

is_active=inactive
is_failed=inactive

八、多版本管理

按版本分目录放置 jsvc 二进制,再用软链接切换当前版本。以 1.5.1 为例(1.5.1 已从主分发站下架,需从 Apache 归档站获取):

cd /opt/commons-daemon
curl -fsSLO https://downloads.apache.org/commons/daemon/source/commons-daemon-1.5.1-src.tar.gz

回显(实测):

curl: (22) The requested URL returned error: 404 Not Found
curl -fsSLO https://archive.apache.org/dist/commons/daemon/source/commons-daemon-1.5.1-src.tar.gz
curl -fsSLO https://archive.apache.org/dist/commons/daemon/source/commons-daemon-1.5.1-src.tar.gz.sha512
sha512sum -c commons-daemon-1.5.1-src.tar.gz.sha512

回显(实测):

commons-daemon-1.5.1-src.tar.gz: OK
tar -xzf commons-daemon-1.5.1-src.tar.gz
cd commons-daemon-1.5.1-src/src/native/unix
sudo ./configure --with-java=/usr/lib/jvm/java-17-openjdk | tail -3
sudo make | tail -2
sudo mkdir -p /opt/commons-daemon/jsvc-1.5.1
sudo cp jsvc /opt/commons-daemon/jsvc-1.5.1/jsvc
ls -l /opt/commons-daemon/jsvc-1.5.1/

回显(实测):

config.status: creating native/Makefile
*** All done ***
Now you can issue "make"
gcc   jsvc-unix.o libservice.a -ldl -lpthread -o ../jsvc
make[1]: Leaving directory '/opt/commons-daemon/commons-daemon-1.5.1-src/src/native/unix/native'
total 196
-rwxr-xr-x 1 root root 198944 Oct 13 07:58 jsvc

验证 1.5.1 的版本指纹,再切换软链接并分别确认:

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

回显(实测):

commons daemon version "1.5.1"
sudo ln -sf /opt/commons-daemon/jsvc-1.5.1/jsvc /usr/local/bin/jsvc
/usr/local/bin/jsvc -version -outfile '&1' -errfile '&2' -java-home /usr/lib/jvm/java-17-openjdk -cp /opt/demo-daemon/commons-daemon-1.6.1.jar:/opt/demo-daemon DemoDaemon | grep 'commons daemon'
sudo ln -sf /opt/commons-daemon/jsvc-1.6.1/jsvc /usr/local/bin/jsvc
/usr/local/bin/jsvc -version -outfile '&1' -errfile '&2' -java-home /usr/lib/jvm/java-17-openjdk -cp /opt/demo-daemon/commons-daemon-1.6.1.jar:/opt/demo-daemon DemoDaemon | grep 'commons daemon'

回显(实测,依次为 1.5.1 与切回后的 1.6.1):

commons daemon version "1.5.1"
commons daemon version "1.6.1"

软链接指向哪个目录,/usr/local/bin/jsvc 就是哪个版本,升级与回滚都只需一条 ln -sf。

九、卸载

按依赖反向顺序清理:先停用并摘除 systemd 单元,再删除二进制与目录,最后删除降权用户。以下命令按本文方案 A 加方案 B 的完整安装路径编写,未启用对应单元时跳过相应条目即可:

sudo systemctl stop demo-daemon.service
sudo systemctl disable demo-daemon.service

stop 停止单元无回显,disable 摘除开机自启链接的回显(实测):

Removed /etc/systemd/system/multi-user.target.wants/demo-daemon.service.
sudo systemctl stop demo-plain.service
sudo rm -f /etc/systemd/system/demo-daemon.service /etc/systemd/system/demo-plain.service
sudo systemctl daemon-reload
systemctl is-active demo-daemon.service

回显(实测):

inactive
sudo rm -f /usr/local/bin/jsvc
sudo rm -rf /opt/commons-daemon /opt/demo-daemon
sudo userdel -r appuser
which jsvc

回显(实测):

which: no jsvc in (/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin)
ls /opt/commons-daemon

回显(实测):

ls: cannot access '/opt/commons-daemon': No such file or directory
id appuser

回显(实测):

id: 'appuser': no such user

若第三章写入过 /etc/profile.d/java17.sh,卸载时一并删除:sudo rm -f /etc/profile.d/java17.sh。

十、常见问题 FAQ

Q1:configure 阶段报 WARNING: cannot find headers for libcap,要紧吗?

不影响编译与常规运行,但会关闭 jsvc 的能力集(capabilities)支持。安装 libcap-devel 后重新 configure 即可启用:启用后 jsvc 降权将保留 CAP_NET_BIND_SERVICE、CAP_SETUID、CAP_SETGID、CAP_DAC_READ_SEARCH 等能力,使非 root 运行的服务仍能绑定 1024 以下特权端口。若服务需要绑定特权端口,务必装上它再编译。

Q2:安装了 java-17-openjdk-devel,java -version 还是 1.8?

CentOS 8 通过 alternatives 管理多版本 Java,安装新 JDK 不会自动切换默认项。需实测执行 alternatives --set java /usr/lib/jvm/java-17-openjdk-17.0.1.0.12-2.el8_5.x86_64/bin/java 与 alternatives --set javac /usr/lib/jvm/java-17-openjdk-17.0.1.0.12-2.el8_5.x86_64/bin/javac(路径以本机 /usr/lib/jvm/ 下实际目录为准),本文第三章有完整回显。

Q3:mvn -version 显示 Java 1.8,但 java 已经是 17?

Red Hat 的 Maven 启动脚本在未设置 JAVA_HOME 时按 /usr/share/java-utils/java-functions 中 _set_java_home() 的硬编码顺序优先取 /usr/lib/jvm/java-1.8.0-openjdk(实测查看脚本确认),与 alternatives 设置无关。写入 export JAVA_HOME=/usr/lib/jvm/java-17-openjdk 到 /etc/profile.d/java17.sh 并重新登录即可,实测修复后 mvn -version 显示 Java version: 17.0.1。同理,jsvc 的所有命令也应显式带 -java-home /usr/lib/jvm/java-17-openjdk,避免多 JDK 环境下加载错版本。

Q4:javac 报大量 error: unmappable character (0xE6) for encoding US-ASCII?

系统 locale 为 C 时 javac 默认以 US-ASCII 读取源码,源码里的中文注释全部无法解析。编译命令加 -encoding UTF-8 即可,本文所有 javac 示例均已携带该参数。

Q5:Maven 里应该用哪个坐标?

org.apache.commons:commons-daemon 在 Maven Central 上仅有 1.0.9(实测查询 metadata 确认),不要用它引 1.6.1。可用坐标是旧 groupId 的 commons-daemon:commons-daemon:1.6.1,实测 mvn dependency:get 可拉取,且其 jar 与官方 bin 包内 jar 的 sha512 完全一致(3217edbd2458dbf1 开头)。

Q6:直接执行 jsvc --help 报 JSVC re-exec requires execution with an absolute or relative path?

jsvc 启动时会 re-exec 自身,要求调用方给出路径;仅靠 PATH 解析出来的裸命令名不满足条件。统一用 /usr/local/bin/jsvc 这类绝对路径调用即可。

十一、参考链接

  1. Apache Commons Daemon 官方主页:https://commons.apache.org/proper/commons-daemon/
  2. 官方下载目录(bin 包与 src 包):https://downloads.apache.org/commons/daemon/
  3. jsvc 启动器文档(含参数说明):https://commons.apache.org/proper/commons-daemon/jsvc.html
  4. Apache 归档站(历史版本下载):https://archive.apache.org/dist/commons/daemon/
  5. Maven Central 上的 commons-daemon 构件:https://repo1.maven.org/maven2/commons-daemon/commons-daemon/
  6. systemd 单元配置官方手册:https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html