IBM JDK 1.6安装配置与J9 VM参数调优实战指南
简介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 还是 JREtar -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-components1 chown -R appuser:appgrp /opt/ibm-java6 chmod 755 /opt/ibm-java6/bin/java第一行先创建目录第二行把压缩包解压进去。--strip-components1的作用是去掉压缩包里的第一层目录让 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别让脚本认错 Java3.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] EnvironmentJAVA_HOME/opt/ibm-java6 EnvironmentPATH/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 21全路径虽然长但可预期。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.envprod -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.envprod没有加-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 21 sleep 900 kill %1 $JAVA_HOME/bin/java -J-Xgcpolicy:optavgpause -J-verbose:gc -jar /app/pay/app.jar /tmp/gc-pause.log 21 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:eventsuncaught,file/logs/app/heapdump.dump \ -jar /app/pay/app.jareventsuncaught表示只在捕获到未处理异常时生成堆 dump比eventsall更克制不会因为容器平滑重启生成一堆无用 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:PermSize128m这类参数直接贴到 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:PermSize128m -XX:MaxPermSize256m这类永久代参数直接删掉。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.66.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 21 | tee -a $LOG exec $JAVA_BIN -J-Xms256m -J-Xmx1024m -jar /app/pay/app.jar $LOG 21这里把 java -version 的输出通过 tee 同时打到屏幕和日志exec 让 java 进程替换掉 shell 进程避免多留一个 shell 壳占用资源。以后排查时打开 startup.log 看前几行如果版本行不是 1.6.0 和 J9 VM立刻就知道环境被串了不用再从进程树里一层层翻。6.2 小步验证换 JDK 后先跑通一条最重的链路版本锁定了还不够老 JDK 的类库实现和现代 JDK 差异不小。我习惯换完 JDK 后先跑一次真实数据的查询链路再跑一次带定时任务的模块最后才放量。这套顺序来自一次血泪教训当时只验证了启动没验证定时任务结果凌晨批处理在 J9 的弱引用回收行为下把缓存撑爆第二天早上才被业务发现。从那以后我每次迁移或者复现老环境都强制走一遍完整动作先导版本日志再跑最小闭环最后盯一两轮 GC 日志。这三个步骤看起来简单省掉的往往是通宵排查的时间。希望帮到你。本文还有配套的精品资源点击获取