Java内存区域与内存溢出异常
运行时数据区
程序计数器
线程私有,线程执行字节码文件的信号指示器,分支、循环、跳转、异常处 理、线程恢复等能都需要依赖这个计数器来完成,由于java虚拟机是多线程的,通过线程轮流切换,分配处理器时间运行,确保线程能回到正确位置,每个线程都是自己的程序计数器,没有任何OutOfMemoryError情况。
虚拟机栈
线程私有,生命周期和线程相同,每个方法被运行的时候都会同步创建一个栈帧存储局部变量表基本数据类型,对象引用,地址等信息,操作数栈,方法出口等信息,一个方法的调用过程,就对应着栈帧的入栈到出栈。线程请求栈深度超过大于虚拟机允许的栈深度就会抛出StackOverflowError异常。
java堆 (分代收集理论 115)
线程共享区域,也是虚拟机中最大的一块,所有的对象都是存储在这里的,GC就是这里产生的,优化也是这里,可以划分出多个线程私有的分配缓冲区,堆满了之后就会抛出OutOfMemoryError异常。
方法区 (方法区回收 113)
线程共享区域,存储被虚拟机加载的类型信息,常量,静态变量,编译后的代码缓存,使用永久代实现,会内存满时会抛出异常
-
- 运行时常量池,方法区的一部分
-
-
- 存放编译期生 成的各种字面量与符号引用,满时会抛出OutOfMemoryError异常
-
对象创建
标记清除算法和标记压缩算法:blog.csdn.net/wang_chao_y…
-
- 1.检查对象的类是否已经被加载,为新生对象分配内存,如果对象堆是绝对工整的所有被使用过的堆都放在一边,被使用的放在另一边,就采用“指针碰撞”,将指针向空闲区域挪动一块距离,如果内存不是工整的,就需要维护一个“空闲列表”,记录那块区域是可用的。
- 2.采用那种内存记录方式是由堆决定的,堆是否各种是由垃圾搜集器是否带空间压缩能力决定的
- 3.java创建对象时十分频繁的,在并发情况下仅仅只修改一个指针的位置是线程不安全的,可能A对象内存指针还没修改,B对象又采用原来的指针来分配内存,解决:
-
-
- 1.分配内存的动作进行同步,采用CAS匹配失败重试的方式
- 2.每个java线程预先分配一小块内存做为本地缓冲区,分配完成在进行同步锁定,是否使用用TLAB,可以通过-XX:+/-UseTLAB参数来 设定。
-
-
- 4.内存分配完毕后,还有初始值,如果使用了TLAB就会在TLAB中顺便进行
// 确保常量池中存放的是已解释的类
if (!constants->tag_at(index).is_unresolved_klass()) {
// 断言确保是klassOop和instanceKlassOop(这部分下一节介绍)
oop entry = (klassOop) *constants->obj_at_addr(index);
assert(entry->is_klass(), "Should be resolved klass");
klassOop k_entry = (klassOop) entry;
assert(k_entry->klass_part()->oop_is_instance(), "Should be instanceKlass");
instanceKlass* ik = (instanceKlass*) k_entry->klass_part();
// 确保对象所属类型已经经过初始化阶段
if ( ik->is_initialized() && ik->can_be_fastpath_allocated() ) {
// 取对象长度
size_t obj_size = ik->size_helper();
oop result = NULL;
// 记录是否需要将对象所有字段置零值
bool need_zero = !ZeroTLAB;
// 是否在TLAB中分配对象
if (UseTLAB) {
result = (oop) THREAD->tlab().allocate(obj_size);
}
if (result == NULL) {
need_zero = true;
// 直接在eden中分配对象
retry:
HeapWord* compare_to = *Universe::heap()->top_addr();
HeapWord* new_top = compare_to + obj_size;
// cmpxchg是x86中的CAS指令,这里是一个C++方法,通过CAS方式分配空间,并发失败的话,转到retry中重试直至成功分配为止
if (new_top <= *Universe::heap()->end_addr()) {
if (Atomic::cmpxchg_ptr(new_top, Universe::heap()->top_addr(), compare_to) != compare_to) {
goto retry;
}
result = (oop) compare_to;
}
}
if (result != NULL) {
// 如果需要,为对象初始化零值
if (need_zero ) {
HeapWord* to_zero = (HeapWord*) result + sizeof(oopDesc) / oopSize;
obj_size -= sizeof(oopDesc) / oopSize;
if (obj_size > 0 ) {
memset(to_zero, 0, obj_size * HeapWordSize);
}
}
// 根据是否启用偏向锁,设置对象头信息
if (UseBiasedLocking) {
result->set_mark(ik->prototype_header());
} else {
result->set_mark(markOopDesc::prototype());
}
result->set_klass_gap(0);
result->set_klass(k_entry);
// 将对象引用入栈,继续执行下一条指令
SET_STACK_OBJECT(result, 0);
UPDATE_PC_AND_TOS_AND_CONTINUE(3, 1);
}
}
}
对象内存布局(3种)
- 对象头
-
- 第一类,存储对象自身运行时数据,哈希码,对象年龄,锁状态
- 第二类,指向类型元数据的指针,如果对象时java素组还会分配一块记录长度
// Bit-format of an object header (most significant first, big endian layout below):
//
// 32 bits:
// --------
// hash:25 ------------>| age:4 biased_lock:1 lock:2 (normal object)
// JavaThread*:23 epoch:2 age:4 biased_lock:1 lock:2 (biased object)
// size:32 ------------------------------------------>| (CMS free block)
// PromotedObject*:29 ---------->| promo_bits:3 ----->| (CMS promoted object)
- 实例数据
-
- 存储代码中对一的各种类型的字内容(包括父类继承数据)(-XX:FieldsAllocationStyle参数 可以修改策略)
- 对齐填充
-
- 不一定存在占位符左右
对象的访问定位
通过栈上的reference数据操作堆中的具体对象,只是指向对象的引用(2种访问方式),垃圾收集时经常会移动对象
- 句柄池访问,会划分出一块空间作为句柄池,reference中存储的就 是对象的句柄地址,句柄中存储实例对象地址信息和类型数据对象地址,好处:对象位置发生改变只用地址信息
- 指针访问,java堆中存放访问类型数据,reference中存储的直接就是对象地址,好处:只用访问一次
对象实例数据(堆):对象中各个实例字段的数据
对象类型数据(方法区):对象的类型、父类、实现的接口、方法等