java内存
java虚拟机内存结构
-
程序计数器
程序计数器属于线程私有,存储下一条待执行的指令的地址,实现跳转、循环、分支等功能,程序计数器不存在内存溢出OOM的问题。 -
虚拟机栈
虚拟机栈属于线程私有,线程每执行一个方法时都创建一个栈帧,栈帧包含了该方法的局部变量表(包括八大基本数据类型的变量、对象引用)、操作数栈、动态链接、方法出口等信息,每一个方法的执行对应栈帧的入栈和出栈过程。线程如果请求的栈深度大于虚拟机所允许的最大深度,抛出StackOverflowError异常。虚拟机栈动态扩展过程中,如果超过规范的大小,抛出OutofMemoryError异常。 -
本地方法栈
本地方法栈属于线程私有,虚拟机栈为执行的java方法服务,本地方法栈为Native方法服务。同虚拟机栈,也可能会抛出SOE和OOM异常。(Native方法???查找) -
方法区
方法区是各个线程共享的内存区域。方法区主要存放类的信息,常量,静态变量。内存不够时,抛出OOM异常。 -
堆
java堆主要存放对象实例和数组。Java堆是垃圾收集器管理的主要区域,在垃圾回收中,将堆划分为新生代、老年代,还可以划分出每个线程私有的分配缓冲区。Java堆物理上不连续,逻辑上连续。内存不够分配内存时,抛出OOM异常。 -
运行时常量池
class文件包含类的版本、字段、方法、接口等信息外,还包括常量池信息,指类中的字面量和符号引用。在类加载时常量池存放在java内存中的运行时常量池中。
对象创建过程(hotspot虚拟机)
类加载->内存分配->初始化为0->配置对象->执行init方法
- 类加载
首先检查类是否被加载过,如果没有,必须先执行相应的类加载过程。 - 内存分配
对象所需内存的大小在类加载完成之后便已经确定。内存分配有两种实现方式:- 指针碰撞。假设java堆内存是绝对规整的,可以使用的内存在一边,已经使用的在另一边,中间存放着指针作为分界点的指示器,内存分配就是将指针向空闲的内存移动对象大小相等的距离,
- 空闲列表。java堆中内存可用和空闲的内存是交替的,空闲列表记录堆中哪些内存块是可用的,当需要分配内存时,从空闲列表中找到一块合适的空间分配给对象。
- 初始化为0
内存分配之后,虚拟机将分配到的内存都初始化为零值,这一操作保证了对象实例字段在java代码中可以不赋初始值就可以直接食用,程序能访问到这些字段的数据类型所对应的零值。 - 配置对象
对对象进行必要的设置,包括这个对象属于哪个类,如何找到这个类的元数据信息,对象的哈希码,对象的GC分代年龄等信息,这些信息存放在对象的对象头中。 方法
执行init方法,把对象按照开发者的意愿进行初始化。
对象的内存布局
对象在内存中存储的信息包括对象头,实例数据,对象填充。
- 对象头
存储对象运行所需要的数据,如哈希码,GC分代年龄、锁状态标志等。另一部分是类型指针,对象通过类型指针找到所属于的类。 - 实例数据
对象存储的有效信息,程序代码中所定义的字段内容。 - 堆起填充
起占位符作用。对象的起始地址必须是8的整数倍。
对象的访问定位
- 句柄访问
堆中存放着某个对象的句柄池,引用则存放句柄池的地址。句柄池中包含指向对象的地址以及指向对象所属类的地址。 - 指针访问
引用直接存放对象的地址,