JAVA后端知识点碎片化整理 基础篇(十三) 了解GC

目录

 

(1)讲一讲垃圾回收算法

(2)内存泄漏和内存溢出的区别

(3)如何解决内存碎片的问题

(4)如何解决同时存在的对象创建和对象回收问题?

(5)内存分代及生命周期

(6)选择合适的垃圾回收器(for 了解一下)


(1)讲一讲垃圾回收算法

算法一:引用计数法,这是一种经典的方法,具体是对对象设置一个计数器,每增加一个变量对其的引用,引用计数器就增加1;每减少一个,引用计数器就减少1.只要当引用计数器变为0的时候就会被回收。

劣势:1、采用这种办法无论增加减少都是频繁操作对象,增加系统的损耗。2、这种方法无法处理循环引用的情况,就是A引用B,B中也引用A了。这时候相互引用导致无法回收A与B,导致内存泄露。

算法二:标记清除法。这个方法将其分为两个阶段:标记阶段与清除阶段。标记阶段,通过对象,标记所有跟节点开始的可达对象,那么未标记的对象就是未被引用的垃圾对象。在清除阶段,清除掉所有的未标记的对象。

缺点:这种办法回收会导致大量的内存碎片,因为对象在堆中的内存是固定的,删除后到知道整个空间内,有的使用,有的没使用,不利用java内存管理。

算法三:标记整理法:(JAVA old老年代区使用)算法分为三个节点,标记阶段、压缩阶段、清楚阶段。标记阶段和清除阶段不变,知识增加了一个压缩阶段,就是做完标记后,将标记的对象记录开始与结束的地址整体向左移动集中排列到一起,这样清楚将不会生成磁盘碎片。效率比标记清除低。

缺点:压缩阶段会增加系统的消耗,如果对象很多那么系统的损耗非常大,标记对象少的话,效率比较高。

算法四:复制算法(JAVA 新生代采用) 核心思想就是将内存空间分成两块,同一时刻只是用其中一块,垃圾回收时将正在使用的内存中的存活的对象复制到使用的内存中去,然后清除这一内存块中的所有对象。交替使用这两块内存块,不会产生内存碎片达到垃圾回收的目的。(将堆划分为几个部分,根据堆划分的内存区分不同选择不同的GC策略)

确定:这样会使内存空间对折,利用效率太低。

算法五:分代法(JAVA堆采用)

主要思想根据对象的生命周期长短特点将其进行分块,根据每块内存区域的特点,使用不同的回收算法,从而提高垃圾回收效率。比如内存分代划分 new old,对于不同的代采用不同的垃圾回收算法。其中old使用整理清除,from to使用复制算法。

补充一个小的不常见的:增量算法:主要是这样的如果一次性将所有的垃圾进行处理,需要造成系统的长时间的停顿,那么就可以让垃圾收集线程和应用程序交替执行,每次垃圾收集线程只收集一小片区域的内存空间,接着切换到应用程序线程。依次反复,直到垃圾收集完成。使用这种方式,由于在垃圾回收过程中,间断性地执行了应用程序代码,所以能减少系统的停顿时间,但是能较少系统的停顿时间。但是,因为线程切换和上下文转换的消耗,会是的垃圾回收的总体成本上升,系统的吞吐下降。(所以不推荐使用。。。)

(2)内存泄漏和内存溢出的区别

内存泄露:分配出去的内存无法回收(不再使用的对象或者变量仍占有内存空间),在JAVA中内存泄露就是存在一些被分配的对象(可达的,确是无用的)无法被GC回收。(情况1、堆中申请的内存没释放 2、对象已经不再使用,但还在内存中保留着

内存溢出:程序要求内存超出了系统所能分配的范围(比如:栈满还入栈,出现上溢,栈空还出栈 出现下溢)可以看出内存泄露是内存溢出的一种诱因,但不是唯一因素。(1、堆内存溢出,堆中内存是用来生成对象实例和数组的,堆内存(新生代、老年代)2、方法区内存溢出,加载了过多的类或者使用了太多的反射方法、cglib等3、线程栈溢出,线程栈时线程独有一块内存结构,所以线程栈发生问题必定是一个线程运行时产生的错误,一般线程栈溢出是由于递归太深或方法调用层过多导致的)

(3)如何解决内存碎片的问题

内存碎片通常分为内部碎片和外部碎片:1、内部碎片由于采用固定大小的内存分区、当一个进程不能完全使用分给它的固定内存区域时就产生了内部碎片,通常内部碎片难以完全避免。2、外部碎片是由于某些未分配的连续内存区域太小,以致于不能满足任意进程的内存分配请求,从而不能被进程利用的内存区域。

现在普通内存采用的段页式内存分配方式就是将进程的内存区域分为不同的段,然后将每一段由多个固定大小的页组成。通过页表机制,使段内的页可以不必连续处于同一内存区域,从而减少了外部碎片,然而同一页内仍然可能存在少量的内部碎片,只是一页的内存空间就比较小,从而使可能的内存碎片也减少。

(4)如何解决同时存在的对象创建和对象回收问题?

垃圾回收线程GC是回收内存的,而程序运行线程则是消耗和分配内存的,一个回收内存,一个分配内存,从这个点看两者是矛盾的。

解决办法一、从现有的垃圾回收方式中,要进行垃圾回收前,一般都需要暂停整个应用(即:暂停内存分配),然后进行垃圾回收,回收完成后再继续应用。这种方式是最直接的,而且最有效的解决两种矛盾的方式。

缺点:当堆空间支持增大时,垃圾回收的时间也将相应的持续增大,对应应用暂停的时间也会相应增大。一些相应时间要求很高的应用,比如最大暂停时间要求是好几百秒,那么当堆空间大于几个G时,就很有可能超过这个限制。

解决方法二:使用并发垃圾回收算法,使用这种算法,垃圾回收线程与程序运行线程同时运行,在这种方式下,解决了暂停的问题。

缺点:因为需要在新生成对象的同时又要回收对象,算法复杂性会大大增加,系统的处理能力也会相应降低,同时“碎片”问题将会比较难以解决。

(5)内存分代及生命周期

java虚拟机将堆内存划分为新生代、老年代和永久代,永久代是HotSpot虚拟机特有的概念,它使用永久代的方法实现方法区,其他虚拟机没有这一概念,而且HotSpot也有取消永久代的趋势,在JDK1.7中HotSpot开始“去永久化”把原本放在永久代的字符串常量池移出,永久代主要存放常量,类信息,静态变量等数据,与垃圾回收关系不大,新生代和老年代是垃圾回收的主要区域。

HotSpot将新生代划分为三块,一块较大的Eden空间和两块较小的Survivor空间,默认8:1:1,划分的目的是因为HotSpot采用复制算法来回收新生代,设置这个比例是为了充分利用内存空间,减少浪费。新生成的对象只会在Eden区进行分配,(除非对象非常大直接进入老年代),当Eden区没有足够的空间进行分配的时候,这时候Minor GC开始了。

GC开始时,对象只会存在于Eden区和From Survivor;To Survivor区是空的。GC进行时,Eden区中所有的存活对象都会被复制到To Survivor区,而在From Survivor区中,仍存货的对象会根据他们的年龄值决定去向,年龄值达到阈值(默认15,新生代中的对象每熬过一轮垃圾回收,年龄就会增加1,GC分代年龄存储对象的Header中)对象就会被移到老年代中,没有达到阈值的相会被复制到TO Survivor区。接着清空Eden区和From Survivor区,新生代中存货的对象都在To Survivor区。

老年代GC(Full GC /Major GC):Full GC发生在老年代的GC,出现了Full GC 一般会伴随着至少一次的Minor GC(老年代对象大部分是Minor GC过程中从新生代进入老年代),比如:分配担保失败。Full GC的速度一般会比Minor GC慢十倍以上。当老年代内存不足或者显式调用System.gc()方法时,会触发Full GC。

Minor GC(Eden满了)和Full GC(堆满了)的区别:新生代GC(Minor GC)指发生在新生代GC,因为新生代的Java对象大多是朝生夕死,所以Minor GC 非常频繁,一般回收速度比较快。当Eden空间不足以为对象分配内存时,会触发Minor GC。(Eden会频繁的进行GC)            老年代GC(Full GC):Full GC指发生在老年代GC,出现Full GC一般会伴随着至少一次的Minor GC(老年代的对象大部分是Minor GC过程中从新生代进入老年代),比如:分配担保失败。Full GC的湿度一般会比Minor GC慢十倍。当老年代内存不足或者显式调用System。gc()方法,会触发Full GC。(堆上FullGC较少)

(6)选择合适的垃圾回收器(for 了解一下)

 

Serial收集器(老年代收集器):最古老的收集器,他的缺点是当Serial收集器相进行垃圾回收的时候,必须暂停用户的所有进程,即stop the world。到现在为止,他依然是虚拟机运行在client 模式下的默认新生代收集器,与其他收集器相比,对于限定单个CPU的运行环境来说,串行收集器的效率非常高。(单线程、针对新生代、复制算法)

ParNew收集器(新生代收集器)ParNew收集器是Serial收集器新生代的多线程实现,注意在进行垃圾回收的时候依然会Stop the world,只是相比较Serial收集器而言他会运行多条线程进行垃圾回收。ParNew收集器在单CPU的环境中绝对不会比Serial收集器效果好,因为线程开销是一个非常严重的,但是随着CPU数目的增加,多线程ParNew收集器的效果比较好。(多线程、并行垃圾回收器同时多个线程同时开始清理,但是用户线程处于等待状态)(与上面基本一致)

Parallel Scavenge收集器(新生代收集器)Parallel是复制算法的多线程垃圾回收器,与Parnew收集器类似,这个Parallel收集器关注的特点就是吞吐量(吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)),停顿时间越短就越适合需要与用户交互的程序,良好的响应速度能够提升用户的体验;高吞吐量则可以最高效率地利用CPU的时间,尽快地完成程序的运算任务。(多线程、并行垃圾回收器同时多个线程同时开始清理,但是用户线程处于等待状态)(这种模式和上面的区别就是JVM会根据就收集的性能监控信息,动态调整这些参数,以提供最合适的停顿时间和最大的吞吐量,GC自适应调节策略)

CMS收集器(老年代收集器):CMS是一个非常重要的收集器,低停顿低延时是它最大的特点,他的应用十分广泛,我们重点看一下,CMS一种获取最短回收停顿时间为目标的收集器,他使得它和适合用于和用户交互的业务。他是基于标志清除算法实现的。(用户线程和垃圾收集线程同时执行,但不一定是并行的,可能会交替执行)    四个步骤:1、初始标记,仅仅标记GC ROOTS能直接关联的对象 2、并发标记:就是GC ROOTS路径测过程 3、重新标记:修正并发标记期间因为用户程序继续运行而导致标记变动的那一部分对象的标记记录,这个阶段的停顿时间一般比初始标记阶段时间稍长,远远比并发标记阶段时间短。

优点:并发收集、低停顿。CMS收集器的内存回收过程是与用户线程一起并发执行的。

缺点:1、CMS收集器对CPU资源非常敏感,并发阶段虽然不会导致用户线程停顿,但是占用了一部分线程使应用程序变慢,总吞吐量会降低。2、CMS处理器无法处理浮动垃圾,CMS在清理的时候,程序仍然在生产新的垃圾,这一步垃圾CMS无法在此次过程中处理,只有等到下次GC的时候再清理掉。3、CMS基于“标记清除”算法实现,所以再收集的时候回产生大量的空间碎片,空间碎片太多的时候,将会给大对象分配带来麻烦。  当无法找到足够大的连续空间只能提前出发Full GC,用户CMS顶不住要进行full gc的时候开启内存碎片的合并整理过程,内存整理的过程是无法并发的,内存碎片没有了,但停顿时间变长了。

G1收集器:面向服务端应用的垃圾收集器。G1具备如下特点:

1、并行于并发,G1能充分利用CPU、多核环境下的硬件优势,使用多个CPU(CPU或者CPU核心)来缩短STPO-THE-WORLD,部分其他收集器原本需要JAVA线程执行GC操作,G1收集器仍然可以通过并发的方式让JAVA程序继续执行。

2、分代收集:虽然G1可以不需要其他收集器配合就能独立管理整个GC堆,但还是保留了分代的概念,他能够采用不同的方式去处理创新的对象和已经存活一段时间,熬过多次GC的旧对象获取更好的收集结果。

3、空间整合:与CMS“标记-清除”算法不同,G1从整体上来看是基于“标记整理”算法实现的收集器,从局部上来看是基于“复制”算法的实现的。

4、可预测的停顿:这是G1相对于CMS的另一大优势,降低停顿时间G1和CMS共同的关注点,但G1除了降低追求停顿外,还能建立可预测的停顿时间模型,可让使用者名曲指定在一个长度为M毫秒的时间片段内。

G1首先采用一种完全不同的方式来管理内存,先被划分为多个大小相等的heap区,每个heap区都是逻辑上连续的一段内存,其中一部分区域被当成老一代收集器相同的角色,但每个角色的区域个数都是不固定的。这在内存使用上提供了更多的灵活性。

G1执行垃圾回收的处理方式与CMS相似,G1在全局标记阶段并发执行,以确定堆内存中哪些对象是存活的。标记阶段完成后,G1就可以知道哪些heap区的empty空间大。他首先会回收这些区,通常获得大量的自由空间,这就是为什么这种垃圾回收方式叫做垃圾优先。顾名思义,G1将精力集中放在可能布满可回收对象的区域,可回收对象就是所谓的垃圾,G1使用暂停预测模型来达到用户定义的目标暂停时间,并根据目标暂停时间来选择此次进行垃圾回收heap区域数量。

被G1标记为适合手机的heap区会使用转移的方式进行垃圾回收,G1将一个或多个heap区域中的对象拷贝到其他的单个区域中,并且再次过程中压缩和释放内存,在多核CPU上转移是并发执行的,这样能减少停顿时间增加吞吐量,因此每次垃圾回收时,G1都会持续不断地减少碎片,并且在用户给定的暂停时间内执行,这笔以前的方法强大很多,CMS垃圾收集器不进行压缩,ParallelOld垃圾收集支队整个堆执行压缩,从而导致相当长的暂停时间。

吞吐量优先的收集器,ParallelScavenge被选择的原因。

响应时间优先(最短回收停顿)的回收器,使用并发模式回收垃圾。

G1是新的垃圾回收器,商用高性能垃圾回收器,通过重新划分内存区域,整合优化CMS,同时注重吞吐量和响应时间。1、优先在Eden上分配内存,2、大对象进入老年代。3、年长者(长期存活对象)进入老年代。4、群体效应(大批中年对象进入老生代)5、担保GC(就是担保minorGC能够满足当前的存储空间,而无需触发老生代的后手,担保失败会发生FullGC)

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值