自动垃圾收集
自动垃圾收集是查看堆内存,识别正在使用那些对象以及哪些对象未被删除以及删除未使用对象的过程。
使用中的对象或者引用的对象意味着程序的某些部分仍然维护指向该对象的指针。程序的任何部分都不再引用未使用的对象或未引用的对象,因此可以回收未引用对象的内存。
像c这样的编程语言,分配和释放内存是一个手动过程。在java中解除分配内存的过程由垃圾收集器自动处理。
确定哪些对象没有使用,确定这个对象什么时候被删除,什么时候删除,这个过程需要先确认这个内存需要被回收
第一步是标记。这是垃圾收集器识别哪些内存正在使用而哪些不在使用的地方
不同类型内存的判断方式:
1.引用计数
在netty中也有引用计数,原理就是在这个对象中添加一个计数器,当有地方用到这个对象的时候,计数器就会加一,用完以后就会减一。但是在java中不会使用,因为在java中会有循环引用的问题,相互引用一个简单的例子就是,obja和objb两个对象之间相互引用,虽然在别的地方没有对这两个对象的引用,但是因为相互引用的关系,导致计数器不会归零,无法进行垃圾回收。
2.可达性分析算法
简单来说,将对象及其引用关系看作一个图,选定活动的对象作为GC Roots;然后跟踪引用链条,如果一个对象和GC Roots之间不可达,也就是不存在引用,那么即可认为是可回收对象,GC Root比如说:创建线程后,线程中所用到的对象或者线程栈中所用到的本地变量
垃圾回收的时候首先找出来所有的可以被作为GC Root的对象,全部找出来以后沿着GC Root往下找,将所有正在使用的标记出来,没有被标记为正在使用的对象则是要被回收,而在可达性分析中的引用在java中也分为很多种,如下所示:
可达性级别:
上面是对对象进行标记,分出可回收的对象和不可回收的对象。然后就应该对对象进行回收,回收算法如下:
上面三种算法有各自的优缺点,但是都无法满足我们对垃圾回收的要求,所以看看JVM是如何进行垃圾回收的。
分代收集
既然说垃圾收集算法有好有坏,根据对象的存活周期,将内存划分为几个区域,不同的区域采用合适的垃圾收集算法。新对象会被分配到Eden,如果超过-XX:+PretenureSizeThreshold:设置大对象直接进入老年代的阀值
内存大小:新生代:老年代 ->1:2 ; 新生代中 S0:S1:Eden -> 1:1:8
在新生代中采用的是复制算法,新创建的对象首先进入Eden区,当Eden区满了以后会进行一次垃圾回收,利用复制算法,将存活的对象复制到S0,并记录一次垃圾回收的次数,剩余的全部清除,当Eden区再次进行垃圾回收的时候,Eden区和S0的对象将会被标记,将Eden和S0区中存活的对象复制到S1中,同时增加垃圾回收的次数,然后清除未存活的对象,当参与清除的存活的对象达到一定次数的时候会将其移动到老年代中,同时假如新生代中内存不足时,也会将新创建的对象存入老年代中,或者大对象创建时直接进入老年代,新生代中之所以使用复制算法,是因为在新生代中的对象一般存活时间都比较短,可以快速的清理。
老年代采用的是标记整理算法。
3.方法区回收
很多人习惯称方法区为永久代(hotspot以永久代来实现方法区)
java虚拟机规范中提到:可以不要求虚拟机在方法区实现垃圾收集。而且在方法区的垃圾回收“性价比”一般比较低。在堆中,尤其是在年轻的,一次垃圾回收一般可以回收70-95%的空间
永久代的垃圾回收分为两部分内容:废弃常量、无用的类
回收常量与java堆的对象回收非常相似。“没有地方引用”
类回收需要满足下面三个条件
该类的所有实例均已回收,也就是该java堆中不存在该类的实例对象
加载该类的classLoader已被回收
该类对应的java.lang.class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法
虚拟机可以对满足以上三个条件的类进行回收。而且这里说的仅仅是可以,而不是跟对象回收一样,不使用了就必然回收。
HotSpot虚拟机提供了-Xnoclassgc参数进行控制
还可以使用以下参数查看类的加载和卸载信息
-XX:+TraceClassLoading :跟踪类加载的信息(诊断内存泄露很有用)
-XX:+TraceClassUnloading :跟踪类卸载的信息(诊断内存泄露很有用)
当这些策略已经准备就绪了就需要一个执行者了----垃圾收集器
在java中有很多类型的垃圾收集器
上面介绍了串行,并行,并发三大类垃圾收集器,通常会有以下组合,我们常用的组合有ParNew+CMS、Parallel Scavenge+Parallel、G1,通常情况下采用JVM默认的就可以了。