对象主要分为三大内容,对象头、对象实例、填充信息(非必要)
对象头
主要存储对象哈希码,GC分代年龄、持有资源(锁)、类信息,如果是数组对象,则还拥有length属性和数组内元素类型大小
填充信息主要是为了满足对象size % 8 == 0的前提,在Java中,一个对象的size是一个8字节整除的数,如果对象的size不满足取余8等于0,那么填充信息就会存在
jvm内存模型
主要分为线程私有,线程共享
线程私有主要有三块区域,程序计数器、虚拟机栈、本地方法栈
程序计数器:用于记录程序执行的地址等,是不会发生OOM的区域
虚拟机栈:
栈帧:
局部变量表 存储方法执行的变量
操作数栈 方法执行时,变量临时存储位置
动态链表 符号引用转为直接引用
返回地址 方法的出口
本地方法栈:与虚拟机栈类似,为native方法服务
线程共享主要有堆区、方法区(可以将方法区理解为标准,就是一种接口,JDK1.7的实现叫永久代、1.8的实现叫原空间)
堆内存中又主要分为新生代和老年代
大对象直接进入老年代、多次GC未被回收的对象也会进入老年代
新生代中又分为Eden区,survivor01(from),survivor02(to),通常默认比例为8:1:1。这样分配的主要原因是Java总大多数对象都是朝生夕死的,所以每次GC都有大量的对象被回收。存活下来的对象只占极少一部分,那么在使用复制整理算法的新生代,这种分配比例是最适合的,因为复制算法的内存使用率通常只有50%,而新生代,待回收的空间只占了总空间的20%,所以对于总空间来说,共计有90%的可用空间。
Eden区还负责对象的分配,在java中,一个对象初始化有这样的过程,加载,验证,准备,解析,初始化,使用,卸载。
当虚拟机收到new的指令后,它会检查class对象是否已存在方法区,如果不存在则进入加载阶段
加载:
1、将字节码文件以流的方式读取
2、数据格式转换为JVM需要的格式
3、在方法区生成一个class对象作为访问的入口
验证:
1、字节码验证,是否cafebabe
2、元数据验证
3、格式验证
准备:
1、对象赋予初始值,如成员变量等无需初始化,直接可用
解析:
1、将对象中的符号引用转为直接引用
初始化:
1、向JVM申请内存空间
2、JVM开辟空间,返回引用
在GC中,class也有可能会被回收,但条件极为苛刻
1、该class没有被任何一处引用(反射)
2、该class的实例已经被全部回收
3、该class的classloader已经被回收
通常jvm中的对象都是在堆中分配,但是满足逃逸分析的情况,会在栈上分配,逃逸分析是指一个对象的引用无法逃出栈,这种优化方式还在锁中体现,被称为“锁消除”
GC的垃圾判定方法中有引用计数法和可达性分析算法,目前jvm已经摒弃了前者,因为存在循环引用的问题,在可达性分析算法中,栈帧中的对象,常量,静态变量,sync关键字持有对象,jni对象都可以为GCROOT对象
分代引用,就是指老年代对象引用新生代对象,可以通过卡表和记忆集来解决,记忆集存储了每个分代引用的引用关系,卡表则记录着分代引用的引用链
三色标记
黑色 灰色 白色
黑色:自身已被垃圾回收扫描且往下引用链也被扫描的对象
灰色:自身已被扫描,但往下引用链未被扫描
白色:被扫描确认为垃圾的对象
增量更新与快照
CMS中如果三色标记引用发生改变,会将原来的引用链丢失的对象的新引用链全部重新扫描
G1中是采用快照的方式,将记录原来丢失的引用从其上一级开始扫描
空间分配算法有指针碰撞和空闲列表
空间分配并发问题,CAS和本地线程缓冲
