用GCJ本地化编译
在网上看到一篇用GCJ把Eclipse本地化编译的文章,最近一直在和lindit25一起学习用GCJ本地化,所以就把这篇文章翻译了一下;
我翻译上半部分,lindit25翻译下半部分.
由于这是我们第一次翻译文章,英文也不好.会有许多错误,请大家谅解,指点,不甚感激.
Eclipse 本地化
作者: John Healy, Andrew Haley and Tom Tromey
红帽的Eclipse的项目组已经把这个流行的集成开发环境移出JVM环境。
Eclipse是一个开源的,可扩展的,发展十分迅速的集成开发环境(即IDE)。完全用Java写的Eclipse提供一个可以让开发者编写Java,c,c++程序的多语言开发环境。为了满足用户提高红帽子开发程序组的性能和跨平台能力,而Eclipse又是这些要求的核心,我们生成了被本地化编译的Eclipse版本,而不是象一般的Java程序一样在Java虚拟机上运行,当然用户也可以选择让Eclipse在Java虚拟机上运行。红帽版的Eclipse是被编译成二进制代码,使用libgcj runtime libraries本地运行的,就像c编写的程序使用GNU C libraries运行一样。
为了将Eclipse本地化,我们使用GCJ(一个免费的,最佳的Java预编译器)这个工具。GCJ能够将Java源代码编译成本地机器代码。我们采用的方法就是先将Java源代码编译成二进制的字节码,然后在编译成本地的机器码。
这篇文章讨论了为什么本地化编译是一种十分吸引人的选择。解释了我们关于GCJ,libgcj和让Eclipse能本地化我们所要做的。而且,通过一个现实的例子展示Java开源还有一条很长的路要走,但是现在已经有商业价值了。
动机
从早期的设计开发程序组时,考虑的两个的因素使我们选择本地化编译:跨平台和性能。红帽子企业版Linux计划在64位的体系结构中运行,而且我们有确保所有的开发程序组程序都能正常运行。其中一个大问题就是Eclipse还不能在64位的系统中运行,而且,Eclipse有一些图形工具集的代码,尤其是SWT界面的,和它的C的库文件只是在32位下寻址。除了创建一个纯净的64位版的SWT,我们还要面对一个更严峻的问题:没有关于x86_64和AMD64位的Java虚拟机,而且我们看不到一丝希望,在我们发布64位版本前这样的Java虚拟机会出现。
我们遇到的另一个问题就是性能。Eclipse能在Windows下运行的非常好,但是此时的Linux版本运行的十分慢,我们发现Startup单独运行就要超过一分钟的时间,而且之前的用户测试发现Eclipse的界面对于舒适的运行的要求显得有点迟钝。例如,Eclipse是基于可视化的(集视图与编辑于一身,但同时只能出现一个)。而且用户在视图与编辑之间的转换是十分频繁的,但是在视角间转换需要相当长的时间是与面向企业编程市场的红帽开发程序组的目标相违背的。
我们的解决方案就是用GCJ把Eclipse编译成本地二进制代码,这样就不需要安装Java虚拟机了。而且,我们知道本地化编译能提高软件的性能,因为我们不再需要运行java虚拟机所带来的开销。不仅如此,由于在我们需要支持的所有64位平台GCJ/libgcj都能运行,它还能解决跨平台问题。但是在某些情况下,如x86_64,我们仍然有许多事要做。本地化编译不仅解决了我们所遇到的问题,而且带来了减少外部相关性的额外好处。而且让我们能够对于Java的开源做一些重要的贡献和在Java开源在商业价值上已经成熟做个示范。
方法
最初做这个项目的时候,我们对于能否用GCJ将Eclipse本地化编译并能运行没有把握。
首先,Eclipse是一个超过二百万行代码的大型程序。而且我们对于Eclipse的内部结构和使用的工具知之甚少。其次,GCJ是最初是运行在嵌入式的系统中。我们所知道的部分仍是工作在Java编程语言上,尤其是在类的装载程序中,而正式Eclipse所大量使用的。最后,那些免费的类库不是完整的。我们根本不知道Eclipse是否会用到一些我们没考虑到的工具。而且就像许多Java程序似乎要做的那样,Eclipse可能会违反准则使用他内部的,不在com.sun.*.interfaces中的类。
因此,我们采取了双管齐下的方式决定这样一个项目是否可以成功。首先,我们要利用GCJ做一个Eclipse用到的而我们又没有或不能实施的API的列表。为了达到这一点,我们写了一个Shell脚本,它能够试着将每一个jar文件编译为目标代码,然后我们检查所有的错误信息看有没有漏掉的。脚本的运行的结果不太乐观:发现漏掉了大量的包。由于还有一些东西我们不太明白,我们需要做进一步的研究。比如说,里面有许多与Swing有关的类,而我们知道Eclipse使用SWT而非Swing.
进一步的研究发现,许多怪异的未定义的引用并不是来自Eclipse本身而是来自Eclipse所包含的第三方的jar文件。Eclipse有它自己的Ant build工具和the Apache Tomcat dynamic Web server。我们知道,在Eclipse环境中许多被引用的类并没有被真正的加载。这就使我们能够采用另一种视角使Eclipse工作。
第二个角度就是libgcj提供的二进制解释器运行Eclipse。基于以下理由我们采用这种方式:我们能够专注于运行时的bug,包括前面提到的类装载的问题和漏掉的Eclipse实际使用的功能。
最初这种方法进展的也不是很顺利。我们不仅在类加载过程中遇到问题,而且在libgcj的执行时所需的保护范围中遇到了问题。那都是基于Java的安全的沙盒结构,一个能够让不安全的代码以安全的方式运行。不幸的是,这一块的问题有一个负面影响:我们必须先修复所有的bug,然后才能发现下一个bug。
对于libgcj的改变
我们的对于libgcj的第一步改变仅仅是修复bug。我们实行适当的保护域。然后,我们遍历了整个运行过程,修复与类加载有关的bug。由于类的装载已采用libgcj, 我们必须调整一切涉及到类加载的本地代码的位置。
一旦这个完成了,我们就能用libgcj二进制解释器运行Eclipse。这是问题变成:我们怎么样才能运用好GCJ来编译Eclipse?
预编译所用的类,然后将他们链接起来的本地编译方法会与Eclipse复杂的类装载方式冲突。由于对Eclipse的内部的研究,我们将这种方法排出。
更多调查显露, 大多数类由DelegatingURLClassLoader装载。 DelegatingURLClassLoade是标准URLClassLoader 的子类被扩大的,用于了解Eclipse's 插件式的体系结构的。现在看来最好的办法就是修改Eclipse是他能象调用预编译的共享库以及二进制文件。
事实上,我们不得不走的更远,也是对libgcj扩展一点。libgcj知道在响应一个调用时,如Class.forName(),如何在后台调用共享库。不过,这个神奇的方式总是发生在水平一流的类装载中.。但是对于那些定义自己的类装载的软件,包括Eclipse这种方法不起作用。 所以我们编写了一种新的gcjlib链接.。这就像一个jar链接。为了让gcjlib能被特殊对待,我们把类加载器的实施做了小小的扩展。
仅做这些是不够的,但是. 我们还得解决链接问题。尤其是,如果我们把一个jar文件编译成一个共享库。如何防止这种dlopen()共享库因未能立即解决符号而出错? 要解决这个问题就要恢复和清理GCJ中的-fno-assume-compiled选项。 这个还没有完成的选项能提供一个可供选择的API,它能让GCJ的解决多数引用的输出在运行时间而不是在链接时间的。
然而,-f-no-assume-compiled选项有种种限制,且影响效率。关于项目组对未来有一个更清晰的方式来实现这些相同的目标.。在GCJ的邮件列表中,作为本办法所指的二进制兼容的API或-findirect-dispatch.这个新的API不仅能够做所有-fno-assume-compiled能做的,而且效率更高,兼容性更好。开发是不断进行的,而且在这个新特点会做得更好。这是数个对GCJ的企业版的准备中有贡献中的一个。

用GCJ本地化编译
接上:
Eclipse的编译
我们已经把这些都设定好了,那么我们将最后准备对eclipse进行本地化编译,这将会使eclipse运行变的非常快,大部分的工作就是把同类的改变为3种不同的方式。实质上就是在它运行时也在寻找相同名字的动态文件,而这些动态文件就与他们旁边,如果找到了,将会改变jar URL到gcjlib URL,做一切重写都是有条件的,所以所有的对ECLIPSE的本地化编译都没有改变Java虚拟机,换句话说,用户如果想用Java虚拟机而不是本地化过的 ,还是可以的。
如果这做好了,就开始写能够知道怎样从动态共享库启动ECLIPSE平台的启动器,这将会在90 行代码左右。
本地化简介
完成了所有的这些后,Eclipse还是莫名其妙的很慢,我们做错了什么吗?是GCJ编译的代码比just-in-time (JIT)编译的差吗?是“fno-assume-compiled”开销很大吗?一个很大的好处就是gcj的输出可以以相同的方式处理,就是说,现有的工具,如:轮廓就可以直接运用而不用做改变。这实际上就是
我们怎么研究我们的性能问题第一个问题就是我们注意到当平台开始工作的时候得到很多异常,尽管对GCJ runtime 的改变可能会违反JAVA的规则。我们注意到轮廓输出有了一个奇怪的现象。
原来,libgcj runtime的一个小的有BUG的组件深线运行造成搜索异常处理表,而非预期二进制搜索。每次对全部代码的搜索的开销将会很大,对这些不定的组件代码的整理证明了这个问题,突然,我们本地化的Eclipse能够比使用JVM的版本快一些,量化它: 启动时间从多分钟减少到15秒 。
局限性
一般说来,我们不直接编译Eclipse从源程序到“实物代码”。代替的是,我们编译到字节码然后编译jar files到分享库。这样做有两个原因。首先,有些小错误在GCJ source compiler固定。其次,Eclipse来自他自己的build scripts这个从源到字节码。重新工作Eclipse让系统直接从源二进制比上游代码有更大的分散度同时,我们不会把所有JAR文件预编译到动态共享库,一些依然保持为JAR,而一些在运行时才得到解释。这样做是因为类库依然完整,同时,那些JAR文件还没有处理。我们其中的一个修补对公共的GCJ并不适合, 我们扼杀了编纂时字节代码验证器,因为对Eclipse jar 文件的编译不好 ,易生BUG,我们在这个过程中更换认证审核,他更加强劲。
另外,值得一提的汇编Eclipse的局限性,不能够用本地化的Eclipse调试GCJ-compiled 应用程序。因为jdwp,Java Debug Wire Protocol (用的是eclipse)还没有用libgcj实施.
意义与展望
实现本地化汇编eclipse强有力显示开放源码基于Java的GCJ和libgcj/classpath 已经达到近乎商业有益.尽管如此,它仍没有完成. 在开源Java语言恰当地替代jvms专利权之前,有的相当差别仍需改进。其中一个重要方面是工作的需要开发/整合了JIT的编译器. JIT应将允许GCJ-based open-source 的java环境能够像传统的JVM那样用。也就是说本地编译与平台的差别将不会影响到程序的运行。
其他主要工作还需要一块,是迄今为止最明显缺少的:piece-Swing。基于open-source 的Swing工作做的很好,他是GNU的一个项目,但是,Swing是一项庞大的项目,同时,GNU的实施也不是很方便。
完全开放源码Java的环境是有吸引力的,能够替代jvms专利。而且现在达成. 在过去6个月中, 红帽子人数已超过一倍工程师的工作,支持和开放源Java的解决方案,Eclipse是一个大型的、复杂的软件 准确无误、编辑和运行,这是一个很好的考验,证明了开放源码的作用。开放源码的力量在于它社区 所以请考虑加入开放源码社区,为GCJ 与 GNU 通过你感兴趣的项目的方式做出你的贡献!
这篇文章来源:www.linuxjournal.com/article/7549.
希利经理约翰是红帽子的Eclipse工程集团总部设在多伦(people.redhat.com/jhealy). 他在过去的工作习惯开放源toolchains嵌入式处理器以
及CRM与计算机电话应用. 7413aajh.jpg
Haley已经是一名编程老手. 他是其中之一GCJ-2099保持的. 他为红帽子,他支持这项工作.
汤姆tromey已工作7413aaah.jpg自由软件自九十年代初. 他出现在海合会块、屋、侏儒、自动配置、光电倍增管及其它配套大概他遗忘. 他
的作品在红帽子作为这次Eclipse带领工程技术团队. 联系:tromey@redhat.com. 7413aatt.jpg
最后,觉得文章得的最后一点说的很好:Eclipse是一个大型的、复杂的软件 准确无误、编辑和运行,这是一个很好的考验,证明了开放源码的作用。开放源码的力量在于它社区 所以请考虑加入开放源码社区,为GCJ 与 GNU 通过你感兴趣的项目的方式做出你的贡献!
这篇文章来源:www.linuxjournal.com/article/7549.
希利经理约翰是红帽子的eclipse工程集团总部设在多伦(people.redhat.com/jhealy). 他在过去的工作习惯开放源toolchains嵌入式处理器以及CRM与计算机电话应用. 7413aajh.jpg
Haley已经是一名编程老手. 他是其中之一GCJ保持的. 他为红帽子,他支持这项工作.
汤姆tromey已工作7413aaah.jpg自由软件自九十年代初. 他出现在海合会块、屋、侏儒、自动配置、光电倍增管及其它配套大概他遗忘. 他的作品在红帽子作为这次eclipse带领工程技术团队. 联系:tromey@redhat.com. 7413aatt.jpg
Eclipse Goes Native
Classpath 用例
适用范围:包括 Fedora Core 在内的所有发行版。
本文示范如何可以体验到我们努力中的成果。以前在Java 陷阱中被束缚的自由应用程序在一个完全自由的环境中注入新的活力。因为我们软件的稳定,所以和任何工作环境都没有关系。一切都已经协调好了!
这是给所有为实现这个可能而提供帮助的同伴们的一份礼物,也是一个自由软件发展的有力的示范。
除了本文中所有令人振奋和漂亮的技术资料外,请不要忘记一个热情的社区为你提供了软件自由:请欣赏并分享它们吧!
索引
基于 Qt4 的 AWT 图形函数对等(peers)
Eclipse - 一种通用的工具平台 - 一个无差别地对任何事物开放可扩展的集成开发环境(IDE)
本地 Eclipse - 快速而强劲的集成开发环境
Apache Ant - 基于 Java 的构建工具
基于 Qt4 的 AWT 图形函数对等(peers)
最后编辑:2005 年 9 月 7 日
说明:
自 GNU Classpath 0.18 以后,我们为 AWT 图形函数对等而支持新的第 4 版的 Qt 函数库。这意味着 java.awt.Frame、java.awt.Button 等所有可视图形由 Qt 库来绘制和操纵。虽然 Qt4 明显被 GNU Classpath 的底层构建组织所支持,但大部分操作系统仍未将其实装。为了体验由我们自由的 Qt4 绘制的 AWT,下文说明你需要做的事情:
第一步是下载、编译并安装 Qt 4.0.1 或以上版本。我们建议你在 configure 的时期通过使用 --prefix 参数把它安装到你的 home 目录内的某个位置中。接下来的讨论假设 ${QT4} 就是该库所在的安装目录。
把 ${QT4}/bin} 放到你的 PATH 环境变量中,并确定当你执行 moc -v 时显示它的版本是 4.0.1 。
把 ${QT4}/lib 添加到 LD_LIBRARY_PATH 环境变量中(或者在你的操作系统上使用一个等价的机制)
把 ${QT4}/lib 添加到 PKG_CONFIG_PATH 环境变量中。
在 Classpath 的源代码目录中执行 autoreconf 。
使用 --with-qt-peer (及所有其他需要的)参数调用 configure 。
下载、构建并安装 GNU Classpath 0.18 。
注意:为了修复一个构建问题,在编译 GNU Classpath 0.18 之前请在 native/jni/qt-peer/componentevent.cpp 中插入 #include 。
运行一个 AWT 应用程序并把系统属性 awt.toolkit 的值设置为 gnu.java.awt.peer.qt.QtToolkit 。
例如,运行 Classpath AWT 演示应用程序你可以使用
jamvm -cp examples.zip -Dawt.toolkit=gnu.java.awt.peer.qt.QtToolkit gnu.classpath.examples.awt.Demo
注意:为此你需要 JamVM 1.3.3,它现在只能从 CVS 获得。
Eclipse - 一种通用的工具平台,一个任何事物都开放可扩展的集成开发环境
最后编辑: 2005 年 9 月 7 日
说明:
GNU Classpath 工作组因为能让这个著名而自由的开发环境脱离限制地运行起来而感到非常自豪。
安装 Eclipse 3.1 并使用最新的 GCC 4.0 快照(snapshot)版本(构建说明,一些发行版已经在它们的试验区域实装了 GCC 4.0 的预发布版本)或者使用 JamVM 1.3.3 (CVS) + GNU Classpath 0.18。
我强烈建议你使用一个预先编译好的或者在你的发行版上实装了的 Eclipse 版本。从源代码构建 Eclipse 是一项非常复杂的任务,我不打算在这里加以说明。
注意:按照构建指南,对于 GCJ 4, 请确定你已经调整好 PATH 和 LD_LIBRARY_PATH 这两个环境变量。
进入 Eclipse 安装目录,运行如下:
对于 GCJ 而言:
./eclipse -consoleLog -debug -vm gij
对于 JamVM 而言:
PATH=.:${PATH} ./eclipse -consoleLog -debug -vm jamvm -vmargs -mx256m
(它阻止了一个问题,这个问题会在找不到 startup.jar 时抛出一个 OutOfMemoryError 。)
注意:在 Gentoo GNU/Linux 上执行的文件将是 eclipse- ,其中 可能是 gtk 或者 motif 。
为了使 Eclipse 在 Kaffe 上运行,你需要从 CVS 中构建它并使用一个启动脚本 。详情请参阅这封邮件 。
参考资料:
Eclipse 的项目主页
Mark Wielaard 自 2002 年 12 月起写的纯历史的说明文档,其结果从那时起成熟了许多。
本地 Eclipse - 快速而强劲的集成开发环境
最后编辑:2005 年 8 月 31 日
说明:
既然你让 Eclipse 运行起来了,你可能会对本地编译究竟有多快而感兴趣。好吧,请继续阅读下去。
当然,你需要 GCJ 进行本地编译和执行。请阅读之前发布的安装说明。
用一个系统适用面广的 Eclipse 安装版进行实验不是一件好事。我建议你把整个 Eclipse 目录复制到你的 home 目录内的某个位置进行实验。然后进入这个目录并把以下脚本写入一个文本文件中:
#!/bin/sh
gcj-dbtool -n eclipse.db
for JAR_FILE in `find -iname "*.jar"`
do
echo "Compiling ${JAR_FILE} to native"
gcj -shared -findirect-dispatch -Wl,-Bsymbolic -fjni -fPIC -o ${JAR_FILE}.so ${JAR_FILE}
gcj-dbtool -a eclipse.db ${JAR_FILE} ${JAR_FILE}.so
done
现在运行这个脚本,去喝一杯咖啡,因为这会花一点时间。我这里使用的版本拥有合共 36 兆(压缩了的)类数据的 146 个 jar 文件。当所有工作都做好了的时候,你将会得到一个带有从字节码 Java 类到它们本地相应部分的映射的一个 gcj 数据库文件。这个数据库就在一个叫做 eclipse.db 的文件里面。
用以下命令运行 Eclipse 以体验本地编译令人惊异的能力:
./eclipse -consoleLog -debug -vm gij -vmargs -Dgnu.gcj.precompiled.db.path=eclipse.db
相信了吗?
顺便说一下,这个伟大的东西已经被 Fedora Core 4 收录,并且非常有可能添加到别的发行版中,例如 Ubuntu。
顺便再说一下,Debian 用户可能需要执行 apt-get install gcj-4.0 gcc-4.0 gcc-snapshot 并用 gcc-4.0 和 /usr/lib/gcc-snapshot/bin/gcj-dbtool 代替 gcj 和 gcj-dbtool 。
Apache Ant - 基于 Java 的构建工具
最后编辑:2005 年 3 月 2 日
说明:
Ant(“another neat tool”,另一个灵巧的工具)是为了克服基于 Makefile 的系统的局限性而制造的。长期以来这个新工具依赖于非自由的运行机制。我将展示一个从命令行调用 Ant 的简单方法,以及你必须关心的东西。我希望各种发行版的用户阅读这篇文章,并找到一个途径,把基于 GNU Classpath 的虚拟机和 GCJ 整合支持到已为 Ant 写的封装脚本中。
如果不使用 GCJ 则很可能你将使用一个像 JamVM 那样的虚拟机来运行程序,并使用 Jikes 作为编译器。正确的命令行将具有如下特点:
执行的不是 java 而是 jamvm(或者类似的虚拟机)
通过指定 build.compiler 属性把 jikes 设置为缺省的编译器
属性 jikes.class.path 指向 GNU Classpath 的类文件或者它的归档文件
如果 Ant 的库文件安装在 /usr/share/ant-core/ ,命令行将类似于:
jamvm -classpath /usr/share/ant-core/lib/ant-launcher.jar
-Dant.home=/usr/share/ant-core -Dbuild.compiler=jikes
-Djikes.class.path=/usr/share/classpath/glibj.zip
org.apache.tools.ant.launch.Launcher
当正在使用 GCJ 和 GIJ 时,事情会变得容易一点:
gij -classpath /usr/share/ant-core/lib/ant-launcher.jar
-Dant.home=/usr/share/ant-core -Dbuild.compiler=gcj
org.apache.tools.ant.launch.Launcher
当然,Ant 是本地编译地运行的。参阅 GCC Wiki 的一般说明中关于这一点的说明。当以这种方式运行 Ant 的时候不要忘记使用 gnu.gcj.precompiled.db.path 属性指定 gcj 数据库文件。
声明:本文档的原文著作权属于 Robert Schuster,译文的著作权属于 懒猫 / Stephen Wong,译文的出版权、发行权属于原文作者、译文作者和 LinuxSir.org 社区共同所有。本文首先公开发布于 LinuxSir.org 社区,转载和/或修订请保留著作权信息和本声明条款。原文作者、译文作者、历次修订者及 LinuxSir.org 社区保留一切法律权利。
引自:LinuxSir;,Classpath 用例
如何使用 GCJ 进行二进制兼容性编译
作者:John Gabriele,最后编辑于 2005 年 8 月 19 日。
翻译:懒猫 / Stephen Wong,译于广州,2005 年 9 月 12 日。
适用范围:包括 Fedora Core 在内的所有发行版。
声明:本文档的原文著作权属于 Robert Schuster,译文的著作权属于 懒猫 / Stephen Wong,译文的出版权、发行权属于原文作者、译文作者和 LinuxSir.org 社区共同所有。本文首先公开发布于 LinuxSir.org 社区,转载和/或修订请保留著作权信息和本声明条款。原文作者、译文作者、历次修订者及 LinuxSir.org 社区保留一切法律权利。
关于具有二进制兼容性的应用程序二进制接口 GCJ 的说明
许多 Java 应用程序假定它们将只会由一个 Java 虚拟机解释,而不是由一个像 GCJ 一样与时俱进的编译器进行本地编译的。
这些 Java 应用程序可能假设自己很难在没有经过重大源代码修改情况下进行本地编译的运行环境中运行。显然,在读取字节码流并使用 ClassLoader.defineClass() 方法把它们载入为类的时候,这些应用程序含有它们自己的代码。
可以看到那是一个怎样的问题,因为没有字节码流是用于一个本地编译了的类的。如果本地编译一个使用 defaineClass() 的程序,当你要结束一个类,这个类尝试为可能不曾以字节码存在的别的类读取一个字节码流,这是无法实现的。更进一步,当你本地编译一个类,指望它载入其他类的时候,过去默认的是相当于只把它作为一个本地编译了的类给找出来。
考虑到这个问题,GCJ 4 有一个新的编译模式,称为间接发布,它使用新的具有二进制兼容性的应用程序二进制接口。如果你喜欢,你可以把旧的编译模式当作“直接发布”。详情请参阅 ftp://gcc.gnu.org/pub/gcc/summit/2004/GCJ%20New%20ABI.pdf
简而言之,为了完成它的工作,GCJ 使用 gcj-dbtool(参阅 gcj-dbtool(1)),它产生一个稍后由 GCJ 使用的“.db”文件。想法是,首先使用 -findirect-dispatch 选项对你的 .jar 或 .class 文件(你打算本地编译的应用程序依赖这些文件)进行本地编译。然后使用 gcj-dbtool 把放在一个 .db 数据库文件中的关于那些本地编译了的类的信息存储起来。
在你正在构建的程序的本地编译过程中,当 GCJ 看到 defineClass() 准备被调用的时候,就把已经通过了的字节码跟共享库匹配(使用 .db 文件中的映射表),这些共享库包含你已经本地编译了的类。
从一个类到在 .db 文件中的编译形式的映射,映射为一个类的签名(一个加密校验和)到一个共享库。你可以把它看成像使用一种缓存的 JIT 一样使用 GCJ。
还有其他关于具有二进制兼容性的应用程序二进制接口的重要细节,这些细节用于确定本地代码遵循某些二进制兼容性规则。这对于让本地代码在处理 .class 文件时得以正确执行是个不错的辅助作用。 :)
大部分典型的不使用自定义类加载器(classloaders)的 Java 应用程序不必为此担心。具有二进制兼容性的应用程序二进制接口可以为你正在不依赖 gcj-dbtool 地构建中的代码启用(通过把 -findirect-dispatch 选项传给 gcj )而生成一个二进制程序,它应该不理会对 libgcj 和其他依赖的类库的修改而继续工作(参阅 gcj(1))。另一方面,如果你使用旧的“C++ 应用程序二进制接口”风格(即不使用 -findirect-dispatch 选项)编译一个应用程序,它会在对所依赖类库的公共 API 的发生任何修改时马上中断 —— 就像 C++ 一样。在过去,正如你可以想象的,这是一个重大的限制并且是更广泛应用 GCJ 的一个障碍。
在启用了二进制兼容性的本地编译构建完成以后,对于外界来说,原始的应用程序仍然做着它以前一直在做的事 —— 例如,和往常一样从一些没有本地编译过的 .jar 文件中载入字节码。然而,defineClass() 过去一直在做的调用过程现在已经被改进了;它们首先尝试找出所寻找的类的一个本地编译形式(很可能现在在一个共享库中),如果没有找到本地编译了的类的话,则回去解释 .class 文件(很可能在一个 .jar 文件中)。
如果你希望能从本地代码中调用解释代码,你需要在构建本地代码时使用 -findirect-dispatch 选项。如果不使用间接发布,你的代码将不能够在需要时载入字节码形式的类。如果在代码中你从来没有提及过“new ThatClassIWant();”的话,你可以私下实现这种需求,手动加载类,并使用工厂模式,但那会很快变得单调乏味。
从解释代码中调用本地代码会工作得很好,无需关心应用程序二进制接口。对解释代码使用间接发布在定义上是缺省的。没错,为本地编译而使用 -findirect-dispatch 选项应该是缺省的,但仍未如此 —— 大部分是因为它还没完善。主要存在的障碍如下:
CNI,因为 C++ 编译器仍不明白具有二进制兼容性的应用程序二进制接口
并且当被使用于源代码编译时(即从 .java 到 .class),程序缺陷(bug(s))会影响 -findirect-dispatch 选项。
当然, 目标是只要问题一解决就马上把二进制兼容性作为应用程序二进制接口的缺省的特性。
还有一个来自具有二进制兼容性的应用程序二进制接口的性能打击。Bryce McKinlay 完成的测试表明这与多数应用程序关系不大(<= 10%),但若让它拥有更多数据会比较好。
编译 JAR
重要:有些时候当 -findirect-dispatch 选项直接从 Java 源文件编译为本地代码的时候并不总是可以工作的。那种情况仍未完全实现(在 gcj 4.0.x 时),且不被支持。唯一被支持的带有 -findirect-dispatch 选项的编译方式就是在这里说明了的方式。
第一步是在应用程序中编译所有 JAR 文件。例如:
gcj -shared -findirect-dispatch -Wl,-Bsymbolic -fjni -fPIC myapp.jar -o myapp.jar.so
这将编译所有包含了的类。注意,没有要求设置类路径(classpath);通过具有二进制兼容性的应用程序二进制接口,所有类在运行时被链接。当使用 -findirect-dispatch 选项时当前必须使用 -Wl,-Bsymbolic 选项。
设置数据库
首先创建一个数据库。
gcj-dbtool -n myapp.db
现在,添加所有编译了的 jar 文件到数据库,例如:
gcj-dbtool -a myapp.db myapp.jar myapp.jar.so
运行
现在可以运行你的应用程序了。用 gij 启动它,正如你用 Java 命令启动它一样。然而,还要把 gij 指向你创建的 .db 文件:
gij --cp myapp.jar -Dgnu.gcj.precompiled.db.path=myapp.db org.package.ClassName etc
注意:应用程序的 jar 文件仍然需要在 classpath 中。
便利方法
你有一堆 JAR 文件而又有点懒于手工完成所有的二进制兼容性编译工作吗?这里有一份脚本,遍历进入每个目录,用 GCJ 处理每个 JAR 文件并把新的库添加到数据库文件中。
#!/bin/sh
# ${1} is the name of a GCJ database file
gcj-dbtool -n ${1}
for JAR_FILE in `find -iname "*.jar"`
do
echo "Compiling ${JAR_FILE} to native"
gcj -shared -findirect-dispatch -Wl,-Bsymbolic -fjni -fPIC -o ${JAR_FILE}.so ${JAR_FILE}
gcj-dbtool -a ${1} ${JAR_FILE} ${JAR_FILE}.so
done
以数据库文件的名字调用这份脚本并稍等片刻。如果所有事情都完成了,这将会给你生成所有的本地库以及一个满载的数据库,这些库和数据库现在可以用于使用 GCJ 来运行应用程序了。
范例
对一些现实的例子感兴趣吗?进入 《Classpath 用例》 页面。在那里你可以看到构建本地的 Eclipse 是多么容易。
故障纠纷
1. 我如何区分代码是运行于解释模式还是本地模式呢?
在 gdb 下运行应用程序,在你的代码中设置一个断点。设置在被解释的代码中的断点会完全不能工作(然而,某些断点可能因为 gdb 的其他程序缺陷而工作失效,因此不要依赖它作为一种迹象),并且被解释的代码将以 _Jv_InterpMethod::run 或类似的形式出现在 gdb 的后台跟踪中。
作为一种选择,在 gij 4.0 中有一个奇怪现象可以利用。gij 中的栈跟踪将显示若干行“(Unknown Source)”,就是正在被解释的代码,而显示库名称的就是被编译的代码。
注意:这个“(Unknown Source)”的奇怪现象将在 4.1 中去除。
2. 即使按照上面的说明去做,我的代码仍运行在解释模式中。为什么呢?
在 gcc 4.0 中,.jar.so 文件由于某些原因而载入失败时不会有没有错误报告 —— 它们只是默默地被忽略。这个策略在以后可能会改变。
对于代码不能正确运行于解释模式有两个很大可能的原因:
a)你忘记了使用 JNI(Java 本地接口)的 -fjni 编译器选项编译代码。这种情况下虚拟机将在每当它尝试载入与那些库相关的类的时候,重复尝试载入链接库并失败。(链接库失败是因为它尝试以 CNI 形式链接,那是失败的。)
或者
b)你的映射文件(例如 class.db)过时了,或者没有在 gnu.gcj.compiled.db.path 中。
3. 当我在 gdb 下运行应用程序时,gdb 把我的库一次又一次地载入很多次。
你大概忘记了在编译时指定 -fjni 选项。请参阅上面 2.a 部分的内容。