Java核心08-JVM内存结构垃圾回收机制

JVM内存结构
  • 主流虚拟机(Hotspot VM)的垃圾回收都采用”分代垃圾回收“的算法。”分代回收“是基于对象的生命周期不同,所以针对不同生命周期的对象,可以采取不同的回收方法,以便提高回收效率;
  • Hotspot VM将内存划分为不同的物理区,JVM内存主要由新生代、老年代、永久代构成

        

  1. 新生代(Young Generation)
    1. 大多数对象在新生代中被创建,其中很多对象的生命周期很短。每次新生代的垃圾回收(又称Minor GC)后只有少量对象存活。对象每经历一次Minor GC,年龄加1,达到“晋升年龄阈值”后,被放到老年代。"晋升年龄阈值”通过参数-XX:MaxTenuringThreshold设定,默认值为15。
  2. 老年代(Old Generation)
    1. 在新生代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代,该区域中对象存活率高。老年代的垃圾回收(又称Major GC)通常使用“标记-清除”算法整个堆空间(包括新生代和老年代)的垃圾回收称为Full GC(HotSpot VM里,除了CMS之外,其它能收集老年代的GC都会同时收集整个GC堆,包括新生代)。
  3. 永久代(Perm Generation)
    1. 主要存放元数据,例如Class、Method的元信息,与垃圾回收要回收的Java对象关系不大。垃圾回收主要是回收堆内存空间。
JVM垃圾回收机制
        堆空间的基本结构

        

  1. 堆是垃圾收集器管理的主要区域,因此也被称为GC堆(Garbage Collected Heap)。
  2. Java堆细分为:新生代和老年代,再细分点是:Eden空间,From Survivor、ToSurvivor空间等。进一步划分目的是更好地回收内存,或者更快地分配内存。
        内存分配与回收策略
  1. 对象优先在新生代Eden区分配。当new一个对象后,会先放到新生代中Eden区划分出来的一块作为存储空间的内存。堆内存是线程共享的,所以有可能会出现两个对象共用一个内存的情况。JVM处理是每个线程都会预先申请好一块连续的内存空间并规定对象存放的位置,如果空间不足将会再申请多块内存空间。称为TLAB。
  2. 大对象直接进入老年代。大量连续内存空间的Java对象直接分配到老年代,避免在新生代上来进行复制。(比如:字符串和数组)
  3. 长期存活的对象将进入老年代。当Eden空间满了以后,会触发一个叫做Minor GC(即年轻代的GC)的操作,存活下来的对象移动到Survivor0区。Survivor0区满后触发Minor GC,就会将存活对象移动到Survivor1区,此时会把from和to两个指针交换,保证一段时间内总有一个survivor区为空且to所指向的survivor区为空。经过多次的Minor GC后仍然存活的对象,对象的年龄会加1(HotSpot会在对象头中的标记字段(Mark Word)中记录对象年龄)。当年龄增加到一定程度(默认为15,分配到的空间仅有4位),则会移动到老年代。
  4. 触发Full GC。老年代是存储长期存活的对象的,占满时就会触发Full GC期间会停止所有线程等待GC的完成。所以对于响应要求高的应用,应该尽量去减少发生Full GC,从而避免响应超时的问题。
  5. OOM。当老年区执行了Full GC后,仍然无法进行对象保存的操作,就会产生内存溢出(OOM,Out of Memory)。表明堆内存不足,可能是堆内存设置的大小过小。可通过设置-Xms  -Xmx参数来调整。也可能是代码中创建的对象太且多,而且对象一直在被引用从而长时间无法进行垃圾收集。
        垃圾回收算法
  1. 标记-清除算法
    1. 分为“标记”和“清除”两个阶段,标记出所有需要回收的对象,标记结束后统一回收
    2. 把已死亡的对象标记为空闲内存,然后记录在一个空闲列表中,当new一个对象时,内存管理模块会从空闲列表中寻找空闲的内存来分给新的对象。
    3. 缺点是效率较低,内存中产生大量不连续的碎片空间。导致需要使用较大的内存块时,无法分配到足够的连续内存。
  2. 标记-复制算法
    1. 将可用内存按容量划分成两等分,每次只使用其中的一块。和survivor一样也是用from到to两个指针,fromPlace存满了,就把存活的对象copy复制到另一块toPlace上,然后把使用的空间一次清理掉,并交换指针的内容。从而解决了内存碎片的问题。
    2. 堆内存使用效率变低了。
  3. 标记-整理算法
    1. 标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接堆可回收对象回收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。
  4. 分代收集算法(当前虚拟机常采用)
    1. 根据对象存活周期的不同将内存划分为几块。一般是把Java堆分为新生代和老年代,根据各个年代的特点,采用最适当的收集算法。
    2. 新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,则选择标记-复制算法。只需付出少量的复制成本就可以完成收集;
    3. 老年代中,因为对象存活率高,没有额外空间对它进行分配,则必须使用“标记-清理”或“标记-整理”算法来进行回收。
    4. 这也就是HotSpot要分为新生代和年老代的原因。
JVM垃圾回收器

HotSpot 虚拟机采用分代收集,将堆分为年轻代和老年代,垃圾收集器也按照这样区分。

        1.Serial收集器(单线程,新生代垃圾收集器)
-XX:+UseSerialGC
  1. 串行收集器,单线程收集器在进行垃圾收集工作时,会停止一切用户线程(Stop The World),适合客户端使用。
  2. 新生代采用标记-复制算法,老年代采用标记-整理算法;
  3. 优点是简单而高效,由于没有线程切换交互的系统开销。在并行能力较弱的单CPU 环境下往往表现优于其他收集器。

        

        2.ParNew收集器(多线程,新生代垃圾收集器)
-XX:+UseParNewGC 指定使用ParNew
-XX:+ParallelGCThreads 指定垃圾收集的线程数量,ParNew默认开启的收集线程与CPU数量相同
  1. 可理解为Serial收集器的多线程版本。除了使用多线程进行垃圾收集外,其余行为(控制参数、收集算法、回收策略等等)和 Serial 收集器完全一样。  
  2. 线程切换需要额外的开销,因此在单CPU 环境中表现不如Serial,清理过程依然需要Stop The World.
  3. 新生代采用标记-复制算法,老年代采用标记-整理算法;
  4. 追求降低用户停顿时间,适合交互式应用。

        

        3.Parallel Scavenge收集器(PS 收集器,多线程,新生代垃圾收集器)
-XX:+UseParallelGC
-XX:MaxGCPauseMills 控制最大垃圾收集停顿时间
  1. 其关注点是吞吐量,高效率的利用CPU。
  2. 吞吐量 = 运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间)。
  3. 停顿时间越短适合需要和用户进行交互的程序,良好的响应能够提升用户的体验。而高效的吞吐量可以充分的利用CPU 时间,尽快的完成计算任务,所以PS 收集器适用于后台计算型任务程序。
  4. 对于收集器运作不熟悉,手工优化存在困难时,使用PS 收集器配合自适应调节策略,把内存管理优化交给虚拟机去完成。
  5. 新生代采用标记-复制算法,老年代采用标记-整理算法。

        

        4.Serial Old 收集器(单线程,Serial收集器的老年代垃圾收集器)
  1. 针对老年代,采用"标记-整理"算法,单线程收集;
        5.Parallel Old 收集器
-XX:+UseParallelOldGC
  1. 是PS 收集器的老年代版本。
  2. 针对老年代,采用"标记-整理"算法,多线程收集。
        6.CMS收集器(Concurrent Mark Sweep,并发标记清除,老年代垃圾收集器)
-XX:+UseConcMarkSweepGC

        

  1. 以最短回收停顿时间为目标的收集器(追求低停顿),它在垃圾收集时使得用户线程和GC 线程并发执行,因此在垃圾收集过程中用户也不会感到明显的卡顿。
  2. 优点是:并发收集,低停顿,以获取最短回收停顿时间为目标,提高用户体验;
  3. 缺点是:对CPU资源敏感;无法处理浮动垃圾;使用的回收算法-“标记-清除”算法会导致收集结束时会有大量空间碎片产生。
  4. 是HotSpot虚拟机第一款真正意义上的并发收集器,第一次实现了垃圾收集线程与用户线程(基本上)同时工作。
  5. 采用标记-清除算法实现(Mark-Sweep),执行步骤:
    • 初始标记:暂停所有的其他线程,并记录下直接与root相连的对象,速度很快。
    • 并发标记:同时开启GC线程和用户线程,用一个闭包结构去记录可达对象;
    • 重新标记:为了修正并发标记期间因为用户程序继续运行,而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始化标记阶段时间稍长,远比并发标记阶段时间短;
    • 并发清除:开启用户线程,同时GC线程开始对未标记的区域做清扫。
        7.G1 收集器(Garbage-First)
-XX:+UseG1GC
-XX:MaxGCPauseMills  为G1设置暂停时间目标,默认200ms
  1. G1 的GC模式
    1. 新生代GC:与其他新生代收集器类似,对象优先在eden region分配,如果eden region 内存不足,就会触发新生代的GC,把存活的对象安置在survivor region ,或者晋升到old region。
    2. 混合GC:当越来越多的对象晋升到old region,当老年代的内存使用率达到某个阈值就会触发混合GC。混合GC 会回收新生代和部分老年代内存,注意是部分老年代而不是全部老年代;G1 会跟踪每个Region 中的垃圾回收价值,在用户指定的垃圾收集时间内优先回收价值最大的region。
    3. Full GC:如果对象内存分配速度过快,混合GC 还未回收完成,导致老年代被填满,就会触发一次Full GC,G1 的Full GC 算法就是单线程执行的serial old gc,此过程与CMS类似,会导致异常长时间的暂停时间,尽可能的避免Full GC。
  2. 是一款面向服务端的垃圾收集器,它没有新生代和老年代的概念,而是将堆划分为一块块独立的Region。当要进行垃圾收集时,首先估计每个Region 中垃圾的数量,每次都从垃圾回收价值最大的Region 开始回收,因此可获得最大的回收效率。
  3. 主要针对配备多颗处理器及大容量内存的机器,以极高概率满足GC停顿时间要求的同时,还具备高吞吐量性能特征。
  4. 特点:
    1. 并行与并发:G1 能充分利用CPU、多核环境下的硬件优势,使用多个CPU来缩短Stop-The-World停顿时间。原本需要停顿Java线程执行的GC动作,G1 收集器仍然可以通过并发的方式让Java程序继续执行。
    2. 分代收集:虽然G1 可以不需要其他收集器配合就能独立管理整个GC堆,当还是保留了分代的概念。
    3. 空间整合:与 CMS 的“标记-清理”算法不同,G1 从整体来看是基于“标记-整理”算法实现的收集器;从局部上来看是基于“标记-复制”算法实现的  
    4. 可预测的停顿:这是 G1 相对于 CMS 的另一个大优势,降低停顿时间是 G1 和 CMS 共同的关注点,但 G1 除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为 M 毫秒的时间片段内。
  5. G1收集器运作过程
    1. 初始标记:仅标记一下GC Roots能直接关联到的对象;且修改TAMS(Next Top at Mark Start),让下一阶段并发运行时,用户程序能在正确可用的Region中创建新对象;需要Stop The World,但速度很快。
    2. 并发标记:进行GC Roots Tracing的过程,刚才产生的集合中标记出存活对象;耗时较长,但应用程序也在运行;并不能保证可以标记出所有的存活对象;
    3. 最终标记:为了修正并发标记期间因用户程序继续运作而导致标记变动那部分对象的标记记录;
    4. 筛选回收:首先排序各个Region 的回收价值和成本;然后根据用户期望的GC 停顿时间来制定回收计划;最后按计划回收一些价值高的Region中垃圾对象。
    5. 回收时,采用"复制"算法,从一个或多个Region 复制存活对象到堆上的另一个空的Region ,并且在此过程中压缩和释放内存。可以并发进行,降低停顿时间,并增加吞吐量。

                

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值