垃圾回收机制:
什么时候:
什么时候开启垃圾回收(触发GC的条件),GC触发的条件有两种:①、程序调用System.gc时可以触发;②、系统自身判断GC的依据:根据Eden区和From Space区的内存大小来决定。当内存大小不足时,则会启动GC线程并停止应用线程。
对什么东西:
GC操作的对象分为:通过可达性分析法无法搜索到的对象和可以搜索到的对象,对于搜索不到的对象进行标记
做了什么:
对于可以搜索到的对象进行复制操作,对于搜索不到的对象,调用finalize()方法进行释放。
常见的垃圾回收算法:
-
引用计数:
- 每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可用回收。此方法简单,无法解决对象相互循环引用的问题,且每次对对象复制时均要维护引用计数器,且计数器本身也有一定的消耗。
复制算法:
堆内存:
- 新生代:新生代划分为Eden(伊甸园)与SurvivorFrom与SurvivorTo
-
老年代:老年代的空间里只有老年代
- 1、首先,当Eden区满的时候回触发第一次GC,把还活着的对象拷贝到SurvivorFrom区,当Eden区再次触发GC的时候会扫描Eden区和From区域,对这两个区域进行垃圾回收,经过这次回收后还存活着的对象,则直接复制到To区域(如果有对象的年龄已经达到了老年的标准,则赋值到老年代区),同时把这些对象的年龄 + 1
- 2、然后,情况Eden和SurvivoFrom中的对象,也即复制之后有交换,谁空谁是To
- 3、SurvivorTo和SurvivorFrom互换
最后,SUrvivoTo和SurvivorFrom互换,原SurvivorTo成为下一次GC时的SurvivorFrom区。部分对象会在From和To区域中复制来复制去,如此交换15次(由JVM参数MaxTenuringThreshold决定,这个参数默认是15),最终如果还是存活,就存入老年代
优点:
在存活对象不多的情况下,性能高,能解决内存碎片和java垃圾回收算法之-标记清除中导致的引用更新问题。
缺点:
会造成一部分的内存浪费。不过可以根据实际情况,将内存块大小比例适当调整;
如果存活对象的数量比较大,coping的性能会变得很差
-
标记清除算法
标记清除算法分为两个阶段:标记和清除。
它的做法是当堆中的有效内存空间(available memory)被耗尽的时候,就会让整个程序stop然后进行标记再清除
标记:标记的过程其实就是,遍历所有的GC Roots,然后将所有的GC Roots可达的对象标记为存活的对象。
清除:清除的过程将遍历堆中所有的对象中没有标记的对象全部清除掉
缺点:
1、它的缺点就是效率比较低(递归与全堆对象遍历),而且在进行GC的时候,需要停止应用程序。
2、这种方式清理出来的空闲内存是不连续的。我们的死亡对象都是随机的出现在内存的各个角落,把它们都清除之后,内存的布局自然会乱七八糟,为了应付这一点,JVM就不得不维持一个内存的空闲列表,这又是一个开销。而且在分配数组对象的,寻找连续的内存不好找 标记整理
分为标记和整理两个阶段:首先标记出所有需要回收的对象,让所有存活的对象都向一端移动,然后直接清理掉边界意外的内存。
优点:不会产生空间碎片,但是整理会需要一点时间
地方:适合老年代进行垃圾收集,parallel Old(针对parallel scanvange gc的) gc和Serial old收集器就是采用该算法进行回收的。-
分代收集算法
目前商用虚拟机都使用“分代收集算法”,所谓分代就是根据对象的生命周期把内存分为几块,一般把Java堆中分为新生代和老年代,这样就可以根据对象的“年龄”选择合适的垃圾回收算法。- 新生代:“朝生夕死”,存活率低,使用复制算法。
- 老年代:存活率较高,使用“标记-清除”算法或者“标记-整理”算法。
垃圾收集器
如果说收集算法是内存回收的方法论,垃圾收集器就是内存回收的具体实现
Serial收集器
串行收集器是最古老,最稳定以及效率高的收集器,可能会产生较长的停顿,只是用一个线程去回收。新生代、老年代使用串行回收;新生代复制算法、老年代标记 - 压缩;垃圾收集的过程中会Stop The World(服务暂停)
参数控制: -XX:+UseSerialGC 串行收集器
-
ParNew 收集器 ParNew收集器其实就是Serial收集器的多线程版本。新生代并行,老年代串行;新生代复制算法、老年代标记-压缩
参数控制:
-XX:+UseParNewGC ParNew收集器
-XX:ParallelGCThreads 限制线程数量
Parallel收集器
Parallel Scavenge收集器类似ParNew收集器,Parallel收集器更关注系统的吞吐量。可以通过参数来打开自适应调节策略,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或最大的吞吐量;也可以通过参数控制GC的时间不大于多少毫秒或者比例;新生代复制算法、老年代标记-压缩
参数控制: -XX:+UseParallelGC 使用Parallel收集器+ 老年代串行
Parallel Old 收集器
Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。这个收集器是在JDK 1.6中才开始提供
参数控制: -XX:+UseParallelOldGC 使用Parallel收集器+ 老年代并行
CMS收集器
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。目前很大一部分的Java应用都集中在互联网站或B/S系统的服务端上,这类应用尤其重视服务的响应速度,希望系统停顿时间最短,以给用户带来较好的体验。
从名字(包含“Mark Sweep”)上就可以看出CMS收集器是基于“标记-清除”算法实现的,它的运作过程相对于前面几种收集器来说要更复杂一些,整个过程分为4个步骤,包括:
初始标记(CMS initial mark)
并发标记(CMS concurrent mark)
重新标记(CMS remark)
并发清除(CMS concurrent sweep)
其中初始标记、重新标记这两个步骤仍然需要“Stop The World”。初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快,并发标记阶段就是进行GC Roots Tracing的过程,而重新标记阶段则是为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短。
由于整个过程中耗时最长的并发标记和并发清除过程中,收集器线程都可以与用户线程一起工作,所以总体上来说,CMS收集器的内存回收过程是与用户线程一起并发地执行。老年代收集器(新生代使用ParNew)
优点: 并发收集、低停顿
缺点: 产生大量空间碎片、并发阶段会降低吞吐量
参数控制:
-XX:+UseConcMarkSweepGC 使用CMS收集器
-XX:+ UseCMSCompactAtFullCollection Full GC后,进行一次碎片整理;整理过程是独占的,会引起停顿时间变长
-XX:+CMSFullGCsBeforeCompaction 设置进行几次Full GC后,进行一次碎片整理
-XX:ParallelCMSThreads 设定CMS的线程数量(一般情况约等于可用CPU数量)
G1收集器
GcRoot
所谓"GC roots" 或者说tracing GC的"根集合"就是一组必须活跃的引用。
基本思路就是通过一系列名为"GC Roots"的对象作为起始点,从这个被称为GC Roots的对象开始向下搜索,如果一个对象到GC Roots没有任何引用链相连时,则说明此对象不可用。也即给定一个集合的引用作为根出发,通过引用关系遍历对象图,能被遍历到的(可到达的)对象就被判定为存活;没有被遍历到的就自然被判定为死亡
哪些可以作为GC Roots的对象:
虚拟机栈(栈帧中的局部变量区,也叫做局部变量表)中引用的对象
方法区中的类静态属性引用的对象。
方法区中常量引用的对象
本地方法栈中JNI(Native方法)引用的对象