1、JVM中对象的创建(仅限于普通对象,不包含数组和Class对象等)
- 遇到new指令时,去常量池定位相应类的符号引用
- 检查该符号引用是否被加载、解析和初始化过
- 若2检查不通过,则去加载相应的类
- 加载过后为新生对象分配内存,内存分配有以下两种方式
- 指针碰撞:前提是堆中内存绝对规整,即用过的内存放在一边,未用过的放在一边,中间放着一个指针作为分界点
- 空先列表:若堆中内存不规整则可采用此种方式,虚拟机维护一个空闲内存列表
分配内存可能会引发线程的安全问题,有以下两种方案:
- 堆内存分配动作进行同步操作,常用CAS + 失败重试
- 把内存分配的动作按照线程划分在不同的空间之中,即每个线程在堆中预分配一小块空间,称为本地线程分配缓冲(TLAB),当TLAB分配完时,再使用同步的方式进行分配
2、对象的内存布局
在HotSpot虚拟机中,对象的存储布局可分为3块区域:对象头、实例数据和对齐填充
2.1 对象头
对象头包括以下两部分信息
- 自身运行时的数据,如HashCode、GC分代年龄,锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。这部分数据的长度也称为Mark Word。Mark Word一般被设计成非固定的数据结构以便在极小的空间存储尽量多的信息。
- 类型指针,即对象指向它元数据的指针,虚拟机通过这个指针确定该对象是哪个类的实例。
2.2 实例数据
这部分存储代码中所定义的各个类型字段的内容。存储顺序受虚拟机存储策略和源码定义顺序的影响。默认策略是,长度相同的字段总被分配到一起,如long和double、short和char
2.3 对齐填充
并不是必然存在,起占位符的作用。VM内存系统要求对象的起始地址是8字节的整数倍。
3、对象的访问定位
3.1 使用句柄
- Java堆中会划出一块内存作为句柄池,引用类型变量中存储的便是句柄地址,句柄中则包含了对象实例数据和类数据的地址信息。
- 这么做的好处是:引用类型变量中存储的是稳定的句柄地址,在对象被移动时(如垃圾收集时)只会改变句柄中的内容。
3.2 直接访问
- 引用类型变量中存储堆中对象的地址信息,这种情况下,堆对象的内存布局中就必须要考虑放置类数据的访问信息。
- 这么做的好处是:在访问对象时少了一次指针定位的时间开销。
4、判断对象是否成活的方法
4.1 引用计数法
给每个对象添加一个引用计数器,每当有一个地方引用他,计数器就加1,当计数器为0时意味着当前对象很可能不会再被调用,于是被判定为“死亡”。这种方法很难解决循环引用的问题。
4.2 可达性分析算法
- 基本思路是(两次标记):
- 通过一系列被称为“GC Roots”的对象作为起始点,从这些结点向下搜索,搜索所经过的的路径称为引用链。当一个对象到“GC Roots”没有任何引用链时,证明此对象不可用,此时为该对象做第一次标记。
- 对象做完第一次标记后会进行一次筛选,筛选的条件是判断该对象是否有必要执行
finalize()
方法(只会被系统自动调用一次) - 如果有必要执行则会被放在一个队列中,并稍后由一个低优先级线程去挨个执行队列中对象的
finalize()
方法,对象可在此方法中将自己与“GC Roots”建立联系,从而避免被回收。否则将会被GC做第二次标记从而被回收。
- 在Java中称为“GC Roots”的对象包括以下几种:
- 虚拟机栈帧中的本地变量表引用的对象
- 方法区中静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中JNI(本地方法)引用的对象
5、Java当中的引用与垃圾回收的关系
5.1 强引用
这种引用在Java中最常见,只要强引用还存在垃圾收集器就永远不会回收被引用的对象
//强引用
Object o = new Object();
5.2 软引用
- 用来引用一些“食之无味,弃之可惜”的对象。对于软引用所引用的对象,在内存充足的情况下并不会被回收,在系统即将发生内存溢出异常之前,将会把这些对象进行回收,如果还没有足够的内存,才会抛出内存溢出的异常。
- Java提供
SoftReference
类来实现软引用。
5.3 弱引用
- 也是用来描述一些非必需的对象
- 在垃圾回收来临之际,无论内存是否充足,这些对象都会被回收
- Java提供
WeakReference
类来实现软引用。
5.4 虚引用
- 最弱的一种引用关系,对对象的生存时间不造成任何影响,也无法通过此引用获取对象实例
- 设置虚引用的唯一目的是,能在对象被回收是收到一个系统通知。
- Java提供
PhantomReference
类来实现软引用。
6、方法区(永久代)中的GC
永久代的垃圾回收主要是废弃常量和无用的类
6.1 废弃常量
系统总再无此常量的引用。
6.2 无用的类
符合下列三条件的无用类可以进行回收
- 系统中所有该类的实例都已被回收
- 该类的ClassLoader已被回收
- 该类对应的Class对象已无任何引用在身
在大量使用反射、动态代理、CGLib等这类需要频繁自定义ClassLoader的场景都需要虚拟机具备类卸载的功能,以保证永久代不会溢出。