简介JDK 1.7.0_13 是 Oracle 发布的 Java 7 系列开发工具包面向需要编译、运行、调试 Java 程序的开发者尤其适合维护历史遗留项目或验证 Java 7 兼容性的场景。该 RAR 压缩包约 79.07MB含 1240 个文件包括 jar 类库、xml 配置、dll 动态库、exe 可执行程序以及 properties 资源文件同时覆盖 JRE、JVM 和完整时区映射数据解压即可形成独立的 Java 开发运行环境。核心组件 javac 负责将源代码编译为字节码JVM 则保证跨平台执行内置 javadoc、jdb、jconsole、jmap 等工具方便生成 API 文档、排查调试问题、监控性能与内存。这些文件组织规整具备 lib、bin、db 等典型目录结构能满足本地开发、服务器部署以及老项目回归验证的需求。目前已有 373 人学习下载对需要快速获得 Java 7 环境或避免繁琐官网配置的开发者而言是一份实用的离线资源包。1. 还在用 jdk1.7.0_13 守着旧业务先搞清这个版本值不值得留生产环境里还跑着 jdk1.7.0_13 的团队通常不是不想升级而是被中间件版本、历史数据和一堆只在新版本里验证过的依赖锁死了。这个标题看起来只是一个早该淘汰的 JDK 小版本实际它卡住过无数迁移计划证书握不上、编译产物跑不了、GC 日志打不出来每一个坑都够运维和开发来回折腾半天。jdk1.7.0_13 不是 JDK 7 的最终更新却是不少老项目的“出厂设置”很多老系统从部署那天起就没换过 JDK。这篇文章我不打算劝你立刻升级而是把怎么识别、怎么安装配置、怎么排障讲透让仍守着这套环境的人能少踩点坑。2. 从 JDK 7 更新线看 jdk1.7.0_13它处在什么节点、为什么还活着2.1 1.7.0_13 在 JDK 7 更新坐标里的真实位置先看清楚版本名字本身。jdk1.7.0_13里的1.7.0是 Java 的语义化大版本号_13是 Update 13 的简写也就是 Java SE 7 的第 13 次更新构建。JDK 7 从 2011 年发布 1.7.0 之后Oracle 按季度发布安全更新每次更新号递增1.7.0_13 对应的是 2013 年初那轮更新属于 JDK 7 前中期的修修补补版本。它不是最终更新后面还有 1.7.0_15、1.7.0_21、1.7.0_25直到 1.7.0_80 收尾。为什么要把这个版本位置说得这么细因为“jdk1.7”这个词在老运维嘴里是个统称可每个 update 版本的 JVM 行为、证书库、TLS 默认支持都不一样。1.7.0_13 比 1.7.0 初版稳但远没有 1.7.0_80 成熟。它的 HotSpot 虚拟机还在较早期的 23.x 系列PermGen 默认值、字符串常量池迁移这些特性和后来的版本都有差异。很多从老服务器上搬运维脚本的人拿到的 JDK 其实就是 1.7.0_13却按后期 JDK 7 的参数写启动脚本自然容易出问题。安装目录里其实有非常明确的身份标识不用靠猜。解压版 JDK 的根目录下有一个release文件打开后第一行就写着JAVA_VERSION1.7.0_13。我排查问题时第一件事就是看这个文件而不是信系统里JAVA_HOME指向的目录名——太多机器上目录叫jdk1.7实际塞进去的是一个别的版本。如果你手里有多个 JDK 安装包也建议用这个文件确认版本再决定要不要纳入统一管理。2.2 被长周期项目锁住的现实场景为什么今天还有人守着它我在一线见过三种典型的“被迫留守”场景。第一种是老中间件厂商只验证到 JDK 1.7.0_13。比如一些基于老版本 WebLogic 或自研框架的企业应用厂商交付时绑定的是 1.7.0_13升级 JDK 后中间件管理控制台的某些插件直接起不来于是项目一跑就是六七年。第二种是旧项目用到了只在 JDK 7 里正常工作的第三方库比如老版本 JVM 参数解析库、字节码增强组件升级到 JDK 8 后反射权限和行为全变了没人敢动。第三种也是最常见的一种是历史镜像里装的就是 jdk1.7.0_13后续维护的人没权限也没动力改。这不只是“旧系统该淘汰”的问题因为它直接决定了部署和排障的边界。你要知道哪些进程还在依赖它。用一条命令就能粗略摸底ps -ef | grep java | grep -v grep | awk {for(i1;iNF;i) if($i ~ /^\/.*jdk/) print $i}这条命令会把/opt/jdk/jdk1.7.0_13/bin/java这类进程路径打出来能快确认当前机器上到底哪些 JVM 是 1.7.0_13。很多线上问题就是因为一台机器混装了多个 JDK启动脚本写死了旧路径而运维只在java -version里看到了新版本结果两头对不上。顺着进程路径再去看那个 JDK 目录的release文件确认实际版本比单纯看环境变量靠谱得多。2.3 官方维护停止之后1.7.0_13 的真实生存状态JDK 7 的公开更新早就结束了1.7.0_13 更是比 1.7.0_80 早了两年的老构建这意味着 Oracle 不会再为它发布公开漏洞补丁。很多外网威胁模型下这个版本确实是不够看的特别是遇到老的 TLS 协议被中间人截获、旧那套加密算法被证明可破解等问题时1.7.0_13 几乎是裸奔状态。但内网系统的情况通常要辩证看。不少企业核心系统的应用服务器部署在隔离网段入口有严格的访问控制出了漏洞也没那么容易被打穿。这时候守着 1.7.0_13 不完全是错的而是要认清楚它是“有边界的老技术”并且把升级债记为明确的技术债。我见过太多项目一边用 1.7.0_13 跑得好好的一边又用 JDK 8 的新语法写新模块最后编译产物混在一起启动时直接抛UnsupportedClassVersionError。与其这样不如要么统一留在 1.7.0_13 的兼容线路上把所有源码编译参数固定住要么痛下决心切到 7u80 或升级 JDK 8把老模块一起验证。最怕的就是中间状态维护成本翻倍还到处是坑。3. 本地复刻一个 jdk1.7.0_13 环境安装、配环境变量和编译验证3.1 解压安装与目录规划先把 JDK 目录放进 /opt现在很少能像当年那样从官网下到 1.7.0_13 的安装包了但企业内部镜像站、离线安装手册里大概率还留着这个版本的压缩包或安装程序。我一般建议用解压版不要用 rpm 或 exe 安装器。理由很简单解压版部署路径完全可控卸载时直接删目录即可不会往/usr/lib/jvm写一堆无用的符号链接也方便一台机器多个 JDK 并存。假设你手头有一个jdk-7u13-linux-x64.tar.gz目录规划建议是这样的mkdir -p /opt/jdk tar -zxf jdk-7u13-linux-x64.tar.gz -C /opt/jdk cd /opt/jdk ls -la解压完目录名通常就是标准的jdk1.7.0_13里面包含bin、jre、lib、include、release这些目录。如果你的解压目录名被人为改过或者嵌套了一层最好立刻改回标准名别在路径上省事。后面所有启动脚本、监控脚本都要引用这个目录名字越规范越不容易出错。这里有个容易被忽视的点/opt/jdk/jdk1.7.0_13/jre和/opt/jdk/jdk1.7.0_13/bin是两套可执行环境。直接跑java用的是 JDK 根目录下bin/java但这个二进制会优先找同名目录下的jre目录作为运行时。如果你把jre目录删了或者换成不匹配的版本java -version也能跑但 JVM 行为可能变得很奇怪。所以我部署完一定会检查目录完整性jdk1.7.0_13下必须有bin、jre/lib/amd64/server/libjvm.so和lib/tools.jar这几个关键文件。前两个缺了 JVM 起不来缺了tools.jar会导致javac、jmap、jstack这些工具全部罢工。3.2 配置 JAVA_HOME 和 PATH多 JDK 并存时的正确姿势一台机器如果只给一个老项目用直接改全局环境变量就行。我更推荐在/etc/profile.d/java.sh里加上一段而不是改/etc/profile主体这样后续删掉一个文件就能回滚不必翻大文件。配置内容是这样的export JAVA_HOME/opt/jdk/jdk1.7.0_13 export PATH$JAVA_HOME/bin:$PATH改完以后执行source /etc/profile.d/java.sh然后用java -version确认。输出大致是这样的java version 1.7.0_13 Java(TM) SE Runtime Environment (build 1.7.0_13-...) Java HotSpot(TM) 64-Bit Server VM (build 23.6-b01, mixed mode)注意第一行的1.7.0_13必须是它后面的HotSpot构建号其实不用记因为不同补丁打包商的字符串不完全一样。真正要确认的是第二行里没有出现OpenJDK之类的字眼——如果你从 Linux 发行版源里装了 OpenJDK版本字符串会变成openjdk version 1.7.0_13之类除了名字不同根目录结构和证书库位置都有差异。排查问题时别把 Oracle JDK 和 OpenJDK 混在一起用。如果机器上还有其他 JDK比如系统自带 OpenJDK 或者后装的 JDK 8我的习惯是不要改系统全局变量而是每个项目一个setenv.sh在启动脚本里显式引用export JAVA_HOME/opt/jdk/jdk1.7.0_13 export PATH$JAVA_HOME/bin:$PATH export CATALINA_HOME/data/web/apache-tomcat-7.0.57这样的话同一个服务器上可以同时跑多个 Tomcat一个用 JDK 7一个用 JDK 8互不干扰。很多翻车场景都是因为一个 Java 进程的环境变量被另一个启动脚本传染了这个原则能直接把问题按下去。3.3 用 javac 做最小编译验证class 文件版本 51.0 与 source/target环境配好以后最怕的是你写的源码用高版本语法最后丢给老 JVM 跑。验证方法不是只用java -version而是亲手编译一个类看产物格式。我每次搭完环境都会做一次这个动作cat /tmp/HelloJava7.java EOF public class HelloJava7 { public static void main(String[] args) { System.out.println(System.getProperty(java.version)); } } EOF /opt/jdk/jdk1.7.0_13/bin/javac /tmp/HelloJava7.java /opt/jdk/jdk1.7.0_13/bin/javap -verbose /tmp/HelloJava7.class | grep major version如果输出是major version 51说明这份 class 文件是 JDK 7 及以下版本编译的能被 1.7.0_13 正常运行。如果出现major version 52或者更高那就是 JDK 8 以上编出来的1.7.0_13 跑起来必定报UnsupportedClassVersionError。这个 51 的数字是 JDK 7 的类文件版本号JDK 8 是 52JDK 6 是 50记住这个映射关系就能一眼看出问题后面排障会很有用。源码编译参数也要固定。用 javac 时务必把-source和-target都写成1.7避免使用 JDK 7 里已经放弃的语法。比如/opt/jdk/jdk1.7.0_13/bin/javac -source 1.7 -target 1.7 -encoding UTF-8 /tmp/HelloJava7.java-encoding UTF-8是我强烈建议加上的尤其是从 Windows 上传源码到 Linux 编译的场景。如果不加javac 会按系统默认字符集读源码一旦代码里有中文注释或中文字符串常量编译完运行时就会出现经典的乱码问题这比编译报错还难排查因为它编译能通过只是内存里的字节已经乱了。4. jdk1.7.0_13 的五类高频排障现场现象、原因和解决办法4.1 部署包报 UnsupportedClassVersionError调用链里混入了高版本 class现象应用启动时抛异常堆栈第一条通常是一堆包名加UnsupportedClassVersionError中间夹着Unsupported major.minor version 52.0然后进程直接退出。原因运行时是 1.7.0_13最多只能加载 class 文件 major 版本 51某个 jar 或 classes 目录里混进了用 JDK 8 编译的 class。最常见的是用了高版本的第三方库或者团队里某个人用新的 Maven 配置编了一小部分模块直接打进同一个 war 包。老项目遇到这个错别急着追库版本先确认罪魁祸首是谁。解决先在临时目录解压出有问题的 jar扫一遍全部 class 的 major versionmkdir /tmp/scanjar cd /tmp/scanjar jar xf /data/app/libs/foo.jar find /tmp/scanjar -name *.class -exec javap -verbose {} \; | grep Classfile\|major version看到某个Classfile后面跟的 class 路径再对比下一行的major version就能定位到是哪个类是用 JDK 8 编的。快一点的替代做法是直接用zipgrep扫但那样只能发现带版本信息的文件头遇到二次打包的 jar 还不够稳。定位到以后要么让对应模块用-source 1.7 -target 1.7重新编译要么把这个库整体替换成兼容 JDK 7 的版本。如果是某类反编译工具造成的大量 class 版本异常建议重新拉取构建产物别手动改 class 头改完基本就是翻车起点。4.2 HTTPS 调用失败或证书过期老 JDK 的信任库跟不上时间现象程序访问外部 HTTPS 接口时报PKIX path building failed或者javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure。同样一份代码放到 JDK 8 环境就能通放到 1.7.0_13 上直接失败。原因1.7.0_13 的 cacerts 信任库是差不多十年前打包的很多现代内部 CA 和公网根证书不在里面同时 JDK 7 早期版本对 TLS 1.2 和更高级加密套件的支持也不积极服务端如果只开放 TLS 1.2握手就会失败。这不是代码 bug也不是网络问题纯属 JDK 版本太老。解决第一步先确认到底是不是 TLS 版本问题。用 1.7.0_13 的java跑一个最小 HTTPs 客户端加一行参数强行指定协议其他版本/opt/jdk/jdk1.7.0_13/bin/java -Djdk.tls.client.protocolsTLSv1.2 -Djavax.net.debugssl:handshake \ -cp /tmp/test.jar TestHttpsCall如果加上系统属性后握手成功说明问题在于默认协议版本如果还是失败再看javax.net.debug输出里的 CA 校验链。对于信任库太旧的问题不能靠-Djava.security.properties绕过证书校验那等于把安全边界拆了。正确做法是把企业内网 CA 证书导入到 1.7.0_13 的 jre 里/opt/jdk/jdk1.7.0_13/bin/keytool -import -alias company_root_ca \ -keystore /opt/jdk/jdk1.7.0_13/jre/lib/security/cacerts -file root_ca.pem导入前记得看一下仓库里已有证书keytool -list -keystore /opt/jdk/jdk1.7.0_13/jre/lib/security/cacerts缺哪个导哪个别一股脑全导。更长期的做法是升级到 JDK 7 的最后更新版本比如 1.7.0_80至少默认支持 TLS 1.2能少很多事。4.3 中文乱码既不是代码问题也不是数据库问题文件编码被平台默认值绑架现象Linux 控制台启动一个打成 jar 的 Spring 项目日志里的中文正常但通过 Web 接口返回的数据变成???或锟斤拷。同一个 war 包放到 Windows 的 1.7.0_13 上反而正常。还有人碰过 Java 源文件里有中文字符串运行起来全乱。原因JDK 7 及之前的版本文件编码依赖平台默认值。Linux 下一般是 UTF-8Windows 中文版是 GBKmacOS 则可能是 UTF-8。当源码或配置文件的编码和 JVM 启动时解析字符串用的编码不一致时乱码就会出现。1.7.0_13 还留着一个影响特别深的坑-Dfile.encodingUTF-8只影响部分 IO 默认编码编译期没有指定-encoding时javac 还是按 Linux 系统 locale 去读源文件。如果系统 locale 是POSIX默认编码其实是 ASCII中文字符直接变成问号或火星文。解决第一步保证编译时指定编码我前面已经写过-encoding UTF-8第二步在启动脚本里给 JVM 参数加上JAVA_OPTS$JAVA_OPTS -Dfile.encodingUTF-8 -Dsun.jnu.encodingUTF-8这里sun.jnu.encoding管文件系统路径和文件名的编码转换file.encoding管读写文件内容两者经常需要同时设置。第三步确认 Linux 系统 locale最好不要让 JVM 进程跑在非 UTF-8 locale 下echo $LANG $LC_ALL export LANGen_US.UTF-8如果是老系统里 /etc/locale.conf 被改成了LANGen_US之类Java 1.7 就可能默认 iso8859-1中文直接丢字节。这里说句公道话JDK 8 之后默认 UTF-8 的情况改善很多但 1.7.0_13 上必须手动干预不然乱码问题会反复折磨每个接手的人。4.4 “Unrecognized VM option”一看就知道是从高版本 JDK 拿参数复制的现象启动脚本里写了-XX:MetaspaceSize128m -XX:MaxMetaspaceSize256m用 jdk1.7.0_13 启动时报Unrecognized VM option MetaspaceSize进程直接退出。反过来把老项目里的-XX:PermSize256m -XX:MaxPermSize512m原封不动搬到新 JDK 8 上却提示ignoring option PermSize只是没退出但内存调优等于没做。原因JDK 8 起废弃 PermGen 改用 Metaspace参数名完全变了JDK 7 的 1.7.0_13 只认 PermGen 系列参数。很多团队喜欢从网上抄一份 JAVA_OPTS没注意自己目标 JDK 版本结果就是启动失败或参数静默失效。解决给 1.7.0_13 写启动参数时严格按它的参数清单来。常见内存参数我列一张对照表方便你确认现在该填哪个目标效果JDK 7 及 1.7.0_13 使用JDK 8 及以上使用初始堆大小-Xms512m-Xms512m最大堆大小-Xmx2048m-Xmx2048m新生代大小-Xmn512m-Xmn512m永久代初始值-XX:PermSize128m-XX:MetaspaceSize128m永久代最大值-XX:MaxPermSize256m-XX:MaxMetaspaceSize256m如果是 1.7.0_13 上想验证参数是否生效用java -XX:PrintFlagsFinal -version | grep MaxPermSize查一下看到输出里 MaxPermSize 是实际数值才算生效。另外注意JDK 7 中-XX:MaxMetaspaceSize完全不认识所以如果项目同时要支持两个 JDK 版本启动脚本最好按版本分开写别用一个套两个版本都兼容。4.5 GC 日志参数不生效或日志打不出来Xloggc 不能单独靠 PrintGCDetails现象启动脚本加了-XX:PrintGCDetails但系统里找不到任何 GC 日志文件或者日志打出来了但里面时间戳全是相对启动时间没法对到具体运维节点。原因-XX:PrintGCDetails只是把 GC 详情输出到 stdout不带-Xloggc:/路径/gc.log时不会写文件。而且 1.7.0_13 里-Xloggc和-verbose:gc同时出现时行为有时会互相覆盖导致看起来什么都开了实际输出很乱。另外-XX:PrintGCDateStamps在 JDK 7 中不够稳定有些 7u 版本只有PrintGCTimeStamps两者区别是前者打印真实日期后者打印进程启动后的秒数。解决建议一行参数把路径和格式都固定住JAVA_OPTS$JAVA_OPTS -Xloggc:/data/logs/gc.log -XX:PrintGCDetails -XX:PrintGCDateStamps -XX:PrintHeapAtGC启动后立刻检查文件是否生成别等半小时再去看。GC 日志积压会占用磁盘最好在运维脚本里按天切割rclone --max-size 1K /dev/shm/gc.log /data/logs/gc-$(date %F).log 2/dev/null || true实际不用 rclone直接在系统 crontab 里把昨日 gc.log 改名即可。还有一个坑是-Xloggc的路径目录必须存在JDK 7 不像高版本会自己建目录目录不存在时文件打不出且不报错这个坑让很多老运维一度信奉“GC 日志开了也没用”。另外如果你在生产上开了-XX:PrintGCDetails又开着 stdout 重定向到/dev/null那堆日志就全丢了排查内存问题时拿不到任何证据。5. 用 jdk1.7.0_13 自带工具做一次在线健康检查jstat、jmap、jstack 怎么配合最后一个技巧也是最容易被忽略的1.7.0_13 自带的可执行工具链非常完整排查线上问题不需要额外装什么诊断软件。先找到 PID再按“堆外 → 堆内 → 线程”三个层面看数据。我的习惯是三步走。第一步用jps找到 JVM 进程。注意这里不能用ps -ef | grep java替代因为后面要用的是 JDK 自己的 PID 编号体系jps能列出和 JVM 版本关联的进程。如果jps没列出内容查看是否权限不足或 JDK 路径不对直接/opt/jdk/jdk1.7.0_13/bin/jps -l即可。第二步用jstat看 GC 和堆的整体压力/opt/jdk/jdk1.7.0_13/bin/jstat -gcutil pid 1000 10输出里的S0、S1、E、O、M分别代表幸存区、伊甸区、老年代和原数据区。1.7.0_13 里没有M列对应的是PPermGen如果P一直涨到接近MaxPermSize说明永久代不够得调-XX:MaxPermSize而不是盲目加-Xmx。这一步是很多从 JDK 8 过来的人最容易看错的点。第三步如果 GC 频繁但吞吐又低用jmap -heap看详细堆配置再用jstack抓一下线程快照看看是不是死在锁上。推荐精简命令/opt/jdk/jdk1.7.0_13/bin/jstack pid /tmp/thread_$(date %F).log抓下来的线程快照先搜java.lang.Thread.State: BLOCKED重点看有没有两个线程互相等待对方释放 monitor那就是典型的死锁现场。注意jmap在 1.7.0_13 上和 Java 8 的行为不太一样某些系统里jmap -histo会触发一次 Full GC这在生产上要慎用最好先确认清楚再跑。我现在的习惯是先把jstat的 30 秒趋势捞出来确认确实有 Full GC 再考虑jmap不然就是在给生产制造故障。这套流程救过我好几次希望帮到你。本文还有配套的精品资源点击获取