调用InstanceKlass类的rewrite_class()函数重写字节码,实现如下:
// Rewrite the byte codes of all of the methods of a class. // The rewriter must be called exactly once. Rewriting must happen after // verification but before the first method of the class is executed. void InstanceKlass::rewrite_class(TRAPS) { assert(is_loaded(), "must be loaded"); instanceKlassHandle this_oop(THREAD, this); // ... Rewriter::rewrite(this_oop, CHECK); this_oop->set_rewritten(); }
如上方法在类验证之后,方法执行之前进行字节码重写,并且保证任何一个类对于此方法都只会被调用一次。调用Rewriter::rewrite()函数进行字节码重写,重写完成后设置这个类已经被重写,这样就不会重复重写这个类了。
调用的Rewriter::rewrite()函数的实现如下:
void Rewriter::rewrite(instanceKlassHandle klass, TRAPS) { ResourceMark rm(THREAD); Array<Method*>* mds = klass->methods(); ConstantPool* cp = klass->constants(); Rewriter rw(klass, cp, mds, CHECK); }
Rewriter类的构造函数的实现如下:
// bcp是bytecode pointer,是某条字节码在内存中的地址;bci是bytecode index,是某条字节码相对该方法的字节码起始位置的偏移量 Rewriter::Rewriter(instanceKlassHandle klass, constantPoolHandle cpool, Array<Method*>* methods, TRAPS) : _klass(klass),_pool(cpool),_methods(methods) { assert(_pool->cache() == NULL, "constant pool cache must not be set yet"); // 第1部分:determine index maps for Method* rewriting compute_index_maps(); // 第2部分 // RegisterFinalizersAtInit命令解释:先执行new分配好对象空间,然后再执行invokespecial调用构造函数, // jvm里其实可以让用户选择在这两个时机中的任意一个将当前对象传递给Finalizer.register方法来注册到Finalizer对象链里, // 这个选择依赖于RegisterFinalizersAtInit这个vm参数是否被设置,默认值为true,也就是在调用构造函数返回之前调用 // Finalizer.register方法,如果通过-XX:-RegisterFinalizersAtInit关闭了该参数,那将在对象空间分配好之后就将这个对象注册进去。 if (RegisterFinalizersAtInit && _klass->name() == vmSymbols::java_lang_Object()) { bool did_rewrite = false; int i = _methods->length(); while (i-- > 0) { Method* method = _methods->at(i); if (method->intrinsic_id() == vmIntrinsics::_Object_init) { // rewrite the return bytecodes of Object.<init> to register the // object for finalization if needed. methodHandle m(THREAD, method); rewrite_Object_init(m, CHECK); did_rewrite = true; break; } } assert(did_rewrite, "must find Object::<init> to rewrite it"); } // 第3部分 // rewrite methods, in two passes int len = _methods->length(); bool invokespecial_error = false; for (int i = len-1; i >= 0; i--) { Method* method = _methods->at(i); scan_method(method, false, &invokespecial_error); // ... } // ... // 第4部分 // allocate constant pool cache, now that we've seen all the bytecodes make_constant_pool_cache(THREAD); // ... }
这个方法实现的逻辑比较多,我们分4个部分来介绍。
1、compute_index_maps()函数
首先调用compute_index_maps()函数,如下:
// Computes a CPC map (new_index -> original_index) for constant pool entries // that are referred to by the interpreter at runtime via the constant pool cache. // Also computes a CP map (original_index -> new_index). // Marks entries in CP which require additional processing. void Rewriter::compute_index_maps() { const int length = _pool->length(); init_maps(length); bool saw_mh_symbol = false; // 通过循环一次性将常量池中的项处理完毕 for (int i = 0; i < length; i++) { constantTag ct = _pool->tag_at(i); int tag = ct.value(); switch (tag) { case JVM_CONSTANT_InterfaceMethodref: case JVM_CONSTANT_Fieldref : // fall through case JVM_CONSTANT_Methodref : // fall through add_cp_cache_entry(i); break; case JVM_CONSTANT_String: case JVM_CONSTANT_MethodHandle : // fall through case JVM_CONSTANT_MethodType : // fall through add_resolved_references_entry(i); break; // ... } } // Record limits of resolved reference map for constant pool cache indices record_map_limits(); // ... }
调用的init_maps()函数会初始化Rewriter类中如下的一些重要变量:
// _cp_map与_cap_cache_map是一对 intArray _cp_map; // for Methodref, Fieldref,InterfaceMethodref and InvokeDynamic intStack _cp_cache_map; intArray _reference_map; // maps from cp index to resolved_refs index (or -1) intStack _resolved_references_map; // for strings, methodHandle, methodType int _resolved_reference_limit; int _first_iteration_cp_cache_limit;
init_maps()函数的实现如下:
void init_maps(int length) { _cp_map.initialize(length, -1); // _cp_map是整数类型数组 // Choose an initial value large enough that we don't get frequent calls to grow(). _cp_cache_map.initialize(length/2); // _cp_cache_map是整数类型栈 // Also cache resolved objects, in another different cache. _reference_map.initialize(length, -1); // _reference_map是整数类型数组 _resolved_references_map.initialize(length/2); // _resolved_references_map是整数类型的栈 _resolved_reference_limit = -1; _first_iteration_cp_cache_limit = -1; }
首先来认识一下JVM_CONSTANT_InterfaceMethodref、JVM_CONSTANT_Fieldref与JVM_CONSTANT_Methodref。Java虚拟机规范规定的格式如下:
CONSTANT_Fieldref_info { u1 tag; u2 class_index; u2 name_and_type_index; } CONSTANT_Methodref_info { u1 tag; u2 class_index; u2 name_and_type_index; } CONSTANT_InterfaceMethodref_info { u1 tag; u2 class_index; u2 name_and_type_index; }
举个例子如下:
interface Computable { void calculate(); } class Computer implements Computable { public void calculate() { } } public class Test { public static Computable x1 = new Computer(); public static Computer x2 = new Computer(); public static String x3 = "mz"; public static void main(String[] args) { x1.calculate(); x2.calculate(); System.out.println(x3); } }
反编译后如下:
public class com.test.Test // ... Constant pool: #1 = Class #2 // com/test/Test #2 = Utf8 com/test/Test #3 = Class #4 // java/lang/Object #4 = Utf8 java/lang/Object #5 = Utf8 x1 #6 = Utf8 Lcom/test/Computable; #7 = Utf8 x2 #8 = Utf8 Lcom/test/Computer; #9 = Utf8 x3 #10 = Utf8 Ljava/lang/String; #11 = Utf8 <clinit> #12 = Utf8 ()V #13 = Utf8 Code #14 = Class #15 // com/test/Computer #15 = Utf8 com/test/Computer #16 = Methodref #14.#17 // com/test/Computer."<init>":()V #17 = NameAndType #18:#12 // "<init>":()V #18 = Utf8 <init> #19 = Fieldref #1.#20 // com/test/Test.x1:Lcom/test/Computable; #20 = NameAndType #5:#6 // x1:Lcom/test/Computable; #21 = Fieldref #1.#22 // com/test/Test.x2:Lcom/test/Computer; #22 = NameAndType #7:#8 // x2:Lcom/test/Computer; #23 = String #24 // mz #24 = Utf8 mz #25 = Fieldref #1.#26 // com/test/Test.x3:Ljava/lang/String; #26 = NameAndType #9:#10 // x3:Ljava/lang/String; #27 = Utf8 LineNumberTable #28 = Utf8 LocalVariableTable #29 = Methodref #3.#17 // java/lang/Object."<init>":()V #30 = Utf8 this #31 = Utf8 Lcom/test/Test; #32 = Utf8 main #33 = Utf8 ([Ljava/lang/String;)V #34 = InterfaceMethodref #35.#37 // com/test/Computable.calculate:()V #35 = Class #36 // com/test/Computable #36 = Utf8 com/test/Computable #37 = NameAndType #38:#12 // calculate:()V #38 = Utf8 calculate #39 = Methodref #14.#37 // com/test/Computer.calculate:()V #40 = Fieldref #41.#43 // java/lang/System.out:Ljava/io/PrintStream; #41 = Class #42 // java/lang/System #42 = Utf8 java/lang/System #43 = NameAndType #44:#45 // out:Ljava/io/PrintStream; #44 = Utf8 out #45 = Utf8 Ljava/io/PrintStream; #46 = Methodref #47.#49 // java/io/PrintStream.println:(Ljava/lang/String;)V #47 = Class #48 // java/io/PrintStream #48 = Utf8 java/io/PrintStream #49 = NameAndType #50:#51 // println:(Ljava/lang/String;)V #50 = Utf8 println #51 = Utf8 (Ljava/lang/String;)V #52 = Utf8 args #53 = Utf8 [Ljava/lang/String; #54 = Utf8 SourceFile #55 = Utf8 Test.java { public static com.test.Computable x1; descriptor: Lcom/test/Computable; flags: ACC_PUBLIC, ACC_STATIC public static com.test.Computer x2; descriptor: Lcom/test/Computer; flags: ACC_PUBLIC, ACC_STATIC public static java.lang.String x3; descriptor: Ljava/lang/String; flags: ACC_PUBLIC, ACC_STATIC static {}; descriptor: ()V flags: ACC_STATIC Code: stack=2, locals=0, args_size=0 0: new #14 // class com/test/Computer 3: dup 4: invokespecial #16 // Method com/test/Computer."<init>":()V 7: putstatic #19 // Field x1:Lcom/test/Computable; 10: new #14 // class com/test/Computer 13: dup 14: invokespecial #16 // Method com/test/Computer."<init>":()V 17: putstatic #21 // Field x2:Lcom/test/Computer; 20: ldc #23 // String mz 22: putstatic #25 // Field x3:Ljava/lang/String; 25: return public com.test.Test(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #29 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=1, args_size=1 0: getstatic #19 // Field x1:Lcom/test/Computable; 3: invokeinterface #34, 1 // InterfaceMethod com/test/Computable.calculate:()V 8: getstatic #21 // Field x2:Lcom/test/Computer; 11: invokevirtual #39 // Method com/test/Computer.calculate:()V 14: getstatic #40 // Field java/lang/System.out:Ljava/io/PrintStream; 17: getstatic #25 // Field x3:Ljava/lang/String; 20: invokevirtual #46 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 23: return }
对于这3个常量池项来说,在Rewriter::compute_index_maps()方法中调用add_cp_cache_entry()方法进行处理。其实方法还会处理JVM_CONSTANT_MethodHandle与JVM_CONSTANT_MethodType,这2个常量池项是为了让Java语言支持动态语言特性而在Java 7 版本中新增的常量池项,只会在极其特别的情况能用到它,在Class文件中几乎不会生成这三个常量池项,暂时不介绍。
在Rewriter::compute_index_maps()函数中调用的add_cp_cache_entry()函数的实现如下:
int add_cp_cache_entry(int cp_index) { int cache_index = add_map_entry(cp_index, &_cp_map, &_cp_cache_map); return cache_index; } int add_map_entry(int cp_index, intArray* cp_map, intStack* cp_cache_map) { int cache_index = cp_cache_map->append(cp_index); // cp_cache_map是整数类型的栈 cp_map->at_put(cp_index, cache_index); //cp_map是整数类型的数组 return cache_index; }
通过cp_cache_map和cp_map建立了cp_index与 cache_index的对应关系,这在后面中会有重要应用。
在Rewriter::compute_index_maps()函数中调用的add_resolved_references_entry()函数的实现与如上方法的实现类似,如下:
// add a new entry to the resolved_references map int add_resolved_references_entry(int cp_index) { int ref_index = add_map_entry(cp_index, &_reference_map, &_resolved_references_map); assert(cp_entry_to_resolved_references(cp_index) == ref_index, ""); return ref_index; } int add_map_entry(int cp_index, intArray* cp_map, intStack* cp_cache_map) { assert(cp_map->at(cp_index) == -1, "not twice on same cp_index"); int cache_index = cp_cache_map->append(cp_index); cp_map->at_put(cp_index, cache_index); return cache_index; }
在Rewriter::compute_index_maps()函数中调用的record_map_limits()函数如下:
void record_map_limits() { // Record initial size of the two arrays generated for the CP cache // relative to walking the constant pool. _first_iteration_cp_cache_limit = _cp_cache_map.length(); // _cp_cache_map是整数类型的栈 _resolved_reference_limit = _resolved_references_map.length(); // _resolved_references_map是整数类型的数组 }
更新变量_resolved_reference_limit与_first_iteration_cp_cache_limit的值,现在还看不到对这些变量的应用,后面就会使用这些变量的值。
2、Rewriter::rewrite_Object_init()函数
// The new finalization semantics says that registration of // finalizable objects must be performed on successful return from the // Object.<init> constructor. We could implement this trivially if // <init> were never rewritten but since JVMTI allows this to occur, a // more complicated solution is required. A special return bytecode // is used only by Object.<init> to signal the finalization // registration point. Additionally local 0 must be preserved so it's // available to pass to the registration function. For simplicty we // require that local 0 is never overwritten so it's available as an // argument for registration. void Rewriter::rewrite_Object_init(methodHandle method, TRAPS) { RawBytecodeStream bcs(method); while (!bcs.is_last_bytecode()) { Bytecodes::Code opcode = bcs.raw_next(); switch (opcode) { case Bytecodes::_return: *bcs.bcp() = Bytecodes::_return_register_finalizer; break; // ... } } }
finalize()方法是Object类中的方法,当垃圾回收器将要回收对象所占内存之前被调用,即当一个对象被虚拟机宣告死亡时会先调用它finalize()方法,让此对象处理它生前的最后事情(这个对象可以趁这个时机挣脱死亡的命运)。所以在类的连接阶段,如果判断到方法有重写了finalize()方法,在new一个新的Java类对象的时候首先标记它是一个含有finalize()方法的类,然后调用Object类的空构造函数,HotSpot在连接Object类的时候,将return指令替换为_return_register_finalizer指令,该指令并不是标准的字节码指令,是HotSpot扩展的指令,这样在处理该指令时调用Finalizer.register()方法,这个方法会将对象包裹成Finalizer对象加入Finalizer链,这样就可以通过Finalizer链来处理这些有finalize()方法的对象了。
3、Rewriter::scan_method()函数
有些字节码指令的操作数在Class文件里跟在运行时看起来不同,因为HotSpot 在加连接类的时候会对字节码进行重写, 把某些指令的操作数从常量池下标(就是之前接触到的cp_index)改写为常量池缓存下标(就是之前介绍的cp_cache_index)。因为这些指令所需要引用的信息无法使用一个constant pool entry slot来表示,需要使用一个更大的数据结构表示常量池项的内容。在Rewriter::scan_method()中就对部分字节码进行了重写,如下:
// Rewrites a method given the index_map information void Rewriter::scan_method(Method* method, bool reverse, bool* invokespecial_error) { int nof_jsrs = 0; bool has_monitor_bytecodes = false; ////////////////////////////////////////////////////////////////////// { // We cannot tolerate a GC in this block, because we've // cached the bytecodes in 'code_base'. If the Method* // moves, the bytecodes will also move. No_Safepoint_Verifier nsv; Bytecodes::Code c; // Bytecodes and their length const address code_base = method->code_base(); const int code_length = method->code_size(); int bc_length; for (int bci = 0; bci < code_length; bci += bc_length) { address bcp = code_base + bci; int prefix_length = 0; c = (Bytecodes::Code)(*bcp); // Since we have the code, see if we can get the length // directly. Some more complicated bytecodes will report // a length of zero, meaning we need to make another method // call to calculate the length. bc_length = Bytecodes::length_for(c); if (bc_length == 0) { bc_length = Bytecodes::length_at(method, bcp); // length_at will put us at the bytecode after the one modified // by 'wide'. We don't currently examine any of the bytecodes // modified by wide, but in case we do in the future... if (c == Bytecodes::_wide) { prefix_length = 1; c = (Bytecodes::Code)bcp[1]; } } switch (c) { case Bytecodes::_lookupswitch : { #ifndef CC_INTERP Bytecode_lookupswitch bc(method, bcp); (*bcp) = ( bc.number_of_pairs() < BinarySwitchThreshold ? Bytecodes::_fast_linearswitch : Bytecodes::_fast_binaryswitch ); #endif break; } case Bytecodes::_fast_linearswitch: case Bytecodes::_fast_binaryswitch: { #ifndef CC_INTERP (*bcp) = Bytecodes::_lookupswitch; #endif break; } case Bytecodes::_invokespecial : { rewrite_invokespecial(bcp, prefix_length+1, reverse, invokespecial_error); break; } case Bytecodes::_getstatic : // fall through case Bytecodes::_putstatic : // fall through case Bytecodes::_getfield : // fall through case Bytecodes::_putfield : // fall through case Bytecodes::_invokevirtual : // fall through case Bytecodes::_invokestatic : case Bytecodes::_invokeinterface: case Bytecodes::_invokehandle : // if reverse=true rewrite_member_reference(bcp, prefix_length+1, reverse); break; // ... case Bytecodes::_ldc: case Bytecodes::_fast_aldc: // if reverse=true maybe_rewrite_ldc(bcp, prefix_length+1, false, reverse); break; case Bytecodes::_ldc_w: case Bytecodes::_fast_aldc_w: // if reverse=true maybe_rewrite_ldc(bcp, prefix_length+1, true, reverse); break; // ... case Bytecodes::_monitorenter : // fall through case Bytecodes::_monitorexit : has_monitor_bytecodes = true; break; } } } ////////////////////////////////////////////////////////////////////// // Update access flags if (has_monitor_bytecodes) { method->set_has_monitor_bytecodes(); } // ... }
如上代码的逻辑非常清晰,方法会对字节码指令进行改写,需要改写的指令以及改写时调用的方法都很清楚,不做过多介绍。
(1)Rewriter::rewrite_invokespecial()
// invokerspecial是作为对private和构造方法的调用,绕过了virtual dispatch; // If the constant pool entry for invokespecial is InterfaceMethodref, // we need to add a separate cpCache entry for its resolution, because it is // different than the resolution for invokeinterface with InterfaceMethodref. // These cannot share cpCache entries. It's unclear(不确定的) if all invokespecial to // InterfaceMethodrefs would resolve to the same thing so a new cpCache entry // is created for each one. This was added with lambda. void Rewriter::rewrite_invokespecial(address bcp, int offset, bool reverse, bool* invokespecial_error) { address p = bcp + offset; if (!reverse) { // 获取常量池中要调用方法的索引 int cp_index = Bytes::get_Java_u2(p); if (_pool->tag_at(cp_index).is_interface_method()) { int cache_index = add_invokespecial_cp_cache_entry(cp_index); if (cache_index != (int)(jushort) cache_index) { *invokespecial_error = true; } Bytes::put_native_u2(p, cache_index); } else { rewrite_member_reference(bcp, offset, reverse); } } // ... }
当reverse为true时,表示出错,需要逆写回去,也就是将字节码中已经替换为cache_index的值替换回原来的cp_index。这里假设不会出错,所以省略了逆写回去的相关代码实现,后续类似的方法也会省略。
调用add_invokespecial_cp_cache_entry()方法根据cp_index来获取cache_index,方法的实现如下:
// add a new CP cache entry beyond the normal cache for the special case of // invokespecial with InterfaceMethodref as cpool operand. int add_invokespecial_cp_cache_entry(int cp_index) { assert(_first_iteration_cp_cache_limit >= 0, "add these special cache entries after first iteration"); // Don't add InterfaceMethodref if it already exists at the end. for (int i = _first_iteration_cp_cache_limit; i < _cp_cache_map.length(); i++) { if (cp_cache_entry_pool_index(i) == cp_index) { return i; } } int cache_index = _cp_cache_map.append(cp_index); assert(cache_index >= _first_iteration_cp_cache_limit, ""); // do not update _cp_map, since the mapping is one-to-many assert(cp_cache_entry_pool_index(cache_index) == cp_index, ""); return cache_index; }
这会利用之前的_cp_map与_cp_cache_map变量等信息来获取对应cp_index的cache_index。
(2)Rewriter::rewrite_member_reference()
调用的rewrite_member_reference()函数的实现如下:
// Rewrite a classfile-order CP index into a native-order CPC index. void Rewriter::rewrite_member_reference(address bcp, int offset, bool reverse) { address p = bcp + offset; if (!reverse) { int cp_index = Bytes::get_Java_u2(p); int cache_index = cp_entry_to_cp_cache(cp_index); Bytes::put_native_u2(p, cache_index); } // ... }
调用的cp_entry_to_cp_cache()方法的实现如下:
int cp_entry_to_cp_cache(int i) { assert(has_cp_cache(i), "oob"); return _cp_map[i]; }
方法的实现比较简单,这里不做过多介绍。
(3)Rewriter::maybe_rewrite_ldc()
// ldc指令从常量池中取值然后压入栈中 // Rewrite some ldc bytecodes to _fast_aldc void Rewriter::maybe_rewrite_ldc(address bcp, int offset, bool is_wide,bool reverse) { if (!reverse) { assert((*bcp) == (is_wide ? Bytecodes::_ldc_w : Bytecodes::_ldc), "not ldc bytecode"); address p = bcp + offset; int cp_index = is_wide ? Bytes::get_Java_u2(p) : (u1)(*p); constantTag tag = _pool->tag_at(cp_index).value(); if (tag.is_method_handle() || tag.is_method_type() || tag.is_string()) { int ref_index = cp_entry_to_resolved_references(cp_index); if (is_wide) { (*bcp) = Bytecodes::_fast_aldc_w; assert(ref_index == (u2)ref_index, "index overflow"); Bytes::put_native_u2(p, ref_index); } else { (*bcp) = Bytecodes::_fast_aldc; assert(ref_index == (u1)ref_index, "index overflow"); (*p) = (u1)ref_index; } } } // ... }
方法不但会替换指令,还会替换指令的常量池索引。
调用的cp_entry_to_resolved_references()函数的实现如下:
int cp_entry_to_resolved_references(int cp_index) const { assert(has_entry_in_resolved_references(cp_index), "oob"); return _reference_map[cp_index]; }
方法的实现比较简单,这里不做过多介绍。
这一篇介绍了对方法字节码的重写,可能会重写字节码,也可能会重写字节码中指向常量池的索引,但是重写后的字节码到底如何处理,以及新的常量池索引到底索引到了什么信息都还没有介绍,后面会详细介绍。
相关文章的链接如下:
1、在Ubuntu 16.04上编译OpenJDK8的源代码
13、类加载器
14、类的双亲委派机制
15、核心类的预装载
16、Java主类的装载
17、触发类的装载
18、类文件介绍
19、文件流
20、解析Class文件
21、常量池解析(1)
22、常量池解析(2)
23、字段解析(1)
24、字段解析之伪共享(2)
25、字段解析(3)
28、方法解析
29、klassVtable与klassItable类的介绍
30、计算vtable的大小
31、计算itable的大小
32、解析Class文件之创建InstanceKlass对象
33、字段解析之字段注入
34、类的连接
35、类的连接之验证
作者持续维护的个人博客 classloading.com。
关注公众号,有HotSpot源码剖析系列文章!