GC详解
GC(垃圾回收)详解
JVM
1.年轻代:年轻代主要存放新创建的对象,垃圾回收会比较频繁。(稍微讲细一点就是即可,年轻代分成Eden Space和Suvivor Space。当对象在堆创建时,将进入年轻代的Eden Space。垃圾回收器进行垃圾回收时,扫描Eden Space,如果对象仍然存活,则复制到Suvivor Space。) 2.年老代:年老代主要存放JVM认为生命周期比较长的对象(在扫描Suvivor Space时,如果对象已经经过了几次的扫描仍然存活,JVM认为其为一个持久化对象,则将其移到Old Gen。) 3.持久代:持久代主要存放类定义、字节码和常量等很少会变更的信息。
GC算法基础
1、引用计数法:无法解决循环引用的问题,不被java采纳
2、根搜索算法:从根结点扫描,只要这个对象在引用链中,那就是可触及的。
现代虚拟机中的垃圾搜索算法:
标记清除 复制算法(新生代) 标记压缩(老年代) 这三种算法都扩充了根搜索算法。
1、标记清除算法
概念: 标记清除算法是现代垃圾回收算法的思想基础。标记清除将垃圾回收分为两个阶段:标记阶段和清除阶段。
- 标记阶段:首先通过根结点,标记所有从根节点开始的可达对象。因此未被标记的对象就是未被引用的垃圾对象。
- 清除阶段:在清除阶段,清除所有未被标记的对象。
实践: 在堆中的有效空间被耗尽时,就会停止整个程序(stop the world),然后进行标记清除。
- 标记:遍历所有的GC Roots,然后将所有的GC Roots可达的对象标记为存活的对象。
- 清除:清除的过程将遍历堆中的所有的对象,将没有标记的对象全部清除。
标记清除算法的缺点:
- 等待时间长(递归与全堆对象遍历),导致stop the world的时间很长。
- 不能解决内存碎片的问题
2、复制算法
概念: 将原有的内存空间分为两块,每次只使用其中的一块。在垃圾回收的时候,将存活对象复制到未使用的内存块中,之后,清除正在使用的内存块中的所有对象,交换两个内存的角色,完成垃圾回收。
不适用于存活对象较多的场合,如老年代(复制算法适合做新生代的GC),和标记清除算法相比解决了内存碎片的问题。
复制算法的缺点:
- 空间浪费,复制算法每次都只对半区内存进行回收,复制算法要想使用,最起码对象的存活率要非常低才行,而且重要的是要克服50%的内存浪费。
- 如果要应对所有对象都100%存活的极端情况,就必须要有很大的额外空间担保,所以这种算法不适用很多的对象存活率都很高的时候(老年代)。
- 新生代:Eden区、Surivor区(From区和to区)
现代商业虚拟机都采用这种收集算法来回收新生代,新生代中的对象98%都是“朝生夕死”的,所以并不需要1:1的比例来划分内存空间,而是将内存划分为一块比较大的Eden区和两块比较小的Surivor空间(From区和to区),每次使用Eden和其中一块Surivor。当回收的时候,先根据分代收集算法将年龄大的对象放入老年代,然后将Eden和Surivor中还存活着的对象一次性的复制到另一块Surivor空间上,最后清理掉Eden和刚才用过的Surivor空间。HotSpot虚拟机默认Eden和Surivor的大小比例是8:1,也就是说,每次新生代中可用内存空间为整个新生代容量的90%(80%+10%),只有10%的空间会被浪费。
当然,我们没法保证每次回收都只有不超过10%的对象存活,当Surivor空间不够用时,需要依赖于老年代进行分配,所以大对象直接进入老年代。
3、标记-整理算法
概念: 在标记-清除算法的基础上做了一些优化,和标记-清除算法一样,首先需要从根结点开始,对所有可达对象做一次标记。但是之后,将所有存活的对象压缩到内存一端,然后清理边界外所有的空间。
实践:
- 标记:遍历所有的GC Roots,然后将所有的GC Roots可达的对象标记为存活的对象。
- 整理:移动所有存活的对象,按照内存地址依次排列,然后将末端内存地址以后的内存全部回收。
- 标记-压缩算法不仅可以弥补标记-清除算法的内存碎片问题,也消除了复制算法中,额外内存空间的问题。
标记-整理算法的缺点:
- 效率很低,不仅要标记所有存活的对象,还要整理所有存活对象的引用地址。效率是上面两个算法中最低的。
4、标记-清除算法、复制算法、标记整理算法的总结
-
效率: 复制算法>标记-整理算法>标记-清除算法
-
内存整齐度 :复制算法=标记/整理算法>标记/清除算法。
-
内存利用率: 标记/整理算法=标记/清除算法>复制算法。
5、分代收集算法
概念: 根据对象的存活周期的不同,将堆内存划分为老年代和新生代。存活时间短的对象未新生代,寻获时间长的为老年代。
实践:
-
少量对象存活,适合复制算法,在新生代中,每次GC都会有大量对象死亡,那么就选择用复制算法。
-
大量对象存活,适合标记-清理\标记-压缩算法,在老年代中,对象存活率高,就用标记-清理\标记-压缩算法进行GC。
6、Stop-The_World
- java中一种全局暂停的现象,全局停顿,所有代码停止,native代码可以执行,但不能和JVM交互。多半情况下时由于GC引起的。
危害:
- 长时间服务停止,没有响应(将用户正常工作的线程全部暂停掉)
- 比如上面的这主机和备机:现在是主机在工作,此时如果主机正在GC造成长时间停顿,那么备机就会监测到主机没有工作,于是备机开始工作了;但是主机不工作只是暂时的,当GC结束之后,主机又开始工作了,那么这样的话,主机和备机就同时工作了。主机和备机同时工作其实是非常危险的,很有可能会导致应用程序不一致、不能提供正常的服务等,进而影响生产环境。
7、GC时为什么会有全局停顿?
-
避免无法彻底清理干净
-
GC的工作必须在一个能确保一致性的快照中进行
8、jvm调优
-
首先打印GC信息:
-
-XX:+PrintGC 根据不同场景设置GC的类型:
web接口交互的系统:使用ParNew+CMS组合,可以提高响应时间; 服务型的系统:使用parallel scavenge + parallel old,可以提高吞吐量;
-
设置堆区大小:
-Xms:初始堆大小 -Xmx:最大堆大小 -XX:SurvivoRatio=n:设置年轻代和年老代的比值。
9、什么时候会触发GC?
- 当新对象生成,并且在Eden申请空间失败时,就会触发Minor GC
- 年老代(Tenured)被写满
- 持久代(Perm)被写满
- System.gc()被显示调用=
- 上一次GC之后Heap的各域分配策略动态变化
10、GC的类型
①年轻代垃圾回收器:
-
Serial:
单线程GC,在进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束。
-
ParNew:
就是Serial的多线程版本,除了使用多条线程进行垃圾收集之外,其余都和Serial完全一样。老年代使用CMS时,新生代会默认使用 ParNew,也只有ParNew可以和CMS配合使用。
-
Paraller Scavenge:
并行多线程收集器,server模式下默认GC的方式。停顿时间短,良好的响应速度可以提升用户体验。有自适应调节策略。
②老年代垃圾回收器:
-
Serial Old:
是Servial的老年代版本,同样是一个单线程收集器,每次进行回收都很耗时。
-
Parallel Old:
Parallel Scavenge收集器的老年代版本,多线程
-
CMS:
一种获得最短停顿时间为目标的GC,目前用重视响应速度的服务端上(如BS架构)。使用XX:+UseConcMarkSweepGC指定使用。
优点:响应时间短
缺点:对CPU资源很敏感,会导致应用变慢,总吞吐量会降低
-
G1:
并行与并发:充分利用多CPU的优势,使用多个CPU来缩短Stop-The-World停顿的时间。通过并发的形式让java程序据继续执行。
分代收集:既可以收集新生代也可以收集老年代
空间整合:采用标记-整理算法
可预测停顿:可以建立可预测的停顿时间模型,从而达到降低停顿时间的要求。