JVM垃圾回收
(JVM学习笔记)
1. 如何判断对象可以回收
1.1 引用计数法
当一个对象被某个变量引用时,该对象的引用计数就会加1,当该对象的引用计数为0时,这个对象就会被回收。
引用计数法存在循环引用的问题:
A对象引用了B对象,同时B对象也引用了A对象,也没有其他的对象引用他们,但是他们的引用计数都为1,不会被回收,Java没有采用引用计数法
1.2 可达性分析算法
Java虚拟机的垃圾回收器采用可达性分析来探索所有存活的对象
扫描堆中的对象,看是否能够沿着 GC Root对象(根对象) 为起点的引用链找到该对象,找不到,表示可以
回收(当对象被根对象引用时就不会被回收,若没有被根对象引用则会被回收)
哪些对象可以作为 GC Root ?
System Class | 例如:Objtect类对象,String类对象 |
---|---|
Native Stack | 调用本地方法时,本地方法中局部变量引用的对象 |
Thread | 当前活动的线程,栈帧对应的方法中局部变量引用的对象 |
Busy Monitor | 锁对象,锁对象那个不作为根对象的话,被垃圾回收了,会导致锁释放不了 |
1.3 四种引用
- 强引用
- 只有所有 GC Roots 对象都不通过【强引用】引用该对象,该对象才能被垃圾回收
- 软引用(SoftReference)
仅有软引用引用该对象时,在垃圾回收后,内存仍不足时会再次出发垃圾回收,回收软引用对象
可以配合引用队列来释放软引用自身(当软引用引用对象被垃圾回收时,软引用对象自身就会入队)
- 弱引用(WeakReference)
仅有弱引用引用该对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用对象
可以配合引用队列来释放弱引用自身(当弱引用引用对象被垃圾回收时,若引用对象自身就会入队)
- 虚引用(PhantomReference)
- 必须配合引用队列使用,主要配合 ByteBuffffer 使用,被引用对象回收时,会将虚引用入队,
由 Reference Handler 线程调用虚引用相关方法释放直接内存
- 终结器引用(FinalReference)
- 无需手动编码,但其内部配合引用队列使用,在垃圾回收时,终结器引用入队(被引用对象
暂时没有被回收),再由 Finalizer 线程通过终结器引用找到被引用对象并调用它的 fifinalize
方法,第二次 GC 时才能回收被引用对象
2. 垃圾回收算法
2.1 标记清除
先将没有直接或间接被根对象引用的对象标记,再将其清除,清除的时候不会对内存空间有过多的操作,只是将这块内存的首尾地址记录下来,当需要造一个新对象开辟一个新的空间时,就看看被清除的内存空间够不够放的下这个对象,如果放的下就直接用,放不下就会造成内存碎片。
优点:清除速度快
缺点:内存空间不连续,产生内存碎片
2.2 标记整理
先标记后整理,将未被标记的对象清除,整理被根对象引用的对象,将他们移动到一个,使得内存空间变得更紧凑,避免内存碎片
- 优点:没有内存碎片
- 缺点:速度慢(需要移动被根对象引用的内存,这些对象的内存地址会发生改变,引用这些对象的局部变量也需要改变,所以慢)
2.3 复制
①将被根对象标记的对象从FROM区域复制到TO区域,复制的时候会进行内存空间的整理,避免内存碎片
②FROM区域剩下的全是未被标记的垃圾对象全部清除
③再将FROM和TO位置交换
- 优点:没有内存碎片
- 缺点:占用双倍的内存空间
3. 分代垃圾回收
- 对象首先分配在伊甸园区域
- 当伊甸园区域内存空间不足时,会触发Minor GC,将伊甸园和幸存区From中存活的对象使用复制算法复制到幸存区To中,存活的对象寿命加1并交换From和To(保证To区域的内存空间是空的)
- Minor GC会触发stop the world,暂停其他用户的线程,等垃圾回收结束,用户线程才恢复
- 当对象寿命超过阈值时,会晋升到老年代,最大寿命是15(4bit)
- 当来年代空间不足时,会先尝试触发Minor GC,如果垃圾回收之后空间仍然不足,那么出发Full GC,STW时间更长(因为老年代存活的对象多,老年代中的对象不容易被当成垃圾回收)
新生代采用的是复制算法,来年代采用的是标记清除/标记整理算法。