LOADING...

加载过慢请开启缓存(浏览器默认开启)

loading

新系统构建老 JDK 的问题处理

前话

前段时间简单地写了个 JVM,遗留一些憾事,是知能力与人力的缺失,每想于此,便不免心生愧疚。

在网络查找提升 JVM 知识的巧合中,无意看到大家对《深入理解Java虚拟机》的赞赏,便读之。由于作者使用的是 JDK 7,而如今就 JDK 15 也出世了,在新系统编译 JDK 7,难免出现疑难症状,特记之。

准备

并非所有环境都是一致,但是有类似的问题,可以保留参考,也可以当作一种启发,去解决过程所遇。

我的环境是在 LXD 下的 Archlinux,首先的是需要 gcc,因为涉及到编译,直接就将 Archlinux 的 devel 包也安装上:

pacman -Syy gcc base-devel
gcc --version
# gcc (GCC) 10.1.0

C/C++ 环境可以参考OpenJDK 所支持构建平台的 Wiki,不然版本不同要绕很多的弯路

更改编译器,如 jdk 子项目的 make/common/shared/Compiler-gcc.gmk 或者 hotspot 子项目的 make/linux/makefiles/gcc.make 文件

因为需要使用 JDK 7 来编译 JDK 7 的源码和 Java 的自动化工具 ant,所以安装 JDK 7 和 ant:

pacman -Syy jdk7-openjdk ant
archlinux-java status
# Available Java environments:
#  java-11-openjdk (default)
#  java-7-openjdk

如果你的环境是 Debian 系的,上面安装的可以执行如下:

apt-get install -y default-jdk ant build-essential gawk m4 libasound2-dev  libcups2-dev libxrender-dev xorg-dev xutils-dev x11proto-print-dev binutils zip unzip

下载 JDK 7 的源码,可以在 官方 或者 Github 下载,如:

git clone https://github.com/openjdk-mirror/jdk7u-jdk

其中 jdk7u 为主分支,里头分了几个子项目,如 corba、hotspot、jdk、langtools,他们可以在主分支编译或者单独编译,编译步骤都是一样,我们就以 jdk 子项目为例子。

进入源码的 ./make 文件夹,执行 make sanity 可以检查编译环境,通过之后使用 make 编译。编译前需要设置一些环境变量,不然会出现 error 和 warn,需要处理的只是 error,简单来说只需:

export LANG=C

假如为了加速编译,可以多线程编译:

export HOTSPOT_BUILD_JOBS=16
export ALT_PARALLEL_COMPILE_JOBS=16

为了更好知道编译过程的错误,可能还需要:

export SKIP_DEBUG_BUILD=false
export SKIP_FASTDEBUG_BUILD=false
export DEBUG_NAME=debug

每次出现错误,最好是 make clean 一下先再 make

编译后的 JDK 会在 ./build 目录中:

./build/linux-amd64/bin/java -version
# openjdk version "1.7.0-internal-debug"

症状

缺少 freetype 2.3 的依赖

直接使用发行版的包管理安装,如 Archlinux:

pacman -Syy freetype2

已经有 freetype,且版本大于 2.3,但是还是提示版本低

看了系统是 2.10 的,可是还是提示低于 2.3,看到 freetypecheck.c 文件里面是用字符串来判断版本高低,那自然排序 2.10 肯定小于 2.3,所以需要到 freetype 官网下载源码,在本地执行 ./configure && make,然后编译 JDK 7 的时候指定该编译的地址。

export _freetype_temp=./freetype-2.3.0

export ALT_FREETYPE_LIB_PATH=${_freetype_temp}/objs/.libs
export ALT_FREETYPE_HEADERS_PATH=${_freetype_temp}/include

提示系统不支持

export DISABLE_HOTSPOT_OS_VERSION_CHECK=ok

出现属于 Java Class 的报错或者找不到虚拟机

因为 JDK 的代码主要是 C++ 和 Java,Java 需要 JDK 7 的编译器,所以需要为其指定一个:

export ALT_BOOTDIR=/usr/lib/jvm/java-7-openjdk
export ALT_JDK_IMPORT_PATH=/usr/lib/jvm/java-7-openjdk
export ALT_HOTSPOT_IMPORT_PATH=/usr/lib/jvm/java-7-openjdk

提示 CurrencyData 超过 10 年的报错

以当前年为限,选定一个 10 年内的年份去替换 ./src/share/classes/java/util/CurrencyData.properties 里的所有超过 10 年的时间,如现在 2020,可以选择 2018:

sed -i 's/[0-9]\{4\}-/2018-/g' src/share/classes/java/util/CurrencyData.properties

出现编译 -Wxxx 的编译错误

在对应项目的编译器配置中修改编译选项,如 hotspot 项目里提示 -Werror=deprecated-declarations,直接在 make/linux/makefiles/defs.make 里的文件前面加入如下:

MAKE_ARGS=CXXFLAGS=" \
-Wno-error=deprecated-declarations \
"

提示认不了 -mimpure-text 选项

这个选项是 g++ 带的,新版已经去掉该选项了,所以我们也在编译选项去掉,在 ./make/common/shared/Compiler-gcc.gmk 里头找到 SHARED_LIBRARY_FLAG = -shared -mimpure-text 改为 SHARED_LIBRARY_FLAG = -shared

提示函数 multiple definition 的报错

这是 gcc 版本 10 之后出现的问题,需要做的就是将 header 文件的声明改为 extern,然后对应的 c 文件在引入 header 头后追加那些声明。

现在有三处地方有该问题:

  1. src/solaris/native/sun/security/jgss/wrapper/NativeFunc.h 和 src/solaris/native/sun/security/jgss/wrapper/NativeFunc.c

    将在 header 头的 ftab 函数加上 extern

    extern GSS_FUNCTION_TABLE_PTR ftab;

    在 c 源码文件中,引入 header 头之后加入声明:

    #include "NativeFunc.h"
    GSS_FUNCTION_TABLE_PTR ftab;
  2. src/solaris/native/sun/nio/ch/Sctp.h 和 src/solaris/native/sun/nio/ch/SctpNet.c

    里面涉及几个函数,但是修改方法同 1。

  3. src/solaris/native/sun/awt/gtk2_interface.h 和 src/solaris/native/sun/awt/gtk2_interface.c

    修改方法一样,但是函数有点多,所以采用脚本,倒数 160 行开始,复制到 c 源码文件中,然后执行:

    tail -160 src/solaris/native/sun/awt/gtk2_interface.h | sed -i 's/^\([^#\/[:space:]]\)/extern \1/g'

提示找不到 <sys/sysctl.h>

新版的 glibc 已经没有这个 header 头,这里可以简单使用 <linux/systcl.h> 代替,现在需要改的有:

  • ./src/solaris/native/java/net/PlainSocketImpl.c
  • ./src/solaris/native/java/net/PlainDatagramSocketImpl.c

返回类型 bool 无法转为 jobject

根据提示找到 ./src/share/native/com/sun/java/util/jar/pack/jni.cpp 文件对应的行数,然后修改该函数的返回类型,由 return false 改为 return 0

C++ 关键字 register 已经废除的问题

在 make 文件夹的 defs.make 文件里头 MAKE_ARGS 追加忽略该特性:

MAKE_ARGS=CXXFLAGS=-Wno-error=register

一些 C++ 的头文件找不到

诸如 hotspot/src/share/vm/adlc/adlc.hpp 里头找不到 opto/opcodes.hpp 和 opto/adlcVMDeps.hpp,如果是需要自己修改文件地址,那确实是麻烦,在 defs.make 的加入全部文件夹:

INCDIRS := $(addprefix -I,$(shell find . -type d -print))
MAKE_ARGS=CXXFLAGS="-Wno-error=register \
-I ${INCDIRS}"

值得注意的是,有些依赖的头文件是在 build 后生成的文件,所以也需要加上类似 .../hotspot/build/linux/linux_i486_compiler2/generated 的路径。

编译时候提示未实现的断言失败和 $XMMRegister 变量问题

找到 hotspot/src/share/vm/adlc/output_c.cpp 对应的地方注释掉:

/* printf("emit_field: %s\n",rep_var);
globalAD->syntax_err(_inst._linenum, "Unknown replacement variable %s in format statement of %s.", rep_var, _inst._ident);
assert( false, "UnImplemented()"); */

fpermissive 错误的问题

这个在新版 gcc 是个无法忽略的错误,主要是两个文件 hotspot/src/share/vm/oops/cpCacheOop.hpphotspot/src/share/vm/oops/cpCacheOop.hpp。分别将语句改成如下,其实就是 -1 改为 ~0u

option_bits_mask           = ~(((~0u) << tos_state_shift) | (field_index_mask | parameter_size_mask))

all_types           = ((1 << TYPE_LIMIT) - 1) & ((~0u) << FIRST_TYPE),

friend 函数默认值问题

在对应文件,如 hotspot/src/share/vm/code/relocInfo.hpp 中去掉默认值:

inline friend relocInfo *prefix_relocInfo*(int datalen);

系统函数没法调用到

这是因为定义识别到 LINUX 的宏,在编译参数追加:

MAKE_ARGS=CXXFLAGS=" \
-Wno-error=deprecated-declarations \
-DLINUX=1 \
-DTARGET_COMPILER_gcc=1 \
"

jlong 没有定义

这个因为 Linux 下没有 __int 64 的 bug,可以追加个,最简单是在编译参数里追加:

MAKE_ARGS=CXXFLAGS=" \
-D__int64=\"long long\" \
"

没有找到 OpenJDK 自己的头文件

INCDIRS := $(addprefix -I,$(shell find $(shell pwd)/.. -type d -print))
-I ${INCDIRS} \
"