[讨论] [被我移过来了]修改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对象
  • 认为对象头后第一个位置为value引用地址,指向实际的字符串值
  • count_offset即为count的偏移,为heapOopSize+header+ sizeof (jint),第一个heapOopSize即为value引用大小偏移,sizeof(jint)即为offset的偏移,故count排第三


通过硬编码偏移,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这么孜孜不倦的精神,虽然这个问题是我提出来的,真正底层的原因还是要高手来解答啊!
Global site tag (gtag.js) - Google Analytics