简介:IBM JDK 1.6 是 IBM 面向 Windows 平台提供的 Java 开发与运行环境,适合需要维护遗留企业应用、部署 WebLogic 等中间件或学习 Java SE 6 特性的开发者使用。压缩包约 2000 个文件,共 83.8MB,以 html 帮助文档、java 源码、jar 类库、properties 配置以及 dll/exe 运行组件为主,另含大量 gif/jpg 图表与 css/js 资源,便于完整了解 JDK 结构与工具链。已有 819 人学习下载。该版本在 JVM 性能、安全管理、脚本语言嵌入(JSR 223)及 NIO.2 文件系统方面均有增强,并针对多线程与大规模并发做了企业级优化;包内还包含 J9 虚拟机相关的本地方法实现与调试源码,对研究 JVM 底层实现、排查旧系统兼容性问题尤其有参考价值。读者可通过安装配置、环境变量设置与 IDE 集成示例,快速掌握 IBM JDK 1.6 的使用方式,并为后续迁移至 Java 8 以上版本提供知识储备。
1. 一份 IBM JDK 1.6 包:为什么新项目不用它,老项目又离不开它
接手某内部系统维护任务时,我翻遍归档盘才找到一份能用的 IBM JDK 1.6 安装包。这个版本对应的是已经跑了很多年的老中间件,官方早已停止对外更新,换了新 JDK 启动后,类加载顺序、内存回收行为和参数体系都变了,程序表现完全对不上。老项目还抱着 1.6 不放,不是恋旧,而是它的运行环境和第三方组件的兼容范围被钉死在了这个版本上。这份资源解决的就是这个问题:把 IBM JDK 1.6 完整拆出来、装到目标服务器、让生产脚本稳定引用它,同时把最容易翻车的参数坑提前排掉。适合正在维护遗留中间件、或者需要复现旧环境问题的人看,写新项目选型 JDK 的可以略过。
2. 拆包与安装:先把 IBM JDK 1.6 的目录结构搞对
2.1 分清 SDK 和 JRE:包里其实藏着两套 Java
拿到包不要急着解压。IBM JDK 1.6 存在两种形态:一种是只带运行时的 JRE,体积小,适合丢给应用单独部署;一种是完整 SDK,在 bin 目录下同时有 java 和 javac,适合做编译、调试和分析 dump。更特殊的地方在于,SDK 内嵌了一套完整 JRE 的目录结构,打开后你会同时看到顶层 lib 和顶层 jre 目录,第一眼看着像装了两份 JDK。
我一般这样理解它的布局:顶层 bin 是 SDK 对外暴露的命令行入口,java、javac、jdb 都从这走;顶层 jre 是运行时要用的私有环境,里面有自己的 bin 和 lib,启动 java 进程时实际加载的核心类库与本地库大部分来自这里。bin 和 jre 是一个整体,单独把 bin/java 拷走,后面大概率起不来。另外,压缩包里如果还带 license 目录和 dump 工具目录,说明发行方把运维辅助脚本一起打进去了,这种包排查问题时往往比裸 SDK 好用。
解压前先看一眼内容,确认包里到底是 SDK 还是 JRE:
tar -tzf ibmjdk1.6-pkg.tar.gz | head -20 tar -tzf ibmjdk1.6-pkg.tar.gz | grep -E "bin/javac|jre/bin/java"第一行看整体结构,第二行直接确认有没有 javac。看到bin/javac说明是 SDK;如果只有bin/java和jre/bin/java,那这份是 JRE。判断清楚再决定往哪放,否则后面写编译脚本时发现 javac 不存在,还要回头补包,时间就浪费了。
2.2 安装路径:放到 /opt/ibm-java6 并保持目录完整性
IBM JDK 6 对安装路径不敏感,不像有些商业软件要求固定目录;但它对目录完整性很敏感。bin 下的启动器会按相对路径去加载 lib 和 jre,路径一旦拆散,程序就会报找不到共享库。我习惯统一放到/opt/ibm-java6,一台机器只留一个版本,避免多版本 JDK 的 JVM 参数体系互相干扰。如果你手头已经有别的 JDK 在跑,至少保证这个路径不和其他版本重叠。
安装操作按这个顺序来:
mkdir -p /opt/ibm-java6 tar -xzf ibmjdk1.6-pkg.tar.gz -C /opt/ibm-java6 --strip-components=1 chown -R appuser:appgrp /opt/ibm-java6 chmod 755 /opt/ibm-java6/bin/java第一行先创建目录,第二行把压缩包解压进去。--strip-components=1的作用是去掉压缩包里的第一层目录,让 bin 直接出现在/opt/ibm-java6/bin,后面配置 JAVA_HOME 时不用再拼接一层未知子目录。第三行把属主改成运行应用的业务账号,避免 java 进程跑在 root 权限上。第四行给启动器最小可执行权限,不要随手chmod -R 777,曾有同事把整个目录放开,结果别的业务账号也能写 bin,安全隐患不小。
权限这个坑值得多说一句:如果只 chmod 了 bin/java 而忽略整个目录的读权限,业务账号执行时会报 Permission denied,但 root 却一切正常。遇到这种情况优先查/opt/ibm-java6路径下每一级目录的权限,别一上来就怀疑包坏了。
2.3 第一次启动验证:看 java -version 的三行输出
装完先别急着配环境变量,直接跑一次绝对路径的 java -version,把基线定下来:
/opt/ibm-java6/bin/java -version正常输出会是三行式结构:第一行是 Java 版本,类似java version "1.6.0";第二行是 J9 VM 的版本标识;第三行会带一个 JCL 或构建级别信息。看到 J9 VM 字样,就说明这是 IBM 的虚拟机,而不是 Oracle 系的 HotSpot。确认这一行很重要,因为后面所有参数行为都以 VM 类型为前提,把它写进运维交接文档里,后面接手的人会少走很多弯路。
如果输出的是其他版本,比如java version "1.7.0",说明手头这份包可能不是 1.6,或者 PATH 里已经被别的 java 抢先了。这时候一定要用绝对路径重试,不要信路径里那个 java 给出的结果。版本确认无误后,就可以进入环境变量配置环节。
3. 配置 JAVA_HOME 和 PATH:别让脚本认错 Java
3.1 JAVA_HOME 要写进三个位置
Java 程序报 JAVA_HOME 找不到,多数不是变量名拼错,而是只改了一个位置。在 Linux 上我一般检查三个地方:当前 shell 的~/.bashrc、应用启动脚本的开头、以及 systemd 服务文件里的 Environment 行。其中~/.bashrc只影响交互式登录,cron 和 systemd 不一定读它,所以决定应用行为的其实是启动脚本和 service 文件。
推荐做法是把 JDK 路径放在一个独立的 profile 片段里统一管理:
cat > /etc/profile.d/ibm-java6.sh <<'EOF' export JAVA_HOME=/opt/ibm-java6 export PATH="$JAVA_HOME/bin:$PATH" EOF source /etc/profile.d/ibm-java6.sh放在/etc/profile.d下,所有登录用户都能自动拿到这套环境,比逐个改~/.bashrc省事。注意这里没有把/opt/ibm-java6/jre/bin加进 PATH,因为顶层 bin 已经能正确启动运行时,再加上 jre/bin 反而可能出现两个同名 java。heredoc 的'EOF'加了引号,是为了防止 shell 把变量展开,写进去的必须是字面量内容。
如果应用由 systemd 托管,还需要在 service 文件里把环境变量再声明一次:
[Service] Environment=JAVA_HOME=/opt/ibm-java6 Environment=PATH=/opt/ibm-java6/bin:/usr/bin:/bin ExecStart=/opt/ibm-java6/bin/java -jar /app/pay/app.jarsystemd 的 Environment 行不需要引号,但 PATH 里必须保留系统目录,否则 java 运行过程中调用的系统命令可能找不到。写完 service 文件要执行systemctl daemon-reload再重启,否则旧的变量缓存还在。
3.2 用绝对路径调用,防止 PATH 混入其他 JDK
很多翻车案例发生在同一台服务器同时有 JDK 1.6 和 JDK 1.8 的时候。某个脚本里写个裸java,实际执行的是 1.8,程序启动报类版本错误,或者 GC 日志特性完全不一样,排查半天才发现执行路径不对。我给自己定的规矩是:生产脚本一律写$JAVA_HOME/bin/java或直接/opt/ibm-java6/bin/java,绝不写裸 java。
排查 PATH 干扰,用这两条命令快速定位:
command -v java readlink -f "$(command -v java)"command -v java显示当前 shell 会执行的 java 路径,readlink 把软链接解析到真实文件。如果解析出来的路径不是/opt/ibm-java6/bin/java,说明 PATH 里其他 JDK 排在了前面,需要调整/etc/profile.d/ibm-java6.sh里的顺序,或者在启动脚本里强制 export 覆盖。
cron 场景比较隐蔽,因为 cron 环境不带登录 shell 的 PATH。在 crontab 里写 java 命令,要么执行前 source 一下 profile,要么直接在命令里写全路径:
*/30 * * * * /opt/ibm-java6/bin/java -jar /opt/cleanup/app.jar > /tmp/cleanup.log 2>&1全路径虽然长,但可预期。
3.3 参数前缀 -J-:IBM J9 家族最容易踩的参数机制
IBM JDK 1.6 的参数体系和 Sun 系差异明显。很多人在启动命令里照搬-Xmx1024m,结果进程起来了,堆却没变大,问题就出在 J9 启动器的参数拆分规则上。J9 启动器把参数分成两类:应用参数直接传给主类的 main 方法,虚拟机参数必须通过-J-前缀传递,例如-J-Xmx1024m、-J-Xms256m。这看起来多一道手续,但这是 J9 早期版本为了不破坏命令行兼容性而保留的规则,生产环境必须遵守。
一个标准的启动脚本可以这么写:
export JAVA_HOME=/opt/ibm-java6 export PATH="$JAVA_HOME/bin:$PATH" JVM_ARGS="-J-Xms256m -J-Xmx1024m -J-Xgcpolicy:gencon" APP_ARGS="-Dapp.env=prod -Dapp.conf=/etc/pay/app.properties" "$JAVA_HOME/bin/java" $JVM_ARGS $APP_ARGS -jar /app/pay/app.jar这里的-J-Xms256m是传给虚拟机的堆初始大小,-J-Xgcpolicy:gencon指定分代 GC 策略;-Dapp.env=prod没有加-J-,因为它作为系统属性传给应用。如果漏掉两个-J-,-Xmx1024m会被 main 方法当成一个普通字符串接住,程序不一定会报错,但 JVM 看到的堆上限仍然是默认值,结果就是系统越跑越慢、内存迟迟不释放,而且很难联想到是参数前缀写少了。
这里有个简单判断法:凡是以-X或-verbose开头的 JVM 选项,IBM JDK 1.6 上基本都要配-J-;-D属性和普通参数不用。等换到 IBM JDK 8 之后,部分-X参数可以不带前缀直接识别,但老版本 1.6 上别抱侥幸。
4. 给 J9 调内存与 GC:老 JDK 的参数体系跟 Sun 的不是一套
4.1 堆参数:-Xms、-Xmx、-Xmn 的边界设置
IBM JDK 1.6 上堆参数的名字和 Sun 系很像,但语义有差别。-Xms是初始堆,-Xmx是最大堆,-Xmn是年轻代大小。区别在于 J9 的-Xms写多小,JVM 不一定会真正把全部内存拿住,它只是把堆空间映射好,物理内存按需分配,所以进程的 RSS 看起来比 Xms 小,这属于正常现象。
给一套常见生产参数做注释:
JVM_ARGS="-J-Xms256m -J-Xmx1024m -J-Xmn192m -J-Xmso256k"-Xmx1024m是硬上限,堆增长到这里停止分配新对象;-Xmn192m指定年轻代,老年代就是 1024 减 192。在大量短生命周期对象的业务里,年轻代不能给太小,否则对象频繁晋升到老年代,触发压缩停顿;但给太大,老年代空间被挤占,长驻对象的分配又会变得局促。-Xmso256k是线程栈大小,等价于 Sun 系的 Xss,线程数很多的服务如果栈空间给太大,会出现OutOfMemoryError: unable to create native thread,调小这个值通常能缓解。
内存参数不能拍脑袋,我一般按物理内存算一个初值:
MEM_KB=$(awk '/MemTotal/ {print $2}' /proc/meminfo) XMX_MB=$((MEM_KB / 1024 * 3 / 4)) XMS_MB=$((XMX_MB / 2)) echo "-J-Xms${XMS_MB}m -J-Xmx${XMX_MB}m"这段脚本从/proc/meminfo读物理内存,最大堆取物理内存的四分之三,初始堆取最大堆的一半。这个比例适合大多数应用服务,如果机器上还跑着数据库,把分母改成 2,给操作系统和数据库留出余量。需要注意 awk 里的乘除顺序,先乘以 3 再除以 4,避免整除误差算出负数。
4.2 GC 策略:gencon、optthruput、optavgpause 怎么选
J9 在 1.6 时代的主打策略是-Xgcpolicy下面的几个值。很多人习惯按 HotSpot 那一套调新生代比例和晋升阈值,但在 J9 上调参入口完全不同,选错策略会直接表现为停顿时间异常或吞吐下降。
| 策略 | 适用场景 | 特点 |
|---|---|---|
| gencon | 大部分 Web 应用 | 默认值,分代并发收集,均衡吞吐和停顿 |
| optthruput | 批处理、后台计算 | 不追求低暂停,吞吐优先,总耗时最低 |
| optavgpause | 交互型应用、交易系统 | 牺牲少量吞吐换取更短暂停时间 |
生产环境我一般先用 gencon 跑基线,如果监控里看到 Full GC 频繁且单次暂停超过几百毫秒,才切到 optavgpause。切换方式是在启动参数上加-J-Xgcpolicy:optavgpause,不需要改代码。这里有个容易混淆的点:某些发行版还支持 metronome 实时 GC 策略,但它属于特殊场景,普通包写了这个值会直接报不认识,不要照抄。
GC 策略最好做一次 A/B 对比再定:
"$JAVA_HOME/bin/java" -J-Xgcpolicy:gencon -J-verbose:gc -jar /app/pay/app.jar > /tmp/gc-gencon.log 2>&1 & sleep 900 kill %1 "$JAVA_HOME/bin/java" -J-Xgcpolicy:optavgpause -J-verbose:gc -jar /app/pay/app.jar > /tmp/gc-pause.log 2>&1 & sleep 900 kill %1对比两个日志里的 GC 总次数、每次暂停耗时和应用响应时间,数字会告诉你答案。跑对比时,两次测试的堆参数必须完全一致,只改变 GC 策略,否则结论会被内存参数差异污染,这个坑我在早期踩过,后来养成了先固定-Xms/-Xmx再改策略的习惯。
4.3 现场不好复现时:-verbose:gc 和 -Xdump 一起开
老环境最怕病态问题不好复现,所以诊断开关要提前开着,而不是等 OOM 之后再手动挂。J9 的-verbose:gc输出格式跟 Sun 系很不一样,它会把并发周期、晋升、压缩等阶段都打出来,日志量不小,但这是定位 GC 瓶颈的第一手材料。-Xdump则是生成 Java 堆快照和系统 dump 的主要入口。
一个比较克制的临时诊断启动方式:
"$JAVA_HOME/bin/java" \ -J-Xms512m -J-Xmx512m \ -J-verbose:gc \ -J-Xdump:heap:events=uncaught,file=/logs/app/heapdump.dump \ -jar /app/pay/app.jarevents=uncaught表示只在捕获到未处理异常时生成堆 dump,比events=all更克制,不会因为容器平滑重启生成一堆无用 dump。file=指定 dump 落盘路径,要求日志目录有足够空间。GC 日志不用长期保留,诊断结束后去掉-J-verbose:gc,否则每天几百 MB 的日志几周就能把磁盘写满。
如果这份包是 JRE 形态,jmap等工具可能不存在,那就只能靠-Xdump兜底,所以部署时能选 SDK 尽量选 SDK,省得事后想起来要装工具还得换包。
5. 避坑:IBM JDK 1.6 上最常见的 5 个现场故障
5.1 参数与版本类问题
故障一:照搬 -XX 参数,启动直接报 Unrecognized VM option
现象:把别的 JDK 上常用的-XX:+UseConcMarkSweepGC、-XX:PermSize=128m这类参数直接贴到 IBM JDK 1.6 启动脚本,java 进程起不来,控制台打印Unrecognized VM option '-XX:+UseConcMarkSweepGC',后面跟一行错误提示。
原因:J9 VM 使用的参数体系是-X系,不识别-XX开头的选项,二者语义也不完全对应。这不是 IBM JDK 1.6 独有的问题,但从别的 JDK 迁移过来最容易踩。
解决:把-XX:+UseConcMarkSweepGC换成-J-Xgcpolicy:optavgpause,把-XX:PermSize=128m -XX:MaxPermSize=256m这类永久代参数直接删掉。J9 的永久代模型与 HotSpot 不同,默认管理方式通常够用。建议在启动脚本注释里保留一张参数对照表,每次迁移先替换再启动。
故障二:UnsupportedClassVersionError 51.0
现象:应用在某台机器上跑得好好的,换到装了 IBM JDK 1.6 的新环境,启动时报UnsupportedClassVersionError: Unsupported major.minor version 51.0,后面跟着一个完整类名。
原因:那个类是用 JDK 7 或更高版本编译的,class 文件主版本号是 51,而 Java 6 只认识主版本号 50 及以下的文件。多数情况是编译机默认 JDK 太新,没有按 source 和 target 对齐 1.6。
解决:编译时加-source 1.6 -target 1.6,并确认第三方依赖 jar 也是 Java 6 兼容版本。依赖里只要有一个类是用 JDK 8 编译的,整个 jar 都可能无法被老 JVM 加载。用zipinfo -l xxx.jar | grep class逐包排查不如直接统一编译环境更彻底;如果某个依赖确实无法替换,那就只能升级 JDK,不要在 1.6 上硬撑。
故障三:-Xmx 写了但没生效,堆一直停在默认值
现象:启动脚本里明明写了-Xmx1024m,监控工具显示 java 进程的堆长期停在默认值附近,GC 频繁,但进程不退出,也没有明显报错。
原因:参数前缀丢掉了。在 IBM JDK 1.6 上,-Xmx1024m被启动器当成应用参数传给 main 方法,JVM 根本没读到。启动器日志通常也不报参数错误,肉眼很难看出问题。
解决:把堆参数改成-J-Xmx1024m形式。排查时直接看启动脚本里的启动命令行,凡是想传给 JVM 的-X参数都必须带-J-。我后来养成的习惯是看脚本先数一遍-J-,数量明显不足时优先怀疑这里。
5.2 运行时与类路径问题
故障四:只拷走 bin/java,报找不到 libj9vm.so
现象:把 IBM JDK 1.6 里的bin/java单独复制到应用工具的目录,执行时提示error while loading shared libraries: libj9vm.so: cannot open shared object file。
原因:bin/java 只是启动器,启动时会按相对路径去找lib和jre/lib下的本地库。单独拷一个文件,破坏了包内部的目录结构,J9 的本地库libj9vm.so自然找不到。这不是环境变量没配好,而是包不完整。
解决:把整个包完整搬到目标路径,别只搬个别文件。如果确实想把工具集中管理,用软链接指向完整包里的 bin,而不是复制文件本身。另外,某些老包还依赖LD_LIBRARY_PATH包含 jre/bin 路径,有条件时在启动脚本里显式写清楚,别依赖全局配置。
故障五:64 位包放到缺库的精简镜像,加载失败
现象:同一个包在某些云主机或精简容器里运行时,报error while loading shared libraries: libpthread.so.0: cannot open shared object file,或者更直接的Exec format error。
原因:包本身是 64 位还是 32 位,必须和操作系统的运行库位数匹配。精简镜像缺了对应位数的底层库,J9 VM 无法加载本地库,报错信息偏底层,指向的往往不是 Java 本身的问题。
解决:先file /opt/ibm-java6/bin/java确认 ELF 类型,再ldd /opt/ibm-java6/bin/java看哪些共享库缺失。如果镜像没有 32 位库支持,就换对应位数的 IBM JDK 包。遇到这种情况别在容器里硬补库,直接换匹配的包最快。
6. 锁定运行时:让每次启动都确认真的在用 IBM JDK 1.6
6.1 把 java -version 写进启动日志
平时靠command -v java能查个大概,但进程一旦由 systemd 或调度平台拉起,很难保证 shell 环境里的 PATH 不会把你带偏。一个很实用的做法,是把版本信息固化到应用启动日志的第一行,让启动脚本自己作出证明。
#!/bin/bash JAVA_BIN=/opt/ibm-java6/bin/java LOG=/logs/pay-app/startup.log echo "=== startup at $(date '+%F %T') ===" >> "$LOG" "$JAVA_BIN" -version 2>&1 | tee -a "$LOG" exec "$JAVA_BIN" -J-Xms256m -J-Xmx1024m -jar /app/pay/app.jar >> "$LOG" 2>&1这里把 java -version 的输出通过 tee 同时打到屏幕和日志,exec 让 java 进程替换掉 shell 进程,避免多留一个 shell 壳占用资源。以后排查时打开 startup.log 看前几行,如果版本行不是 1.6.0 和 J9 VM,立刻就知道环境被串了,不用再从进程树里一层层翻。
6.2 小步验证:换 JDK 后先跑通一条最重的链路
版本锁定了还不够,老 JDK 的类库实现和现代 JDK 差异不小。我习惯换完 JDK 后先跑一次真实数据的查询链路,再跑一次带定时任务的模块,最后才放量。这套顺序来自一次血泪教训:当时只验证了启动,没验证定时任务,结果凌晨批处理在 J9 的弱引用回收行为下把缓存撑爆,第二天早上才被业务发现。
从那以后,我每次迁移或者复现老环境,都强制走一遍完整动作:先导版本日志,再跑最小闭环,最后盯一两轮 GC 日志。这三个步骤看起来简单,省掉的往往是通宵排查的时间。希望帮到你。
本文还有配套的精品资源,点击获取