Field访问对象int字段,对象访问int字段,通过openjdk17 C++源码看对象字段访问原理
在Java反射机制中,访问对象的int
类型字段值(如field.getInt(object)
)的底层实现涉及JVM对内存偏移量的计算与直接内存访问。本文通过分析OpenJDK 17源码,揭示这一过程的核心实现逻辑。
一、字段偏移量计算
1. Java层初始化偏移量
反射访问字段时,会通过UnsafeFieldAccessorImpl
初始化字段的偏移量:
UnsafeFieldAccessorImpl(Field field) {this.field = field;if (Modifier.isStatic(field.getModifiers()))fieldOffset = unsafe.staticFieldOffset(field);elsefieldOffset = unsafe.objectFieldOffset(field); // 实例字段偏移量isFinal = Modifier.isFinal(field.getModifiers()); }
-
对于实例字段,调用
Unsafe.objectFieldOffset(field)
获取偏移量。
2. 本地方法调用
objectFieldOffset
方法通过JNI调用C++实现:
public long objectFieldOffset(Field f) {return objectFieldOffset0(f); // 调用native方法 } private native long objectFieldOffset0(Field f);
3. C++层计算偏移量
在JVM中,Unsafe_ObjectFieldOffset0
最终调用find_field_offset
函数:
UNSAFE_ENTRY(jlong, Unsafe_ObjectFieldOffset0(...)) {return find_field_offset(field, 0, THREAD); } UNSAFE_ENDstatic jlong find_field_offset(...) {oop reflected = JNIHandles::resolve_non_null(field);Klass* k = java_lang_Class::as_Klass(mirror);int slot = java_lang_reflect_Field::slot(reflected); // 获取字段slotint offset = InstanceKlass::cast(k)->field_offset(slot); // 通过slot计算偏移量return field_offset_from_byte_offset(offset); }
-
字段slot:反射对象
Field
中存储的slot
值对应类元数据(InstanceKlass
)中字段的索引。 -
偏移量计算:
InstanceKlass::field_offset(slot)
通过slot索引从类元数据中获取字段的实际内存偏移量。
二、通过偏移量访问字段值
1. Java层读取字段值
反射调用getInt
时,直接通过偏移量访问内存:
public int getInt(Object obj) {ensureObj(obj);return unsafe.getInt(obj, fieldOffset); // 使用偏移量读取int值 }
2. C++层内存访问
在JVM解释执行字节码时(如GETFIELD
),访问字段的逻辑与反射一致:
// 字节码解释执行逻辑片段 case itos:SET_STACK_INT(obj->int_field(field_offset), -1);break;
-
obj->int_field(field_offset)
:通过偏移量直接从对象内存中读取int
值。 -
SET_STACK_INT
将值压入操作数栈。
三、关键数据结构与内存布局
1. 对象内存布局(oopDesc)
Java对象在内存中的布局包含对象头(Header)和实例数据(Instance Data):
-
对象头:存储Mark Word和类指针(Klass*)。
-
实例数据:字段按声明顺序排列,每个字段的偏移量由类元数据确定。
2. 类元数据(Klass)
InstanceKlass
存储类的元信息,包括字段表:
class InstanceKlass : public Klass {// 字段表(fieldDescriptor数组)int field_offset(int slot) const {return field(slot)->offset(); // 获取字段偏移量} };
3. 反射字段的slot映射
反射对象Field
通过slot
与类元数据关联:
// 反射Field对象存储slot值 int java_lang_reflect_Field::slot(oop reflect) {return reflect->int_field(_slot_offset); }
-
slot
值在类加载阶段生成,对应字段在类字段表中的索引。
四、性能优化与安全性
1. 偏移量缓存
反射调用Field.getInt()
时,偏移量(fieldOffset
)在UnsafeFieldAccessorImpl
初始化阶段计算并缓存,后续访问无需重复计算。
2. 内存直接访问
通过Unsafe.getInt(obj, offset)
绕过Java访问控制,直接操作内存。这种设计虽然高效,但也绕过了语言层面的安全性检查。
3. final字段处理
若字段为final
,UnsafeFieldAccessorImpl
会标记isFinal
,部分JVM实现可能阻止修改(尽管某些场景下仍可通过反射修改)。
五、总结
通过反射访问int
字段的流程可概括为:
-
计算偏移量:反射初始化阶段通过
slot
从类元数据获取字段偏移量。 -
内存访问:调用
Unsafe.getInt()
直接读取对象内存。 -
字节码执行:解释器/即时编译器使用相同机制访问字段。
这一机制体现了JVM反射与字节码执行在底层实现上的一致性:均依赖预先计算的字段偏移量直接操作内存。理解这一过程有助于优化反射性能,并为分析JVM内存模型提供基础。
openjdk17源码
UnsafeFieldAccessorImpl(Field field) {this.field = field;if (Modifier.isStatic(field.getModifiers()))fieldOffset = unsafe.staticFieldOffset(field);elsefieldOffset = unsafe.objectFieldOffset(field);isFinal = Modifier.isFinal(field.getModifiers());}public long objectFieldOffset(Field f) {if (f == null) {throw new NullPointerException();}return objectFieldOffset0(f);}private native long objectFieldOffset0(Field f);public int getInt(Object obj) throws IllegalArgumentException {ensureObj(obj);return unsafe.getInt(obj, fieldOffset);}} else if (type == Integer.TYPE) {return new UnsafeIntegerFieldAccessorImpl(field);}C++代码
{CC "objectFieldOffset0", CC "(" FLD ")J", FN_PTR(Unsafe_ObjectFieldOffset0)},UNSAFE_ENTRY(jlong, Unsafe_ObjectFieldOffset0(JNIEnv *env, jobject unsafe, jobject field)) {return find_field_offset(field, 0, THREAD);
} UNSAFE_ENDstatic jlong find_field_offset(jobject field, int must_be_static, TRAPS) {assert(field != NULL, "field must not be NULL");oop reflected = JNIHandles::resolve_non_null(field);oop mirror = java_lang_reflect_Field::clazz(reflected);Klass* k = java_lang_Class::as_Klass(mirror);int slot = java_lang_reflect_Field::slot(reflected);int modifiers = java_lang_reflect_Field::modifiers(reflected);if (must_be_static >= 0) {int really_is_static = ((modifiers & JVM_ACC_STATIC) != 0);if (must_be_static != really_is_static) {THROW_0(vmSymbols::java_lang_IllegalArgumentException());}}int offset = InstanceKlass::cast(k)->field_offset(slot);return field_offset_from_byte_offset(offset);
}int field_offset (int index) const { return field(index)->offset(); }int java_lang_reflect_Field::slot(oop reflect) {return reflect->int_field(_slot_offset);
}void java_lang_reflect_Field::set_slot(oop reflect, int value) {reflect->int_field_put(_slot_offset, value);
}oop Reflection::new_field(fieldDescriptor* fd, TRAPS) {Symbol* field_name = fd->name();oop name_oop = StringTable::intern(field_name, CHECK_NULL);Handle name = Handle(THREAD, name_oop);Symbol* signature = fd->signature();InstanceKlass* holder = fd->field_holder();Handle type = new_type(signature, holder, CHECK_NULL);Handle rh = java_lang_reflect_Field::create(CHECK_NULL);java_lang_reflect_Field::set_clazz(rh(), fd->field_holder()->java_mirror());java_lang_reflect_Field::set_slot(rh(), fd->index());inline jint oopDesc::int_field(int offset) const { return HeapAccess<>::load_at(as_oop(), offset); }
inline jint oopDesc::int_field_raw(int offset) const { return RawAccess<>::load_at(as_oop(), offset); }
inline void oopDesc::int_field_put(int offset, jint value) { HeapAccess<>::store_at(as_oop(), offset, value); }case stos:SET_STACK_INT(obj->short_field(field_offset), -1);break;case itos:SET_STACK_INT(obj->int_field(field_offset), -1);#define DEFINE_GETSETOOP(java_type, Type) \\
UNSAFE_ENTRY(java_type, Unsafe_Get##Type(JNIEnv *env, jobject unsafe, jobject obj, jlong offset)) { \return MemoryAccess<java_type>(thread, obj, offset).get(); \
} UNSAFE_END \T get() {if (_obj == NULL) {GuardUnsafeAccess guard(_thread);T ret = RawAccess<>::load(addr());return normalize_for_read(ret);} else {T ret = HeapAccess<>::load_at(_obj, _offset);return normalize_for_read(ret);}}