GC

什么时候GC

首先说明GC是有两种的:MinorGC和FullGC。
MinorGC发生在新生代,FullGC发生在老年代。默认新生代:老年代=1:2,即新生代占用1/3的堆空间。

  • 新生代MinorGC
    在新生代上的GC触发的场景是当对象在堆上申请内存空间,但是内存空间不足时触发。新生代上的内存被划分成三个区域:eden、from survivor、to survivor。他们的比率是8:1:1。新对象的申请内存是向eden区域申请,不足则GC。
  • 老年代FullGC
    老年代的GC的触发场景类似于新生代,只不过申请内存的不是新对象,而是来自于新生代的对象移动。当老年代内存不足时会触发FullGC。或者老年代内存充足,但是配置了HandlePromotionFailure=true,所以每次对象放入老年代都会强制FullGC。

对什么东西GC

JVM采用GCRoots的方式来判断堆上的对象是否仍然被引用,只要对象可达,对象就不会被回收。

什么可以作为GCRoots

  • 虚拟机栈(栈帧中的本地变量表)中的引用的对象
  • 方法区中的类静态属性引用的对象
  • 方法区中的常量引用的对象
  • 本地方法栈中JNI(Native方法)的引用对象

其中方法区的回收场景为

  • 该类所所有的对象实例已经被回收,也就是java堆中不存在该类的任何实例
  • 加载该类的类加载器已经被回收
  • 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

GC做了什么

  • 新生代 复制清理
    1. MinorGC执行时,会将eden和from survivor中存活的对象复制到to survivor中。会对存活的对象岁数判断,如果超过设置的晋升老年代岁数,即晋升到老年代
    2. 如果对象过大,to区无法存放,则放入老年代中。如果此时打开了自适应开关,GC结束后会调整新生代的大小
    3. 复制完后清除eden和from区域,并将to和from的标记对调,即下次GC时的to仍然是空的

对于用可达性分析法搜索不到的对象,GC并不一定会回收该对象。要完全回收一个对象,至少需要经过两次标记的过程。
第一次标记:对于一个没有其他引用的对象,筛选该对象是否有必要执行finalize()方法,如果没有执行必要,则意味可直接回收。(筛选依据:是否复写或执行过finalize()方法;因为finalize方法只能被执行一次)。
第二次标记:如果被筛选判定位有必要执行,则会放入FQueue队列,并自动创建一个低优先级的finalize线程来执行释放操作。如果在一个对象释放前被其他对象引用,则该对象会被移除FQueue队列。

  • 老年代 标记清理
    • 调用System.gc时,系统建议执行Full GC,但是不必然执行
    • 老年代空间不足
    • 方法区空间不足

标记清理是清理掉不用的对象,但并不会移动对象的位置。这样GC后可能就会产生内存碎片,即不连续的空间。通过维护空闲表,尽可能的减少碎片对大对象的影响。

推荐阅读更多精彩内容