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

麒麟系统浏览器调用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

你输入什么,就会显示什么,如下图

麒麟系统浏览器调用java_maven_02

在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

麒麟系统浏览器调用java_麒麟系统浏览器调用java_03

修改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>

麒麟系统浏览器调用java_vim_04

步骤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 加壳替换成功了。

麒麟系统浏览器调用java_maven_05

步骤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

麒麟系统浏览器调用java_maven_06

看到如图中所示的输出,代表编译成功

在你执行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

看到类似输出代表编译成功

麒麟系统浏览器调用java_开发语言_07

可以到相关的target目录查看编译产生的jar包

麒麟系统浏览器调用java_maven_08

在你执行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库。