java垃圾回收是java语言方面与c++的一个主要特性吧,极大的方便了程序员的开发操作,无需自己管理内存。

要了解java垃圾回收机制首先简单了解下java内存模型

JAVA内存模型

程序运行的时候,内存主要由以下部分组成:

  1. :所有线程共享一个堆;存放的都是new 出来的对象;使用完了的对象,将来都不被使用的对象,由垃圾回收器回收,清理出内存以便创建新对象
  2. 方法区:所有线程共享一个方法区;里面存放的内容有点杂,可以认为是除堆和栈中的其它东西(如类信息,静态变量,常量,代码等);Java虚拟机规范规定可以不对方法区进行垃圾回收,当并不是不回收,主要看具体虚拟机的实现,比如可以回收一些废弃常量和无用的类,如果大量加载cglib代理子类的话将会发生持久代溢出;
  3. 程序计数器:也叫PC,存放下一条指令所在单元的地址的地方;
  4. JAVA栈每个线程都有一个自己的JAVA栈;这里面有一个叫做栈帧的东西,栈帧存放的一般是方法的局部变量,方法出口信息等;方法调用过程中,栈帧自动压栈出栈,栈空间大小是有限制的;
  5. 本地方法栈:与JAVA栈类似,区别是使用的对象不一样,本地方法栈是给Native方法使用的,JAVA栈是给JAVA方式使用的;

如何判断java对象是否要被回收

可达性分析算法:通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,当一个对象到GC Roots没有任何路径相连(用图论的话来说,就是从GC Roots到这个对象不可达)时,则此对象为垃圾对象。

那么,哪些对象可以被认为是“GC Roots”对象呢,如下

  • Class - 由系统类加载器加载的对象,这些类是不能够被回收的,他们可以以静态字段的方式保存持有其它对象。
  • Thread - 活着的线程
  • Stack Local - Java方法的local变量或参数
  • JNI Local - JNI方法的local变量或参数
  • JNI Global - 全局JNI引用
垃圾回收算法

复制算法

将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。

标记-清除算法

首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象;

它主要有两个不足:一个是效率问题,标记和清除两个过程的效率都不高;另一个是空间问题,标记清除之后会产生大量不连续的内存碎片;

标记-整理

标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。

那么,垃圾对象是如何回收的呢?答案是分代回收。

为什么需要分代收集

不同的对象的生命周期是不一样的。因此,不同生命周期的对象可以采取不同的收集方式,以便提高回收效率。分代垃圾回收采用分治的思想,进行代的划分,把不同生命周期的对象放在不同代上,不同代上采用最适合它的垃圾回收方式进行回收。

  一般,Java堆分为新生代和老年代和持久代(方法区),这样就可以根据各个年代的特点采用最适当的收集算法。

  在新生代中,有大批对象死去,只有少量存活,一般选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。新生代分三个区。一个Eden区,两个Survivor区(Eden:Survivor比例是8:1)。使用的使用一个Eden区和一个Survivor区作为申请内存存放对象的区域,如果不够发送一次YGC,将对象通过复制算法移动到另一个Survivor区,如果这个Survivor还是不够需要老年代来进行担保,也就是说如果另一块Survivor区不够将通过担保进制进入老年区。新创建的对象的内存都分配自eden,Minor collection的过程就是将eden和在survivor space中的活对象copy到空闲survivor space中,对象在新生代里经历了一定次数(可以通过参数配置)的minor collection后,就会被移到老年代中,称为tenuring。

  老年代中因为对象存活率高,不需要大量清理或整理,因此使用“标记—清理”或者“标记—整理”算法来进行回收。

  GC分为两种,YGC和FGC;

  • YGC:对Eden区域进行GC,清除非存活对象,并且把尚且存活的对象移动到Survivor区。
  • FGC:对整个堆进行GC,包括Young、Tenured和Perm。在对JVM调优的过程中,很大一部分工作就是对于FullGC的调节。尽可能减少Full GC的次数
  • 有如下原因可能导致Full GC:
  • 年老代(Tenured)被写满
  • 持久代(Perm)被写满
  • System.gc()被显示调用
  • 上一次GC之后Heap的各域分配策略动态变化
什么时候发生垃圾回收(GC)

如果只是每次在内存不够就发生GC这种策略是很片面的,当然如果是申请对象内存空间不够的时候肯定是要发送GC以便创建对象的,如果GC了还是不够就会出现内存溢出。但是代码运行阶段GC也是在某些特定点上发生的。

我们已经知道了GC的发送需要让jvm知道哪些对象的要被回收的,那就是说哪些对象被标记了,通过可达性分析发现没有被引用了,但是程序是一直在变化的,我们如果要找到这些对象就需要让程序停下来,这被称为Stop the world这个时候让垃圾回收线程去标记有哪些对象要被回收。而程序需要在那些地方发生停顿然后GC呢?如果停顿点太多影响效率,太少又会经常的出现内存溢出。所以需要定义好那些地方出线GC,这些地方被称为安全点

什么是安全点,长时间执行的地方被称为安全点,例如方法调用,循环跳出,异常跳转。

那怎么保证所有线程在安全点这个时候停下来,现在主要运用一种算法,主动式中断算法,要发生GC的时候设置个标志位,让线程去轮询,标志位置与安全点重合。

当线程不执行的时候怎么办?难道重新分配cpu时间?线程处于sleep的时候,安全区域代码可以被认为是安全点的扩展,表示这个区域内都是安全点,当线程离开安全区域时候,系统检查是否完全根节点,如果完成了,那线程继续执行,否则就要等待完成。