什么时候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做了什么
- 新生代 复制清理
- MinorGC执行时,会将eden和from survivor中存活的对象复制到to survivor中。会对存活的对象
岁数
判断,如果超过设置的晋升老年代岁数,即晋升到老年代 - 如果对象过大,to区无法存放,则放入老年代中。如果此时打开了自适应开关,GC结束后会调整新生代的大小
- 复制完后清除eden和from区域,并将to和from的标记对调,即下次GC时的to仍然是空的
- MinorGC执行时,会将eden和from survivor中存活的对象复制到to survivor中。会对存活的对象
对于用可达性分析法搜索不到的对象,GC并不一定会回收该对象。要完全回收一个对象,至少需要经过两次标记的过程。
第一次标记:对于一个没有其他引用的对象,筛选该对象是否有必要执行finalize()方法,如果没有执行必要,则意味可直接回收。(筛选依据:是否复写或执行过finalize()方法;因为finalize方法只能被执行一次)。
第二次标记:如果被筛选判定位有必要执行,则会放入FQueue队列,并自动创建一个低优先级的finalize线程来执行释放操作。如果在一个对象释放前被其他对象引用,则该对象会被移除FQueue队列。
- 老年代 标记清理
- 调用System.gc时,系统建议执行Full GC,但是不必然执行
- 老年代空间不足
- 方法区空间不足
标记清理是清理掉不用的对象,但并不会移动对象的位置。这样GC后可能就会产生内存碎片,即不连续的空间。通过维护空闲表,尽可能的减少碎片对大对象的影响。