[讨论] [被我移过来了]修改java.lang.String类时出错?
fp1203
2011-10-12
见http://www.iteye.com/problems/73052
撒加一语点睛,我们大家讨论一下具体的原因吧! 另外,hotspot抛出这个异常的确是正常的,但很搞,不了解hotspot实现,的确不知道 为什么会有这个异常出现: Caused by: java.lang.IllegalArgumentException: name can't be empty at java.security.BasicPermission.init(Unknown Source) at java.security.BasicPermission.<init>(Unknown Source) at java.lang.RuntimePermission.<init>(Unknown Source) at java.lang.Thread.<clinit>(Unknown Source) 下面说说我的理解: 1) 创建虚拟机的过程 http://hg.openjdk.java.net/jdk6/jdk6/hotspot/file/dc40301aed45/src/share/vm/runtime/thread.cpp 3092行 jint status = init_globals(); 2) 这是全局初始化的过程 http://hg.openjdk.java.net/jdk6/jdk6/hotspot/file/dc40301aed45/src/share/vm/runtime/init.cpp 98行 jint status = universe_init(); 3) 计算硬编码偏移 http://hg.openjdk.java.net/jdk6/jdk6/hotspot/file/dc40301aed45/src/share/vm/memory/universe.cpp 758行 JavaClasses::compute_hard_coded_offsets(); 4) 具体计算过程 http://hg.openjdk.java.net/jdk6/jdk6/hotspot/file/dc40301aed45/src/share/vm/classfile/javaClasses.cpp 2801行 注意到: const int x = heapOopSize; const int header = instanceOopDesc::base_offset_in_bytes(); java_lang_String::value_offset = java_lang_String::hc_value_offset * x + header; java_lang_String::offset_offset = java_lang_String::hc_offset_offset * x + header; java_lang_String::count_offset = java_lang_String::offset_offset + sizeof (jint); java_lang_String::hash_offset = java_lang_String::count_offset + sizeof (jint); 见: http://hg.openjdk.java.net/jdk6/jdk6/hotspot/file/dc40301aed45/src/share/vm/classfile/javaClasses.hpp 56行: hc_value_offset = 0, hc_offset_offset = 1 http://hg.openjdk.java.net/jdk6/jdk6/hotspot/file/dc40301aed45/src/share/vm/utilities/globalDefinitions.cpp 161行: if (UseCompressedOops) { heapOopSize = jintSize; } else { heapOopSize = oopSize; } 而: http://hg.openjdk.java.net/jdk6/jdk6/hotspot/file/dc40301aed45/src/share/vm/utilities/globalDefinitions.hpp 81行:const int jintSize = sizeof(jint); 77行:const int oopSize = sizeof(char*); 因此可以理解为:heapOopSize可以理解为"堆中oop的大小" 因此可以断定:java.lang.String对象
通过硬编码偏移,String对象都是这样完成值的填充的: 见: http://hg.openjdk.java.net/jdk6/jdk6/hotspot/file/dc40301aed45/src/share/vm/classfile/javaClasses.cpp 119行:set_value(obj, buffer); 这时候还只是分配了空间 123行:set_count(obj, length); 这里就进行了赋值 132行:buffer->char_at_put(index, unicode[index]); 才是真正填充字符串值的 关键在于set_count(obj, length),见 http://hg.openjdk.java.net/jdk6/jdk6/hotspot/file/dc40301aed45/src/share/vm/classfile/javaClasses.hpp 72行: static void set_count( oop string, int count) { string->int_field_put(count_offset, count); } 因此,set_count很笨的将count_offset对应位置,即count值赋值为length 因此:在我们不改变java.lang.String类的情况下,这是没有问题的: 但如果我们修改了它,会出现什么情况呢? 这还要看类的解析过程: 见: http://hg.openjdk.java.net/jdk6/jdk6/hotspot/file/dc40301aed45/src/share/vm/classfile/classFileParser.cpp 3288行 因此有 allocation_style = 0 故再见 3302行,有表明,非静态域的排序规则为: // Fields order: oops, longs/doubles, ints, shorts/chars, bytes 所以,当修改java.lang.String,例如添加一个数组,必然增加了一个oop 因此,偏移都乱掉了,原本是这样的: value(oop) | offset(int) | count(int) | hash(int) 现在变成这样: value(oop) | myarray(oop) | offset(int) | count(int) | hash(int) 在执行set_count时,相当于给offset赋值,而count未被赋值,因此为默认值0 现在再来看这奇怪的异常吧: 在create vm中,全局初始化时对java.lang.String等类做过硬编码,而java.lang.Thread实例化在其后 见异常最后两句: at java.lang.RuntimePermission.<init>(Unknown Source) at java.lang.Thread.<clinit>(Unknown Source) 我们知道,肯定是在java.lang.Thread做静态初始化时抛出了异常,而且还是和java.lang.RuntimePermission有关, 于是定位到下面这一行: private static final RuntimePermission SUBCLASS_IMPLEMENTATION_PERMISSION = new RuntimePermission("enableContextClassLoaderOverride"); 最终定位到java.security.BasicPermission中 private void init(String name) { if (name == null) throw new NullPointerException("name can't be null"); int len = name.length(); if (len == 0) { throw new IllegalArgumentException("name can't be empty"); } ... 其实name.length() 其实就是return count, 因为偏移被修改,因而count = 0,但String对象的创建是没有问题的 所以name != null 所以才抛出了IllegalArgumentException("name can't be empty")这个异常. 这只是我个人分析的结果,欢迎大家讨论! |
|
RednaxelaFX
2011-10-12
感谢楼主的文字,总结得不错
有若干个核心类的字段排布(FieldsAllocationStyle)在HotSpot VM被固定为0,原因也是为了便于硬编码字段偏移量,让Java一侧与C++一侧都能很方便的访问到对象里的内容。 如果要在java.lang.String的字段上动手脚,在OpenJDK或者Oracle/Sun JDK上的话,那最重要的修改点就是javaClasses.[hpp|cpp]。 |
|
FAvril
2011-10-14
噢~~学习了。非常佩服楼主和RednaxelaFX这么孜孜不倦的精神,虽然这个问题是我提出来的,真正底层的原因还是要高手来解答啊!
|