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 {@link #print(String)} and then
 * {@link #println()}.
 *
 * @param x  The String to be printed.
 */
public void println(String x) {
	synchronized (this) {
		print(x);
		newLine();
	}
}

/**
 * Print a string.  If the argument is null then the string
 * "null" 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
 * {@link #write(int)} method.
 *
 * @param  s   The String 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 cache
	Class.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 code
	setOut(new FooPrintStream(out, true));
	setErr(new FooPrintStream(err, true));
	// Hack code end

	Terminator.setup();
	// initialize the getName cache
	Class.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 + " - [length: " + s.length() + "]");
    }
    
    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 异常:

 

Exception in thread "main" java/lang/NoClassDefFoundError: com.lzy.javaeye.fooattach.FooPrintStream
at java/lang/System.completeInitialization (System.java:117)
at java/lang/Thread. (Thread.java:124)
JVMJ9VM015W Initialization error for library jclscar_23(14): JVMJ9VM009E J9VMDllMain failed

 

          创建 Java 虚拟机失败的原因在于 vm.jar 是被 bootstrap class loader 加载的,其中的 System 类当被 bootstrap class loader 加载时由于找不到依赖的 FooAttach.jar 包,造成抛出 FooPrintStream 类未定义异常,最终导致整个 JVM 初使化失败。

          下面是些关于 Java 类加载方面的科普知识,理解的兄弟请自行快速跳过。

 

          关于 Java 类是如何被加载的,可参见Sun提供的一篇文章:
          How Classes are Found

          还有一篇 IBM 写的关于类加载器相关问题诊断的文篇,也很好的说明了类加载问题:
          Demystifying class loading problems, Part 1: An introduction to class loading and debugging tools

          这里列出 Java 命令行 classpath 相关选项的说明:

 

 

          我们也可以通过 JVM heap dump 分析得到类加载的相关信息。在下面的示例 heap dump 中可以看到 3 个类加载及其加载的类信息:

 

本质上有以下 4 种生成 Java heap dump 的事件:

* A fatal native exception occurs;
* The JVM runs out of heap space;
* A signal is sent to the JVM (for example, if Control-Break is pressed on Windows, or Control-\ on Linux);
* The com.ibm.JVM.Dump.JavaDump() method is called.

 

 

 

          其中显示的 3 个类加载器分别是:

 

  1. The system class loader (sun/misc/Launcher$AppClass loader);
  2. The extension class loader (sun/misc/Launcher$ExtClass loader);
  3. The bootstrap class loader (*System*).


          上面已经说明,发生 NoClassDefFoundError 异常并导致 JVM 初使化失败的原因是由于我们生成的 FooAttach.jar 包没有被 bootstrap class loader 加载造成的。那么我们只需要通过 Java 的 -Xbootclasspath 启动选项将 FooAttach.jar 包含进 bootstrap class loader 加载列表即可:(C:\Program Files\Java_ibm1.5\jre 是我本机环境的 JRE HOME)

 

-Xbootclasspath:”C:\Program Files\Java_ibm1.5\jre\lib\FooAttach.jar;C:\Program Files\Java_ibm1.5\jre\lib\vm.jar;C:\Program Files\Java_ibm1.5\jre\lib\core.jar;C:\Program Files\Java_ibm1.5\jre\lib\charsets.jar;C:\Program Files\Java_ibm1.5\jre\lib\graphics.jar;C:\Program Files\Java_ibm1.5\jre\lib\security.jar;C:\Program Files\Java_ibm1.5\jre\lib\ibmpkcs.jar;C:\Program Files\Java_ibm1.5\jre\lib\ibmorb.jar;C:\Program Files\Java_ibm1.5\jre\lib\ibmcfw.jar;C:\Program Files\Java_ibm1.5\jre\lib\ibmorbapi.jar;C:\Program Files\Java_ibm1.5\jre\lib\ibmjcefw.jar;C:\Program Files\Java_ibm1.5\jre\lib\ibmjgssprovider.jar;C:\Program Files\Java_ibm1.5\jre\lib\ibmjsseprovider2.jar;C:\Program Files\Java_ibm1.5\jre\lib\ibmjaaslm.jar;C:\Program Files\Java_ibm1.5\jre\lib\ibmjaasactivelm.jar;C:\Program Files\Java_ibm1.5\jre\lib\ibmcertpathprovider.jar;C:\Program Files\Java_ibm1.5\jre\lib\server.jar;C:\Program Files\Java_ibm1.5\jre\lib\xml.jar”

 

          此时添加了上述 -Xbootclasspath 选项后再运行测试程序就可以得到正确结果了。尝试加入 System.getProperties().list(System.out); 代码,我们可以看到当前 JVM 系统属性信息:

 

java.assistive => ON
java.runtime.name => Java(TM) 2 Runtime Environment, Standard Edition
ibm.signalhandling.rs => false
sun.boot.library.path => C:\Program Files\Java_ibm1.5\jre\bin
java.vm.version => 2.3
com.ibm.oti.configuration => scar
java.vm.vendor => IBM Corporation
java.vendor.url => http://www.ibm.com/
path.separator => ;
java.vm.name => IBM J9 VM
user.country => CN
java.vm.specification.name => Java Virtual Machine Specification
user.dir => C:\Documents and Settings\Administrator\My Documents\workspace\PrintTest
java.runtime.version => 2.3
java.fullversion => J2RE 1.5.0 IBM J9 2.3 Windows XP x86-32 j9vmwi3223-20060504 (JIT enabled)
J9VM - 20060501_06428_lHdSMR
JIT - 20060428_1800_r8
GC - 20060501_AA
java.awt.graphicsenv => sun.awt.Win32GraphicsEnvironment
os.arch => x86
com.ibm.vm.bitmode => 32
java.io.tmpdir => C:\DOCUME~1\ADMINI~1\LOCALS~1\Temp\
line.separator =>

com.ibm.util.extralibs.properties =>
java.vm.specification.vendor => Sun Microsystems Inc.
user.variant =>
java.awt.fonts =>
os.name => Windows XP
sun.java2d.fontpath =>
sun.jnu.encoding => GB18030
java.library.path => C:\Program Files\Java_ibm1.5\jre\bin;.;C:\Program Files\Java_ibm1.5\jre\bin;C:/Program Files/Java/jre6/bin/client;C:/Program Files/Java/jre6/bin;C:\Program Files\Common Files\NetSarang;C:\Program Files\ruby-1.8.7-p72\bin;C:\oracle\product\10.2.0\client\bin;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;C:\Program Files\TortoiseSVN\bin;C:\Program Files\JProbe 8.1\bin;C:\Program Files\IBM\Rational AppScan\;C:\Program Files\MinGW\bin;C:\Program Files\StormII\Codec;C:\Program Files\StormII;C:\Program Files\UltraEdit\
jxe.current.romimage.version => 9
com.ibm.oti.vm.bootstrap.library.path => C:\Program Files\Java_ibm1.5\jre\bin
com.ibm.cpu.endian => little
java.specification.name => Java Platform API Specification
java.class.version => 49.0
ibm.system.encoding => GB18030
java.util.prefs.PreferencesFactory => java.util.prefs.WindowsPreferencesFactory
invokedviajava =>
os.version => 5.1 build 2600 Service Pack 3
com.ibm.oti.vm.library.version => 23
user.home => C:\Documents and Settings\Administrator
user.timezone =>
java.awt.printerjob => sun.awt.windows.WPrinterJob
file.encoding => GBK
java.specification.version => 1.5
user.name => Administrator
java.class.path => C:\Documents and Settings\Administrator\My Documents\workspace\PrintTest\bin
java.vm.specification.version => 1.0
sun.arch.data.model => 32
java.home => C:\Program Files\Java_ibm1.5\jre
com.ibm.oti.jcl.build => 20060331_1751
user.language => zh
ibm.signalhandling.sigint => true
java.specification.vendor => Sun Microsystems Inc.
os.encoding => GB18030
awt.toolkit => sun.awt.windows.WToolkit
java.vm.info => J2RE 1.5.0 IBM J9 2.3 Windows XP x86-32 j9vmwi3223-20060504 (JIT enabled)
J9VM - 20060501_06428_lHdSMR
JIT - 20060428_1800_r8
GC - 20060501_AA
java.version => 1.5.0
java.ext.dirs => C:\Program Files\Java_ibm1.5\jre\lib\ext
jxe.lowest.romimage.version => 9
sun.boot.class.path => C:\Program Files\Java_ibm1.5\jre\lib\FooAttach.jar;C:\Program Files\Java_ibm1.5\jre\lib\vm.jar;C:\Program Files\Java_ibm1.5\jre\lib\core.jar;C:\Program Files\Java_ibm1.5\jre\lib\charsets.jar;C:\Program Files\Java_ibm1.5\jre\lib\graphics.jar;C:\Program Files\Java_ibm1.5\jre\lib\security.jar;C:\Program Files\Java_ibm1.5\jre\lib\ibmpkcs.jar;C:\Program Files\Java_ibm1.5\jre\lib\ibmorb.jar;C:\Program Files\Java_ibm1.5\jre\lib\ibmcfw.jar;C:\Program Files\Java_ibm1.5\jre\lib\ibmorbapi.jar;C:\Program Files\Java_ibm1.5\jre\lib\ibmjcefw.jar;C:\Program Files\Java_ibm1.5\jre\lib\ibmjgssprovider.jar;C:\Program Files\Java_ibm1.5\jre\lib\ibmjsseprovider2.jar;C:\Program Files\Java_ibm1.5\jre\lib\ibmjaaslm.jar;C:\Program Files\Java_ibm1.5\jre\lib\ibmjaasactivelm.jar;C:\Program Files\Java_ibm1.5\jre\lib\ibmcertpathprovider.jar;C:\Program Files\Java_ibm1.5\jre\lib\server.jar;C:\Program Files\Java_ibm1.5\jre\lib\xml.jar
java.vendor => IBM Corporation
file.separator => \
java.compiler => j9jit23
sun.io.unicode.encoding => UnicodeLittle
ibm.signalhandling.sigchain => true

 

          其中的 sun.boot.class.path 系统属性给出了当前配置的所有 boot class path,这些 jar 包会由 Bootstrap class loader 负责将其中的类装入 JVM。

          截止到此,我们最开始的需求“基本”已经可以达到了,即在不改变应用代码中对 System.out.println 方法调用前提下,通过 hack JRE 实现改变了 System.out.println 实现。但所谓“基本”是由于我们需要配置 -Xbootclasspath 选项来让 bootstrap class loader 加载我们封装的自定义代码 jar 文件。那么是否也可以通过上面类似的 hack 方法来更新 JVM 的默认 boot class path 值呢?经过分析认为不可能,因为 boot class path 的内容,也就是 bootstrap class loader 负责加载的默认 jar 文件集是 JVM 在源代码中/编译前指定的,而在 JRE 层面完成该配置的方法只能通过 Java 进程的 -Xbootclasspath 命令行参数来完成。

          下面的分析过程将针对 JVM 实现展开,目标是以 openjdk 开源项目为基础,源码可以在这里下载:
          openjdk-7-ea-src-b59-14_may_2009

 

          首先,openjdk\hotspot\src\share\vm\runtime\os.cpp 文件 bool os::set_boot_path(char fileSep, char pathSep) 方法指出了 bootstrap class loader 负责加载的默认 jar 文件集:

 

bool os::set_boot_path(char fileSep, char pathSep) {
    const char* home = Arguments::get_java_home();
    int home_len = (int)strlen(home);

    static const char* meta_index_dir_format = "%/lib/";
    static const char* meta_index_format = "%/lib/meta-index";
    char* meta_index = format_boot_path(meta_index_format, home, home_len, fileSep, pathSep);
    if (meta_index == NULL) return false;
    char* meta_index_dir = format_boot_path(meta_index_dir_format, home, home_len, fileSep, pathSep);
    if (meta_index_dir == NULL) return false;
    Arguments::set_meta_index_path(meta_index, meta_index_dir);

    // Any modification to the JAR-file list, for the boot classpath must be
    // aligned with install/install/make/common/Pack.gmk. Note: boot class
    // path class JARs, are stripped for StackMapTable to reduce download size.
    static const char classpath_format[] =
        "%/lib/resources.jar:"
        "%/lib/rt.jar:"
        "%/lib/sunrsasign.jar:"
        "%/lib/jsse.jar:"
        "%/lib/jce.jar:"
        "%/lib/charsets.jar:"
        "%/classes";
    char* sysclasspath = format_boot_path(classpath_format, home, home_len, fileSep, pathSep);
    if (sysclasspath == NULL) return false;
    Arguments::set_sysclasspath(sysclasspath);

    return true;
}

 

          然后,在 openjdk\hotspot\src\share\vm\runtime\arguments.hpp 文件中定义了 Arguments::set_sysclasspath 方法,它将参数给出的 jar 包文件路径存入 Arguments::_sun_boot_class_path 的 SystemProperty 结构中:

 

static void Arguments::set_sysclasspath(char *value) { _sun_boot_class_path->set_value(value); }

static SystemProperty *_sun_boot_class_path;

 

          其次,我们通过 Java 进程的 -Xbootclasspath 命令行参数配置的 boot class jar 包路径会通过 openjdk\hotspot\src\share\vm\prims\jvmtiEnv.cpp 文件中定义的 AddToBootstrapClassLoaderSearch 方法添加到上述的 _sun_boot_class_path SystemProperty 结构中。其中也是通过调用 Arguments::append_sysclasspath 方法来完成的:

 

JvmtiEnv::AddToBootstrapClassLoaderSearch(const char* segment) {
  jvmtiPhase phase = get_phase();
  if (phase == JVMTI_PHASE_ONLOAD) {
    Arguments::append_sysclasspath(segment);
    return JVMTI_ERROR_NONE;
  } else {
    assert(phase == JVMTI_PHASE_LIVE, "sanity check");

    // create the zip entry
    ClassPathZipEntry* zip_entry = ClassLoader::create_class_path_zip_entry(segment);
    if (zip_entry == NULL) {
      return JVMTI_ERROR_ILLEGAL_ARGUMENT;
    }

    // lock the loader
    Thread* thread = Thread::current();
    HandleMark hm;
    Handle loader_lock = Handle(thread, SystemDictionary::system_loader_lock());

    ObjectLocker ol(loader_lock, thread);

    // add the jar file to the bootclasspath
    if (TraceClassLoading) {
      tty->print_cr("[Opened %s]", zip_entry->name());
    }
    ClassLoader::add_to_list(zip_entry);
    return JVMTI_ERROR_NONE;
  }

}

 


	Add To Bootstrap Class Loader Search
	
		This function can be used to cause instrumentation classes to be defined by the
		bootstrap class loader. See
		.
		After the bootstrap
		class loader unsuccessfully searches for a class, the specified platform-dependent
	search path  will be searched as well. Only one segment may be specified in
the . This function may be called multiple times to add multiple segments,
the segments will be searched in the order that this function was called.

In the OnLoad phase the function may be used to specify any platform-dependent search path segment to be searched after the bootstrap class loader unsuccessfully searches for a class. The segment is typically a directory or JAR file.

In the live phase the may be used to specify any platform-dependent path to a JAR file. The agent should take care that the JAR file does not contain any classes or resources other than those to be defined by the bootstrap class loader for the purposes of instrumentation.

The specifies that a subsequent attempt to resolve a symbolic reference that the Java virtual machine has previously unsuccessfully attempted to resolve always fails with the same error that was thrown as a result of the initial resolution attempt. Consequently, if the JAR file contains an entry that corresponds to a class for which the Java virtual machine has unsuccessfully attempted to resolve a reference, then subsequent attempts to resolve that reference will fail with the same error as the initial attempt. new The platform-dependent search path segment, encoded as a modified UTF-8 string. is an invalid path. In the live phase, anything other than an existing JAR file is an invalid path.

 

          Arguments::append_sysclasspath 方法被定义为:

 

static void Arguments::append_sysclasspath(const char *value) {
  _sun_boot_class_path->append_value(value);
}

 

          说明最终 bootstrap class loader 负责加载的 boot class jar 文件路径都被保存在 _sun_boot_class_path 中。

          接下来,openjdk\hotspot\src\share\vm\classfile\classLoader.cpp 文件中定义了 ClassLoader 类,它将 Arguments::_sun_boot_class_path 中指定的 jar 文件路径初使化,并保存在 ClassPathEntry 链表中:

 

void ClassLoader::setup_bootstrap_search_path() {
  assert(_first_entry == NULL, "should not setup bootstrap class search path twice");
  char* sys_class_path = os::strdup(Arguments::get_sysclasspath());
  if (TraceClassLoading && Verbose) {
    tty->print_cr("[Bootstrap loader class path=%s]", sys_class_path);
  }

  int len = (int)strlen(sys_class_path);
  int end = 0;

  // Iterate over class path entries
  for (int start = 0; start < len; start = end) {
    while (sys_class_path[end] && sys_class_path[end] != os::path_separator()[0]) {
      end++;
    }
    char* path = NEW_C_HEAP_ARRAY(char, end-start+1);
    strncpy(path, &sys_class_path[start], end-start);
    path[end-start] = '\0';
    update_class_path_entry_list(path, false);
    FREE_C_HEAP_ARRAY(char, path);
    while (sys_class_path[end] == os::path_separator()[0]) {
      end++;
    }
  }
}

 

ClassPathEntry* ClassLoader::_first_entry = NULL;
ClassPathEntry* ClassLoader::_last_entry = NULL;

 

          最后,最终的 class 加载动作是由 ClassLoader::load_classfile 方法完成的,位于 openjdk\hotspot\src\share\vm\classfile\classLoader.cpp 文件:

 

instanceKlassHandle ClassLoader::load_classfile(symbolHandle h_name, TRAPS) {
  VTuneClassLoadMarker clm;
  ResourceMark rm(THREAD);
  EventMark m("loading class " INTPTR_FORMAT, (address)h_name());
  ThreadProfilerMark tpm(ThreadProfilerMark::classLoaderRegion);

  stringStream st;
  // st.print() uses too much stack space while handling a StackOverflowError
  // st.print("%s.class", h_name->as_utf8());
  st.print_raw(h_name->as_utf8());
  st.print_raw(".class");
  char* name = st.as_string();

  // Lookup stream for parsing .class file
  ClassFileStream* stream = NULL;
  int classpath_index = 0;
  {
    PerfTraceTime vmtimer(perf_accumulated_time());
    ClassPathEntry* e = _first_entry;
    while (e != NULL) {
      stream = e->open_stream(name);
      if (stream != NULL) {
        break;
      }
      e = e->next();
      ++classpath_index;
    }
  }

  instanceKlassHandle h(THREAD, klassOop(NULL));
  if (stream != NULL) {

    // class file found, parse it
    ClassFileParser parser(stream);
    Handle class_loader;
    Handle protection_domain;
    symbolHandle parsed_name;
    instanceKlassHandle result = parser.parseClassFile(h_name,
                                                       class_loader,
                                                       protection_domain,
                                                       parsed_name,
                                                       CHECK_(h));

    // add to package table
    if (add_package(name, classpath_index, THREAD)) {
      h = result;
    }
  }

  return h;
}

 

          从上面的分析可以看出,若需要改变 JRE 默认 boot classpath 配置,加入自定的 jar 文件,则需要更改 openjdk\hotspot\src\share\vm\runtime\os.cpp 文件,并重新编译 jvm,而且如果 boot classpath 包含的默认 jar 文件过多、容量增大,还会影响JVM的加/下载、启动时间。

 

          希望通过本次对 JRE 无奈的 hack 过程所做的浅显研究,能够在后续有机会做更深入的学习、研究、收获。

 

作者:lzy.je
出处:http://lzy.iteye.com
本文版权归作者所有,只允许以摘要和完整全文两种形式转载,不允许对文字进行裁剪。未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。


// 2009.06.14 01:10 添加

          推荐一篇比较详细介绍 bootstrap class loader 和 boot classpath 和相关内容的文章,篇幅不长值得快速一览。参见附件 BootClasspath.pdf 。

 

// 2009.08.18 16:04 添加

 

          在编译 Hack/替换的代码时,一定要注意编译器(javac)版本代码版本兼容性生成类文件的虚拟机版本 3 件事。否则编译虽然能够成功,但在 Hack/替换后会出现“java.lang.UnsupportedClassVersionError”异常。在 IBM JDK 提供的 javac 编译器上,可以通过“-source <版本>”配置代码版本兼容性,通过“-target <版本>”生成指定虚拟机版本的类文件。

 

你可能感兴趣的:(Programing)