openjdk中的java栈帧是如何布置的呢,在java栈中如果确定变量是一个引用呢,先复习《深入java虚拟机第二版》有关栈帧的内容。
“栈帧由三部分组成:局部变量区、操作数栈和栈数据区。局部变量区和操作数栈要视对应的方法而定,他们是按字长计算的。编译器在编译时就确定了这些值并放在class文件中,而栈数据区的大小依赖于具体实现。
当虚拟机调用一个java方法时,它从对应的类的类型信息得到局部变量区和操作数栈的大小,并据此分配栈帧内存,并压入java栈中。
局部变量区 java栈帧的局部变量区被组织成一个以字长为单位、从0开始计数的数组。字节码指令通过以0开始的索引来使用其中的数据。类型为int、float、refence和returnAdress的值在数组内只占据一项。而类型为byte、short、char的值在存入数组时都先转换为int值,因此同样只占据一项。而类型为long和double的值在数组中却占据了连续两项。
局部变量区包含了对应的方法参数和局部变量。”
现在的问题是在垃圾回收算法遍历java线程堆栈时候,它是如何确定局部变量是基本类型还是对象引用呢?
openjdk对局部变量区解释有两种方法,通过TaggedStackInterpreter这个设置来区分。从英文意思上面来看应该是一个通过在栈中打上标志,另外一个则是在其他地方做标志。在栈上打标志,则应该是每个变量多压入一个标志头(为一个字长),猜测是否正确呢,看看解释器将引用压栈的代码。
void BytecodeInterpreter::astore(intptr_t* tos, int stack_offset,
intptr_t* locals, int locals_offset) {
if (TaggedStackInterpreter) {
frame::Tag t = (frame::Tag) tos[Interpreter::expr_tag_index_at(-stack_offset)];
locals[Interpreter::local_tag_index_at(-locals_offset)] = (intptr_t)t;
}
intptr_t value = tos[Interpreter::expr_index_at(-stack_offset)];
locals[Interpreter::local_index_at(-locals_offset)] = value;
}
local_index_at代码
{
return stackElementWords() * i + (value_offset_in_bytes()/wordSize);
}
stackElementWords() 代码
{
return TaggedStackInterpreter ? 2 : 1; //如果是在栈打标志,字长为2,否则是1,在这可以肯定每个变量前都会多压入一个字长的标志头,虚拟机会通过这个标志头来确定是否是对象引用
}
上面分析对象在栈中打标志来区分基本类型和引用类型,这种方法每个变量起码多了一个字的内存空间,空间浪费严重。如果能用位图表示岂不更好?再看另外一种方案。
从上面的压栈代码看不到另外的操作,openjdk是如何做的呢,还是从上篇gc代码中看看。
void frame::oops_interpreted_do(OopClosure* f, const RegisterMap* map, bool query_oop_map_cache) {
........
jint bci = interpreter_frame_bci();
methodHandle m (thread, interpreter_frame_method());
InterpreterFrameClosure blk(this, max_locals, m->max_stack(), f);
InterpreterOopMap mask;
if (query_oop_map_cache) { //传入true
m->mask_for(bci, &mask); //从oopMapCache中填充mask变量
} else {
OopMapCache::compute_one_oop_map(m, bci, &mask);
}
mask.iterate_oop(&blk);
}
void InterpreterOopMap::iterate_oop(OffsetClosure* oop_closure) {
int n = number_of_entries();
int word_index = 0;
uintptr_t value = 0;
uintptr_t mask = 0;
// 遍历每项
for (int i = 0; i < n; i++, mask <<= bits_per_entry) {
if (mask == 0) {
value = bit_mask()[word_index++];
mask = 1;
}
// 判断是否是对象引用?
if ((value & (mask << oop_bit_number)) != 0) oop_closure->offset_do(i);
}
}
void InterpreterFrameClosure::offset_do(int offset) {
oop* addr;
if (offset < _max_locals) {
addr = (oop*) _fr->interpreter_frame_local_at(offset);
assert((intptr_t*)addr >= _fr->sp(), "must be inside the frame");
_f->do_oop(addr); //进行标记和压栈
}
}
从上面来看,采用位图来区分对象引用基本上是肯定的,具体如何实现还有待于细读。
分享到:
相关推荐
变量槽可以存放基本数据类型(如int, float等)、对象引用以及returnAddress类型。对于32位的数据类型,每个槽位可以存放一个变量;而64位的数据类型(如long和double)则需要两个槽位。变量槽的位置与方法参数和...
2. **栈内存**:栈主要用于存储方法的局部变量,包括基本类型和对象引用。每当一个方法被调用,都会在栈上创建一个新的栈帧,用于存放局部变量、操作数栈和方法返回地址等信息。当方法执行完毕,栈帧就会被弹出,其...
堆内存主要用来存储对象实例和数组,而栈内存主要存储基本类型变量和对象引用。 2. **堆内存分配** 堆内存是Java中的全局共享区域,用于存储所有的对象实例和数组。当通过`new`关键字创建一个新的对象时,其实际的...
3. **this本质**:`this`关键字在Java中表示当前对象的引用,它常用于区分成员变量和局部变量,或者在构造方法中调用其他构造方法。 4. **OOTest**:OOTest可能是指对象和类(Object-Oriented Testing)的测试,...
栈内存主要用于存储基本类型变量(如int、char)和对象引用,而堆内存则用于存储对象实例。 当一个方法被调用时,JVM会在栈内存中创建一个栈帧(Stack Frame),这个栈帧包含了局部变量表、操作数栈、动态链接和...
- **栈内存共享**:栈中的基本类型变量和对象引用是按值存储的。如果多个变量引用同一个对象,它们实际上共享同一份堆内存中的对象,改变一个引用变量的值不会影响其他引用变量。 了解这些内存分配规则有助于避免...
首先,`this`关键字在Java中主要表示当前对象的引用。它可以在类的方法中使用,用来访问该类的实例变量,调用其他方法,或者在构造器中传递当前对象的引用。当在类的方法或构造器中使用`this`时,它指代的就是正在...
首先,栈区是Java内存模型中的一个重要部分,它主要用于存储基本类型变量(如int、float、boolean等)和对象引用。栈内存分配速度快,因为它的操作主要是基于固定大小的数据单元。当一个方法被调用时,一个新的栈帧...
- **对象引用**:为了访问堆内存中的对象,可以在栈内存中创建一个引用变量,该变量的值为对象在堆内存中的地址。 - **垃圾回收**:Java虚拟机(JVM)会自动管理堆内存,通过垃圾回收机制(Garbage Collection, GC)...
通过对象引用,我们可以访问堆内存中的对象数据。 **对象实例化过程**: 1. 当执行 `new Rectangle(3,5)`,系统首先在堆内存中为Rectangle类的实例分配空间,初始化成员变量为默认值。 2. 接着,根据类定义的初始化...
在Java编程语言中,了解程序内存的管理对于优化...开发者应该注意合理使用栈和堆,避免创建不必要的大对象,及时解除不再使用的对象引用,以确保程序的高效运行。此外,理解垃圾收集机制也是确保内存有效管理的关键。
对于基本类型(如int)的变量,它们的值直接存储在栈中,而引用类型的变量则在栈中存储对堆中对象的引用。 在方法调用中,局部变量会根据需要在栈中创建和销毁。例如,当传递一个对象作为参数时,实际上传递的是...
* 强引用(Strong Reference):普通的对象引用,垃圾收集器不回收。 * 软引用(Soft Reference):对象的软引用,垃圾收集器在内存不足时回收。 * 弱引用(Weak Reference):对象的弱引用,垃圾收集器在执行时回收...
例如,当创建一个类的实例时,JVM会在堆中分配空间,然后在栈中存储一个指向该对象的引用。对于基本类型的变量,它们的值直接存储在栈中;而对于引用类型的变量,栈中存储的是指向堆中对象的指针。 在方法调用过程...
栈内存主要用于存储基本数据类型变量和对象引用。当在方法内部声明一个变量时,Java会在栈中为其分配空间,存放变量的值。基本数据类型包括boolean、byte、char、int、short、long、float和double,它们的值直接存储...
- **对象与引用的关系**:Java中,程序员只能通过引用去访问或操作对象。这意味着,即使创建了一个对象,如果没有相应的引用指向它,那么这个对象就无法被访问,进而可能会被垃圾回收机制清理掉。 - **引用的特点**...
数组在Java中也是一种对象,数组的引用存储在栈中,而数组对象本身则存储在堆中。数组对象包含了数组元素的值。 例如: ```java int[] x = new int[3]; ``` 上面的代码创建了一个数组对象,这个对象有三个元素,...
在Java中,引用变量是一个普通变量,存储在栈中,当其作用域结束时,栈中的引用变量会被释放,但堆内存中的对象仍然存在,直到GC将其回收。 例如,在下面的代码中: ```java public class ArrayTest { public ...
在JVM中,方法调用通过操作数栈进行参数传递,例如`b.deposit(100)`会将`b`对象引用和`100`推入操作数栈,然后调用`deposit`方法。方法调用结束后,栈帧会被弹出,程序计数器更新,执行下一条指令。 总结来说,Java...