1.实验目标
对于以Java语言为代表的解释型语言来说,应用迁移相对简单一 些,因为Java的虚拟机JVM屏蔽了不同处理器之间指令集架构的区 别,所以,纯Java语言编写的程序不需要重新编译,而对于调用了编 译型语言so库的应用来说,需要重新编译。通过具体的实验案例,掌握纯java语言编写的程序如何迁移,以及调用so库的解决方案。
2.实验环境
基于X86架构的麒麟操作系统V10。
基于Arm架构(鲲鹏920)的麒麟操作系统V10。
3.实验步骤
查询本地的java环境
分别在X86架构的麒麟操作系统V10,Arm架构(鲲鹏920)的麒麟操作系统V10环境下执行以下命令
步骤1 查看本机已经安装的java版本。
#yum search java
麒麟默认提供了java1.8,java-11两套java开发环境。可以依据实际情况自行确定。
我们这里选用java1.8作为开发环境
yum install java-1.8.0-openjdk java-1.8.0-openjdk-devel java-1.8.0-openjdk-headless -y
本地安装java1.8的环境
yum remove java-11-openjdk-devel java-11-openjdk java-11-openjdk-headless -y
删除java-11
步骤3 验证java,javac的版本
[root@jd4 tmp]# java -version
openjdk version "1.8.0_292"
OpenJDK Runtime Environment Bisheng (build 1.8.0_292-b10)
OpenJDK 64-Bit Server VM Bisheng (build 25.292-b10, mixed mode)
[root@jd4 tmp]# javac -version
javac 1.8.0_292
在两台主机上均完成以上操作,则可以进入下一步骤。
纯java代码迁移
x86架构下完成以下操作,配套代码可以在java_demo目录中查找
步骤1 编写java测试代码
mkdir -pv /usr/local/src/code/java_demo
cd /usr/local/src/code/java_demo
vim Demo.java
import java.util.Scanner;
public class Demo{
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
String input = sc.nextLine();
System.out.println(input);
sc.close();
}
}
步骤2 编译并执行
[root@node1 java_demo]#javac Demo.java
[root@node1 java_demo]# ls
Demo.class Demo.java
java Demo
你输入什么,就会显示什么,如下图
在x86架构下编译及运行没问题了,把这个编译好的.class文件 复制到鲲鹏架构的服务器上。
步骤3 移植java代码
把x86架构下环境下编写的java代码拷贝到Arm架构环境。在Arm 环境完成以下操作:
先确认环境
[root@jd4 tmp]# java -version
openjdk version "1.8.0_292"
OpenJDK Runtime Environment Bisheng (build 1.8.0_292-b10)
OpenJDK 64-Bit Server VM Bisheng (build 25.292-b10, mixed mode)
[root@jd4 tmp]# javac -version
javac 1.8.0_292
拷贝java代码
在x86主机执行
[root@node1 java_demo]# scp Demo.class 179.119.224.2:/tmp
The authenticity of host '179.119.224.2 (179.119.224.2)' can't be established.
ECDSA key fingerprint is SHA256:QBfezyONvuiNbSmao2ml3MjaZX/BMllINTtPJ4TetJk.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '179.119.224.2' (ECDSA) to the list of known hosts.
Authorized users only. All activities may be monitored and reported.
root@179.119.224.2's password:
Demo.class
这里的ip地址请根据实际情况修改
步骤4 执行java代码,并验证
在arm主机执行
[root@jd4 ~]# cd /tmp/
[root@jd4 tmp]# java Demo
hello java
hello java
Java 依赖So库迁移
对于大型项目来说,很多时候不仅仅使用一种语言来开发应用, 有时候会使用多种语言进行混合编程,例如著名的开源项目Netty, 主体开发语言是Java,但是在部分项目里还使用了C语言。出现这种 情况的一个原因是Netty在Linux下的异步/非阻塞网络传输中,使用 了Epoll——一个基于I/O事件通知的高性能多路复用机制。Netty是 通过JNI方式提供Native Socket Transport的,在Netty的 transport-native-epoll项目中,有相关调用的C代码。除此之外,在Netty的依赖项目netty-tcnative-parent中,也有 JNI方式提供的C语言调用。
Netty是开源的项目,在获得所有的源代码后,可以通过对代码 进行重新编译的方式来执行迁移。因为代码里有Java和C语言,并且 Netty项目是通过Pom进行项目组织管理的,在迁移时不但要安装C的 编译环境,还要安装openjdk和Maven。
步骤1 通过yum 安装依赖环境
#yum install gcc gcc-c++ make cmake3 libtool autoconf automake ant wget git openssl openssl-devel apr-devel ninja-build -y
步骤2 安装Maven
下载Maven
#cd /usr/local/src
#wget http://archive.apache.org/dist/maven/maven-3/3.5.4/binaries/apache-maven-3.5.4-bin.tar.gz
解压缩
#cd /usr/local/src
#tar fvxz apache-maven-3.5.4-bin.tar.gz
#mv apache-maven-3.5.4 /opt/maven
修改环境变量
vim /etc/profile
MAVEN_HOME=/opt/maven
PATH=$MAVEN_HOME/bin:$PATH
export MAVEN_HOME PATH
#执行下面的命令更新环境变量
source /etc/profile
查看版本
mvn -version
修改maven的配置文件,更改镜像仓库网址为国内的镜像网址。
vim /opt/maven/conf/settings.xml
<mirror>
<id> huawei </id>
<name>huawei maven</name>
<url>https://mirrors.huaweicloud.com/repository/maven/</url>
<mirrorOf>central</mirrorOf>
</mirror>
步骤3 修改gcc、g++ 默认编译选项
鲲鹏架构中char类型问题
设置gcc和g++的编译选项来处理,也就是把这两个编译器的编译 加上-fsigned-char的选项。
给gcc加一个壳
[root@jd4 src]# which gcc
/usr/bin/gcc
[root@jd4 src]# mv /usr/bin/gcc /usr/bin/gcc_bak cp
[root@jd4 src]# vim /usr/bin/gcc
#!/usr/bin/env bash
/usr/bin/gcc_bak -fsigned-char "$@"
[root@jd4 src]# chmod +x /usr/bin/gcc
[root@jd4 src]# gcc --version
gcc_bak (GCC) 7.3.0
Copyright © 2017 Free Software Foundation, Inc.
看到下图,就代表gcc 加壳替换成功了。
步骤4 下载netty相关的安装包
cd /usr/local/src/
wget https://mirrors.tuna.tsinghua.edu.cn/apache/apr/apr-1.6.5.tar.gz
wget https://mirrors.tuna.tsinghua.edu.cn/OpenBSD/LibreSSL/libressl-3.1.1.tar.gz
wget https://www.openssl.org/source/openssl-1.1.1g.tar.gz
wget https://GitHub.com/netty/netty-tcnative/archive/netty-tcnative-parent-2.0.34.Final.tar.gz
步骤5 移植
通过编译安装,在arm 环境下完成以下操作
-tcnative-parent-2.0.34.Final.tar.gz
-tcnative-netty-tcnative-parent-2.0.34.Final/
.xml
474行
<!-- <get src="http://archive.apache.org/dist/apr/${aprTarGzFile}" dest="${project.build.directory}/${aprTarGzFile}" verbose="on" /> -->
cd libressl-static/
.xml
263行
<!-- <get src="https://ftp.openbsd.org/pub/OpenBSD/LibreSSL/${libre sslArchive}" dest="${project.build.directory}/${libresslArchive}" verbose="on" /> -->
../openssl-static/
.xml
334行和338行
<!-- <get src="https://www.openssl.org/source/openssl-${opensslVersion}.tar.gz" dest="${project.build.directory}/openssl-${opensslVersion}.tar.gz" verbose="on" /> -->
<!-- <get src="https://www.openssl.org/source/old/${opensslMinorVersion}/openssl-${opensslVersion}.tar.gz" dest="${project.build.directory}/openssl-${opensslVersion}.tar.gz" verbose="on" /> -->
..
.xml
-static的编译(在第603行),因为 boringssl-static需要从谷歌服务器获取资源,由于无法获取成功,
这里就取消对它的编译,但不影响后续的使用
<!-- <module>boringssl-static</module> -->
纯文本
创建openssl-static,libressl-static项目的target目录
[root@jd4 netty-tcnative-netty-tcnative-parent-2.0.34.Final]# pwd
/usr/local/src/netty-tcnative-netty-tcnative-parent-2.0.34.Final
在此目录下执行一下命令
[root@jd4 netty-tcnative-netty-tcnative-parent-2.0.34.Final]# mkdir -pv openssl-static/target
mkdir: 已创建目录 'openssl-static/target'
[root@jd4 netty-tcnative-netty-tcnative-parent-2.0.34.Final]# mkdir -pv libressl-static/target
mkdir: 已创建目录 'libressl-static/target'
纯文本
拷贝之前下载的软件包到target目录
# cp /usr/local/src/apr-1.6.5.tar.gz /usr/local/src/netty-tcnative-netty-tcnative-parent-2.0.34.Final/openssl-static/target/
# cp /usr/local/src/openssl-1.1.1g.tar.gz /usr/local/src/netty-tcnative-netty-tcnative-parent-2.0.34.Final/openssl-static/target/
# cp /usr/local/src/apr-1.6.5.tar.gz /usr/local/src/netty-tcnative-netty-tcnative-parent-2.0.34.Final/libressl-static/target/
# cp /usr/local/src/libressl-3.1.1.tar.gz /usr/local/src/netty-tcnative-netty-tcnative-parent-2.0.34.Final/libressl-static/target/
纯文本
进入主目录执行编译
#cd /usr/local/src/netty-tcnative-netty-tcnative-parent-2.0.34.Final
#mvn install
看到如图中所示的输出,代表编译成功
在你执行mvn install的过程中,背后其实调用了make
[root@jd4 netty-tcnative-netty-tcnative-parent-2.0.34.Final]# find ./ -name Makefile
./libressl-static/target/apr-1.6.5/test/internal/Makefile
./libressl-static/target/apr-1.6.5/test/Makefile
./libressl-static/target/apr-1.6.5/Makefile
./libressl-static/target/native-build/Makefile
./openssl-dynamic/target/native-build/Makefile
./openssl-static/target/apr-1.6.5/test/internal/Makefile
./openssl-static/target/apr-1.6.5/test/Makefile
./openssl-static/target/apr-1.6.5/Makefile
./openssl-static/target/openssl-1.1.1g/demos/bio/Makefile
./openssl-static/target/openssl-1.1.1g/demos/evp/Makefile
./openssl-static/target/openssl-1.1.1g/Makefile
./openssl-static/target/native-build/Makefile
纯文本
编译netty-all-4.1.52
先查询jni.h与jni_md.h的位置
[root@jd4 netty-netty-4.1.52.Final]# find / -name jni.h
/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.292.b10-8.ky10.aarch64/include/jni.h
/opt/hyper_tuner/tool/bisheng-jdk-11.0.12/include/jni.h
[root@jd4 netty-netty-4.1.52.Final]# find / -name jni_md.h
/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.292.b10-8.ky10.aarch64/include/linux/jni_md.h
/opt/hyper_tuner/tool/bisheng-jdk-11.0.12/include/linux/jni_md.h
纯文本
#cd /usr/local/src
#wget https://GitHub.com/netty/netty/archive/netty-4.1.52.Final.tar.gz
#tar fvxz netty-4.1.52.Final.tar.gz
#cd netty-netty-4.1.52.Final/
#vim /usr/local/src/netty-netty-4.1.52.Final/transport-native-unix-common/pom.xml
198行
<env key="CFLAGS" value="-O3 -Werror -Wno-attributes -fPIC -fno-omit-frame-pointer -Wunused-variable -fvisibility=hidden -I/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.292.b10-8.ky10.aarch64/include/ -I/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.292.b10-8.ky10.aarch64/include/linux/" />
263行
<env key="CFLAGS" value="-O3 -Werror -Wno-attributes -fPIC -fno-omit-frame-pointer -Wunused-variable -fvisibility=hidden -I/usr/lib/jvm/java-1.8.0-openjdk-1.8. 0.292.b10-8.ky10.aarch64/include/ -I/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.292.b10-8.ky10.aarch64/include/linux/" />
#mvn install -DskipTests
看到类似输出代表编译成功
可以到相关的target目录查看编译产生的jar包
在你执行mvn install的过程中,背后其实调用了make
[root@jd4 netty-netty-4.1.52.Final]# find ./ -name Makefile
./transport-native-epoll/target/native-build/Makefile
./transport-native-unix-common/Makefile
总结:
在一个Java程序执行时,首先通过javac把java文件编译为虚拟机可以识别的class文件。 然后由JVM解释器解释class文件中的字节码,通过JVM把解释结果转变为机器码执行。 这是我们通常所说的解释执行。所以,纯Java语言编写的程序不需要重新编译。有依赖的So库需要迁移,可以先安装好编译需要的GCC与Maven,然后在新平台下重新编译So库。