. java内存区域和内存溢出异常
- 运行时数据区域:
线程独享:
- java虚拟机栈:生命周期和线程相同,每个方法执行时,都会创建一个栈帧,保存局部变量表和方法出口等信息,方法的执行和结束就是栈帧入栈和出栈的过程。如果栈的深度大于虚拟机允许的最大深度,会抛出Stack OverflowError。对于可动态扩展的虚拟机栈,如果扩展的时候无法获取更多的内存空间,会抛出OutOfMemeryError
- 本地方法栈:基本和虚拟机栈一致,只是本地方法栈为虚拟机的native方法服务。
- 程序计数器:当前线程所执行字节码的行号指示器。字节码解释器的工作就是读取这个计数器的值来选取下一条指令
线程共享:
- 方法区:和java堆一样,线程共享区域,用于存放已被虚拟加载的类信息,常量和静态变量。
1.1 常量池:用于存放编译时的各种字面量和符号引用。常量池具有动态性,即不一定必须在编译时产生,在运行时也可以把变量放到常量池,如string的intern()方法。 - 堆:线程共享内存,虚拟机启动时分配,是垃圾收集器的主要工作区域,可以分为年轻代和老年代,也可以分为Eden区,From Survior,To Survior区
直接内存
直接内存并不是java运行时数据区域的一部分,是在native堆分配的内存。
2.对象的创建
- 类加载检查:虚拟机遇到一条new指令,检测new指令后的参数在常量池中是否存在符号引用,并检查这个符号引用代表的类是否已经被加载、解析、初始化。如果没有,要进入类加载过程。
- 为对象分配空间:类检查之后,需要为对象在堆中分配内存空间,大小在类加载的时候已经确定。
1.1 假设堆的内存空间是规整的,即使用的空间和空闲的空间在两边,中间通过一个指针分隔,这种时候,只需要将指针朝空闲空间移动对象大小的长度即可,这种分配方式成为指针碰撞。
1.2 假设堆的内存空间不是规整的,这虚拟机必须创建一个列表,记录空闲空间,分配内存时,从空闲空间分配内存,并更新列表。称为空闲列表
使用何种分配方式通过垃圾收集器决定。使用Serial,PerNew等带有Compact的收集器时,使用指针碰撞。使用CMS等基于Mark-Swap方式的收集器时,使用空闲列表方式。
在分配内存空间时,要解决并发问题:
1.对分配内存空间的动作进行同步-实际上虚拟机采用CAS和失败重试的方式保证操作的原子性
2.把内存分配的动作放到线程中,即线程本身在堆中预分配小块内存,当该内存不够时,在进行同步操作 - 初始化分配的内存空间为零值。
- 对象头设置。虚拟机对对象进行必要的设置,如对象hash码,分代信息,对象头,是否要设置偏向锁等。
到此为止,虚拟机工作已经完成。之后便是init工作,这部分由程序员完成。
- 对象的内存布局
在HotSpot虚拟机中,对象可以分为三个部分
- 对象头(Mark Word)
对象头分为两部分:
1.1 存放对象自身运行时数据:如hash码,GC分代信息,锁状态标志,线程持有的锁、状态id,偏向时间戳等。
1.2 对象的类型指针,即指向类元数据的指针。对于对象是数组的,还有数组长度。 - 实例数据
真正保存数据的部分,如果示例字段值 - 对齐填充
非必须,HotSpot虚拟机自动内存管理系统要求对象地址必须是8字节的整数倍,因为对象头必然是8字节的整数倍,所以当示例数据不是8的整数倍时,需要对齐填充。
网友评论