
Java在线运行环境搭建与使用教程
Java在线运行环境,通常指用户通过浏览器编写、编译和运行 Java 程序,不必在自己的电脑上安装完整开发工具。它可以是公共在线编译器,也可以是个人或团队自建的浏览器开发环境。两者看起来都能“在线写 Java”,但运行方式、权限和安全边界并不相同。
本文采用一种适合个人练习和小团队开发的方案:在 Docker 容器中运行 code-server 和 JDK,通过浏览器使用类 VS Code 界面,在终端中编译、运行和调试程序。这个方案搭建简单,适合远程开发;它不是面向陌生用户开放的公共代码执行平台。若要让任意用户提交代码并由服务器代为执行,需要额外的隔离、队列、审计和资源控制,不能只靠一个容器或网页编辑器解决。
一、适用场景与不适用场景
适合的场景
- 学习和练习:在浏览器中练习 Java 语法、数据结构、算法或命令行编译。
- 临时使用:在没有本地开发环境的设备上,通过浏览器访问远程工作区。
- 轻量开发:编写小型项目、修改配置、查看日志,或在远程服务器上维护代码。
- 团队内部工具:为可信任的成员提供隔离的个人开发容器,再配合账号认证和反向代理使用。
不适合的场景
- 直接运行公众提交的任意代码:用户代码可能访问文件、消耗资源或尝试逃逸容器。浏览器界面和容器本身都不是完整的多租户安全方案。
- 替代完整生产开发平台:大型项目可能需要数据库、消息队列、私有依赖仓库、CI/CD、权限管理和备份,这些能力需要另行配置。
- 承诺可运行所有 Java 程序:本例以 JDK 17 为基础。使用其他 Java 版本、系统库或特定硬件的程序,需按项目要求调整环境。
如果目标只是在线试几行代码,使用可信的公共在线编译器会更省事;如果要长期使用浏览器开发环境,自建 code-server 更合适。若目标是提供公开的在线判题或代码运行服务,应采用专门的执行架构,不能把本文方案直接暴露给公众。
二、运行原理与准备条件
本方案中,Docker 容器负责运行 code-server 和 JDK;浏览器通过 HTTP 连接到 code-server,编辑器终端则在容器内执行 javac 和 java。源代码保存在挂载到容器的工作目录中,因此容器重建后,工作区文件仍可保留。
开始前需要:
- 一台安装 Docker Engine 和 Docker Compose 插件的 Linux 主机,或支持 Docker 的其他环境。
- 能够访问该主机的现代浏览器。
- 基本的命令行操作能力。
下面的配置将服务只绑定到主机的本地回环地址 127.0.0.1。这样,默认情况下只能在主机本地访问。远程使用时,应通过 SSH 隧道,或配置经过 TLS 的反向代理和访问控制;不要为了省事把服务端口直接开放到公网。
三、创建 Java 在线开发环境
1. 创建目录和 Dockerfile
在主机上创建一个目录,用来保存容器配置和 Java 工作区:
mkdir -p java-online/project
cd java-online
在该目录下创建 Dockerfile:
FROM codercom/code-server:latest
USER root
RUN apt-get update \
&& apt-get install -y --no-install-recommends openjdk-17-jdk \
&& rm -rf /var/lib/apt/lists/*
USER coder
WORKDIR /home/coder/project
这个镜像以 code-server 为基础,再安装 OpenJDK 17。这里使用 latest 是为了让示例容易跟随上游镜像更新;用于长期运行时,建议选定经过验证的固定版本标签,并定期评估更新,避免镜像变化导致环境不可预期。镜像基础系统或软件源变化时,安装命令也可能需要调整。
2. 创建 Compose 配置
在同一目录创建 compose.yaml:
services:
java-online:
build: .
container_name: java-online
environment:
PASSWORD: "${CODE_SERVER_PASSWORD}"
command:
- code-server
- --bind-addr
- 0.0.0.0:8080
- --auth
- password
- /home/coder/project
ports:
- "127.0.0.1:8080:8080"
volumes:
- ./project:/home/coder/project
mem_limit: 2g
cpus: 2.0
pids_limit: 256
restart: unless-stopped
这里设置了几个需要理解的边界:
127.0.0.1:8080:8080将容器端口映射到主机回环地址,不是公网监听。若把左侧改为8080:8080,通常会让端口监听在主机的网络接口上,具体还受主机防火墙和 Docker 网络配置影响。./project:/home/coder/project将主机目录挂载为工作区。容器内对该目录的修改会写入主机文件系统。mem_limit、cpus和pids_limit是 Docker Compose 支持的资源约束配置。它们限制容器可用资源,但并不能保证容器内的程序安全,也不是对所有宿主机配置都具有相同效果的性能承诺。应在实际 Docker 环境中验证。
不要将 Docker Socket 挂载进这个容器,也不要给它不必要的特权或宿主机目录访问权限。若容器内的程序能控制 Docker 守护进程,风险可能扩展到宿主机。
3. 设置访问密码并启动
在 java-online 目录创建 .env 文件,填入一个足够强、未在其他服务重复使用的密码:
CODE_SERVER_PASSWORD=替换为你自己的长随机密码
避免将真实密码提交到 Git 仓库。可以把 .env 加入项目的 .gitignore。随后构建并启动服务:
docker compose up -d --build
docker compose ps
如果运行正常,可以在 Docker 主机本机打开 http://127.0.0.1:8080,输入配置的密码登录。初次使用时,code-server 可能需要从网络获取编辑器资源或扩展;是否能安装扩展取决于主机网络和服务端的访问策略。
查看启动日志:
docker compose logs --tail=100 java-online
4. 远程访问方式
对于临时的个人远程访问,可以从自己的电脑建立 SSH 本地端口转发:
ssh -L 8080:127.0.0.1:8080 用户名@服务器地址
保持 SSH 会话运行后,在本机浏览器访问 http://127.0.0.1:8080。SSH 隧道只负责转发连接,不会替代应用密码,也不应把私钥交给他人。
若要通过域名长期访问,应由管理员配置 HTTPS 反向代理、身份认证、访问日志和必要的网络限制,并正确设置代理转发相关配置。不要简单地把容器端口改成公网可访问,然后认为密码登录已经足以保护服务。密码泄露、弱口令、未修补的服务漏洞,以及工作区文件权限问题,都可能造成风险。
四、在浏览器中编写并运行 Java 程序
1. 检查 Java 工具链
登录 code-server 后,打开终端,运行:
java -version
javac -version
which java
which javac
前两条命令分别检查 Java 运行时和编译器版本。若输出中显示 17,说明当前镜像里的 JDK 17 已可用。只安装 JRE 的环境可以运行已经编译的程序,但没有 javac,无法从源文件编译;本例安装的是 JDK。
2. 编写一个最小程序
在工作区创建 Hello.java。Java 的公开类名必须与文件名一致,注意大小写:
public class Hello {
public static void main(String[] args) {
String name = args.length > 0 ? args[0] : "Java";
System.out.println("你好," + name + "!");
}
}
在终端中编译并运行:
cd /home/coder/project
javac Hello.java
java Hello
java Hello 世界
第二次运行时,命令行参数 世界 会传入 main 方法的 args 数组。终端预期输出:
你好,Java!
你好,世界!
javac Hello.java 默认会在当前目录生成 Hello.class;java Hello 的参数是类名,不是文件名,因此不能写成 java Hello.class。如果需要指定编译输出目录,可以使用 -d 参数:
mkdir -p out
javac -d out Hello.java
java -cp out Hello 世界
这里的 -cp 指定类路径。项目变复杂后,不建议长期手工管理编译命令,可以改用 Maven 或 Gradle,并在项目中声明所需的 Java 版本和依赖。
3. 读取标准输入
在线练习经常需要从标准输入读取数据。下面的程序读取一行文本并输出其长度:
import java.util.Scanner;
public class LineLength {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
if (scanner.hasNextLine()) {
String line = scanner.nextLine();
System.out.println(line.length());
}
}
}
编译和运行:
javac LineLength.java
java LineLength
运行后在终端输入一行文字并按回车;程序读到终端输入的行后输出长度。若要通过管道提供输入,可以使用:
printf 'hello\n' | java LineLength
标准输入和命令行参数不是一回事:java Hello 世界 把“世界”传给 args,而管道或终端输入则由 System.in 读取。
4. 使用 JShell 交互试验
JDK 9 及以上版本通常包含 JShell,可以直接执行表达式,适合快速检查语法或 API:
jshell
进入提示符后输入:
int n = 7;
n * n
输入 /exit 退出。JShell 适合短小试验,不适合替代有多个源文件、构建配置和测试的项目。它也不意味着所输入的代码被限制在安全沙箱内。
五、项目开发、文件保存与版本管理
工作区位于主机的 java-online/project 目录。关闭浏览器不会自动删除其中的文件;容器重建后,只要挂载目录仍在,项目文件一般仍会保留。重要文件仍应另做备份,容器卷并不等于备份。
可以在工作区中创建一个简单的包结构:
mkdir -p src/com/example
cat > src/com/example/Main.java <<'EOF'
package com.example;
public class Main {
public static void main(String[] args) {
System.out.println("运行成功");
}
}
EOF
javac -d out src/com/example/Main.java
java -cp out com.example.Main
类路径中使用的是包名对应的完全限定类名 com.example.Main,而不是源文件路径。实际项目可用 Maven 或 Gradle 管理构建。例如,使用 Maven 时通常通过 mvn test 执行测试、通过 mvn package 构建产物。容器中需要另外安装 Maven,或选择已包含所需构建工具的开发镜像;本文 Dockerfile 只安装 JDK,不包含 Maven、Gradle、数据库或其他服务。
建议把项目代码纳入版本控制,不要把密码、访问令牌、私钥或生产环境配置写进源码仓库。容器工作区对开发者开放,能够在容器内执行命令的人也可能读取工作区内可访问的文件。
六、参数限制与安全边界
资源限制不等于代码隔离
Compose 配置中的内存、CPU 和进程数上限,可以降低单个容器占用宿主机资源的风险,但不能阻止所有不受信任代码行为。程序仍可能读取其有权访问的文件、进行网络访问、产生大量输出,或利用系统和运行时漏洞。还需要结合最小权限、网络策略、宿主机更新和监控来降低风险。
Java 自身的 -Xmx 参数限制的是 Java 堆的最大内存,不等同于整个进程的内存上限。JVM 还可能使用元空间、线程栈、直接内存和本地库内存。即使设置了 -Xmx,仍要考虑容器资源限制和进程实际行为。例如:
java -Xms64m -Xmx256m Hello
这表示为 JVM 设置初始和最大堆参数,不代表整个容器最多只使用 256 MB。堆参数的有效范围还受 JVM 版本、容器识别能力及可用资源影响,应在目标环境中测试。
在线练习的资源上限取决于服务提供方
使用第三方在线编译器时,执行时长、内存、文件大小、依赖、网络、并发数和可用 JDK 版本,均由该平台的实际规则决定。不同平台的参数名称和限制可能不同,而且规则可能调整。使用前应查看平台说明,不要假定它支持任意命令行参数或无限运行时间。
本文自建方案没有配置“每次运行的超时”和“每位用户的配额”。容器的 CPU、内存和进程上限是长期运行服务的资源约束,不等于每个程序执行到某个时间会自动被终止。终端中的程序若运行不结束,可以按 Ctrl+C 中断;生产化的代码执行平台还需要由服务端可靠地管理任务超时和进程回收。
公开运行用户提交代码需要额外设计
如果要建设类似在线判题的服务,不能让网页用户直接登录共享开发容器运行代码。最低限度也要将编辑与执行分开,采用短生命周期、按任务创建的执行环境,限制 CPU、内存、进程、磁盘和运行时间,限制网络访问,清理临时文件,并处理并发、日志、队列和异常退出。还应评估容器之外的隔离技术、宿主机安全更新及滥用应对措施。具体方案取决于威胁模型和业务规模,不能把“使用 Docker”当作安全证明。
七、常见错误与排查
浏览器无法访问
- 在主机运行
docker compose ps和docker compose logs --tail=100 java-online,检查容器是否启动及是否有配置错误。 - 确认 Compose 端口映射和实际访问地址一致。本配置绑定在主机的
127.0.0.1,直接从另一台电脑访问服务器地址通常不会成功,这是预期行为。 - 远程访问时检查 SSH 隧道是否仍在运行;使用反向代理时检查代理目标、TLS 证书、认证策略和防火墙规则。
提示找不到 javac
运行 java -version 和 javac -version。如果 Java 可运行而 javac 不存在,可能安装的是 JRE 而非 JDK,也可能容器镜像没有成功构建。修改 Dockerfile 后使用 docker compose up -d --build 重新构建。
提示找不到主类
检查编译是否成功、当前目录和类路径是否正确。对于没有声明包的 Hello,在 class 文件所在目录运行 java Hello;对于声明了 package com.example; 的类,应从输出目录运行 java -cp out com.example.Main。
提示文件名与类名不一致
如果文件中声明了 public class Hello,文件名必须为 Hello.java。Java 标识符大小写敏感,Hello 和 hello 是不同名称。
中文显示异常
先确认源文件以 UTF-8 保存,并检查终端和浏览器的字符编码设置。若项目使用非默认编码,可以在编译时显式指定源文件编码,例如:
javac -encoding UTF-8 Hello.java
该选项控制编译器读取源文件的编码;它不会自动修复终端、字体或程序输出环境中的所有字符显示问题。
修改容器配置后没有生效
仅修改 Dockerfile 后需要重新构建镜像。仅修改 Compose 配置通常需要重新创建服务,执行:
docker compose up -d --build
排查期间可查看日志;确认不再需要服务后,可以停止容器:
docker compose down
该命令通常会移除由 Compose 创建的容器和网络,但不会因为本例中的目录挂载而删除主机上的 project 文件夹。执行清理操作前仍应核对实际配置和 Docker 资源,避免误删其他项目的数据。
八、如何选择下一步方案
个人学习、远程维护或可信团队内部使用,可以从本文的 code-server 加 JDK 方案开始:配置简单,工作目录清晰,也便于逐步增加 Maven、Gradle 和测试工具。长期使用时,应固定镜像版本、保护访问凭据、备份工作区并及时更新基础镜像。
若只想临时运行示例,公共在线编译器通常更方便,但要先确认其 JDK 版本、输入方式、依赖支持和资源限制。若需要多人同时使用,应该为用户分配独立工作区和权限,避免共享账号与共享敏感文件。若要开放任意代码执行,则需要专门设计执行服务,而不是把个人开发容器改成公网入口。
概括来说,在线 Java 环境的核心不是“能否在网页里点运行”,而是明确代码在哪里运行、数据保存在哪里、可访问哪些资源,以及谁有权限提交和执行代码。先根据使用对象和威胁边界选方案,再逐步增加工具与功能,通常比一开始搭建一个缺少隔离的公共运行器更可靠。




