lzy.je 发表于 2013-2-5 02:43:19

JRE Hack 浅度研究

          很多时候对应用软件代码层面的性能调优受到很多主观、客观条件的影响,本文所述的 JRE Hack就是在这样的背景下展开的。当前的应用中需要记录大量的调试信息,程序直接采用了 System.out.println方法来将这些内容输出到日志文件中(WebSphere 的 outputStreamRedirect 默认配置到了${SERVER_LOG_ROOT}/SystemOut.log 文件),因此 System.out.println方法大量遍布在整个程序代码中。而由于该方法是线程安全的,即被同步(临界区保护)形成了串行执行,结果造成整体业务处理串行点很多,性能比较低下。解决方法也很简单,就是避免使用 System.out.println 方法来进行日志输出减少串行瓶颈,可换用支持异步输出的 log4j组件来实现日志功能。本来这个事情通过代码批量替换就可以完成,但实际诸多原因造成不允许修改应用代码,那么该怎么解决这个问题呢?

          这样的前提就不得以造就这样一个方法,通过把 JRE 中提供的 System.out.println 方法实现代码替换成使用log4j 来进行 Log 输出的实现代码。这里并不打算评论该方法的好坏(实际上 JRE 中使用了 PrintStream类的地方不少,该方法影响面较大),本文姑且作为对 JRE 内部粗浅研究的记录吧。

          应用使用的是 IBM JRE 5.0,JRE 本身是开源的。用 DJ Java Decompiler工具也可以反编译得到相关 Java 类源码(System.class、System$1.class 位于 jre/lib/vm.jar中)。找到 java/lang/System.java 文件,可以看到 stdin、stdout、stderr 都是 PrintStream类实例:
 
// Typically, these are connected to the shell which// ran the Java program./** * Default input stream */public static final InputStream in;/** * Default output stream */public static final PrintStream out;/** * Default error output stream */public static final PrintStream err; 
          在 java/io/PrintStream.java(PrintStream.class 位于 jre/lib/core.jar 中)文件中可以看到 println(String) 等相关方法定义如下:
 
/** * Print a String and then terminate the line.This method behaves as * though it invokes <code>{@link #print(String)}</code> and then * <code>{@link #println()}</code>. * * @param xThe <code>String</code> to be printed. */public void println(String x) {synchronized (this) {print(x);newLine();}}/** * Print a string.If the argument is <code>null</code> then the string * <code>"null"</code> is printed.Otherwise, the string's characters are * converted into bytes according to the platform's default character * encoding, and these bytes are written in exactly the manner of the * <code>{@link #write(int)}</code> method. * * @params   The <code>String</code> to be printed */public void print(String s) {if (s == null) {s = "null";}write(s);}private void write(String s) {try {synchronized (this) {ensureOpen();textOut.write(s);textOut.flushBuffer();charOut.flushBuffer();if (autoFlush && (s.indexOf('\n') >= 0))out.flush();}}catch (InterruptedIOException x) {Thread.currentThread().interrupt();}catch (IOException x) {trouble = true;}} 
          此时,如果 hack 代码很简单的话,那么就可以将其直接写入到 PrintStream.java相关方法中。但这样一来,加入的 hack 代码会与 JRE 中这些 Java 标准类库代码混在一起。更多时候我们为了便于维护,有必要将这些hack 代码放在另外的独立 jar 文件中,这会引出一些问题,下面慢慢道来。

          让我们先再看看 System 类的 completeInitialization 方法代码,注意 Sun JRE 中 System 类实现并没有该方法:
 
static void completeInitialization() {setIn(com.ibm.JVM.io.ConsoleInputStream.localize(new BufferedInputStream(new FileInputStream(FileDescriptor.in))));Terminator.setup();// initialize the getName cacheClass.setClassNameMap();com.ibm.misc.SystemIntialization.lastChanceHook();}           对于上面提出的需求,可以在这里直接加入我们 hack 的 PrintStream 实现:
 
import com.lzy.javaeye.fooattach.FooPrintStream;... ...static void completeInitialization() {setIn(com.ibm.JVM.io.ConsoleInputStream.localize(new BufferedInputStream(new FileInputStream(FileDescriptor.in))));// Hack codesetOut(new FooPrintStream(out, true));setErr(new FooPrintStream(err, true));// Hack code endTerminator.setup();// initialize the getName cacheClass.setClassNameMap();com.ibm.misc.SystemIntialization.lastChanceHook();}... ... 
          这里使用的 com.lzy.javaeye.fooattach.FooPrintStream 类被单独封装在了 FooPrintStream.jar 文件中,实现代码如下:
 
package com.lzy.javaeye.fooattach;import java.io.PrintStream;import java.io.OutputStream;public final class FooPrintStream extends PrintStream {    public FooPrintStream(OutputStream outputstream, boolean flag)    {      super(outputstream, flag);                ps = new PrintStream(outputstream, flag);    }    public void println(String s)    {    ps.println(s + " - ");    }      private PrintStream ps;} 
          这里仅用于说明,为了简便其中的 println 方法并没有直正调用 log4j 做 Log 输出,而是简单的在输出 String 加上了长度信息。
 
          接下来把 FooAttach.jar 文件放入 JRE HOME 的 lib 目录中,该目录中有 vm.jar 和core.jar 等一系列“核心”的 JRE class jar 文件。下面我们就可以将这个 hack 版的 System.java文件编译了:
 
javac –cp %CLASSPATH%\FooAttach.jar System.java 
          此时会生成 System.class 和System$1.class(System.AccessController.doPrivileged方法使用的匿名类)两个文件,将把这个编译好的“新” System 类放入到 jar/lib/vm.jar/java/lang目录中。这里随便写个测试程序:
 
public class PrintTest {public static void main(String[] args) {System.out.println("test11");}} 
          心急的兄弟如果现在运行它,那会得到 NoClassDefFoundError 异常:
 
<div class="quote_div">Exception in thread "main" java/lang/NoClassDefFoundError: com.lzy.javaeye.fooattach.FooPrintStream
at java/lang/System.completeInitialization (System.java:117)
at java/lang/Thread.<init> (Thread.java:124)
JVMJ9VM015W Initialization error for library jclscar_23(14): JVMJ9VM009E J9VMDllMain failed
页: [1]
查看完整版本: JRE Hack 浅度研究