GC

     以栈或寄存器中的引用【GC Roots】为起点,找到堆中的对象,又从这些对象找到对堆中其余对象的引用,这种引用逐步扩展,最终以null引用或者基本类型结束,这样就造成了一颗以Java栈中引用所对应的对象为根节点的一颗对象树,若是栈中有多个引用,则最终会造成多颗对象树。
    在这些对象树上的对象,都是当前系统运行所须要的对象,不能被垃圾回收。而其余剩余对象,则能够视为没法被引用到的对象,能够被当作垃圾进行回收。所以,垃圾回收的起点是一些根对象(java栈, 静态变量, 寄存器…)。而最简单的Java栈就是Java程序执行的main函数。这便是“标记-清除”的回收方式。java

GC Roots包括:算法

  •  虚拟机栈中引用的对象。
  • 方法区中类静态属性实体引用的对象。
  • 方法区中常量引用的对象。
  • 本地方法栈中JNI引用的对象。

分代收集

Minor (Young ) GC

通常状况下,当新对象生成且在Eden申请空间失败时,就会触发Minor GC
对Eden区域进行GC,清除非存活对象,而且把尚且存活的对象移动到Survivor区;而后整理Survivor的两个区。由于大部分对象都是从Eden区开始的,同时Eden区不会分配的很大,因此Eden区的GC会频繁进行。因此通常在这里须要使用速度快、效率高的算法 - 复制,使Eden去能尽快空闲出来。数组

虚拟机给每一个对象定义了一个对象年龄(Age)计数器,能够经过参数 -XX:MaxTenuringThreshold 默认为 15 岁)来设置。
若是对象在 Eden 出生并通过第一次 Scavenge GC 后仍然存活,而且能被 Survivor 容纳的话,将被移动到 Survivor 空间中,并将对象年龄设为 1。对象在 Survivor 区中每熬过一次 Scavenge GC,年龄就增长 1 岁,当它的年龄增长到必定程度时,就会晋升到老年代中。对象晋升老年代的年龄阈值。服务器

Full GC

对整个堆进行整理,包括Young、Tenured和MetaSpace。由于须要对整个对进行回收,因此很慢,所以应该尽量减小Full GC的次数。在对JVM调优的过程当中,很大一部分工做就是对于FullGC的调节。多线程

有以下缘由可能致使Full GC:并发

  1.  年老代(Tenured)无可知足需新分配的内存
      当准备要触发一次young GC时,若是发现统计数据: 以前young GC的平均晋升大小比目前old gen剩余的空间大(分配担保机制),则不会触发young GC而是转为触发full GC。
    (HotSpot VM的GC里,除了CMS的concurrent collection以外,其它能收集old gen的GC都会同时收集整个GC堆,包括young gen,因此不须要事先触发一次单独的young GC)
  2.  System.gc()被显示调用, 系统建议执行Full GC,可是没必要然执行。
  3. 上一次GC以后Heap的各域分配策略动态变化
  4. metaSpace 无可分配知足需新内存

Major GC一般是跟full GC是等价的,收集整个GC堆。但由于HotSpot VM发展了这么多年,外界对各类名词的解读已经彻底混乱了,当说“major GC”的时候必定要问清楚他想要指的是上面的full GC仍是old gen。app

Parallel Scavenge(-XX:+UseParallelGC)框架下:
默认是在要触发full GC前先执行一次young GC,而且两次GC之间能让应用程序稍微运行一小下,以期下降full GC的暂停时间(由于young GC会尽可能清理了young gen的死对象,减小了full GC的工做量)。
以CMS GC (-XX:+UseConcMarkSweepGC)为例,它主要是定时去检查old gen的使用量,当使用量超过了触发比例(-XX:CMSInitiatingOccupancyFraction)就会启动一次CMS GC,对old gen作并发收集。经过配置-XX:+CMSScavengeBeforeRemark开启或关闭在CMS从新标记阶段以前的清除(YGC)尝试。框架

内存分配策略

  • 对象优先在Eden区分配
    大多数状况下,对象在先新生代Eden区中分配。当Eden区没有足够空间进行分配时,虚拟机将发起一次Young GC,Minor GC期间虚拟机将Eden区域的对象移动到其中一块Survivor区域。
  • 大对象直接进入老年代

    所谓"大对象" 就是指一个占用大量连续内存空间的对象。(如很长的字符串及数组)
    若在Eden区 或 当前使用Survior区中存不下,  就须要把Eden区 或 当前使用Survior区的存活对象都移动到老年代中去,而后再将新对象存入Eden区。
    一个大对象可以存入Eden区及 当前使用Survior区的几率比较小, 发生分配担保的几率比较大,  而分配担保须要涉及到大量的复制,就会形成效率低下。
    所以: 大对象直接把放到老年代中去,从而就能避免大量的复制操做
    -XX:PretenureSizeThreshold 参数 该参数用于设置大小超过该参数的对象被认为是"大对象", 直接分配在老年代。(注意:  该参数只对Serial和ParNew收集器有效。)ide

  • 为了使内存分配更加灵活,年龄相同对象的内存大小总和超过了任一Survivor空间的一半, 那么全部年龄相同及超过的对象都会被转移到老年代中,无须等到MaxTenuringThreshold要求的年龄。函数

分配担保

分配担保是老年代为新生代做担保,担保有足够的空间存入新生代可存活的对象, 若是OldGeneration空间还不够就OOM。

发生MinorGC前, JVM首先会检查老年代中最大可用的连续空间是否大于新生代中全部对象的大小。若此条件:

  • 成立:  直接执行minorGC.
  • 不成立: 断定HandlePromotionFailure设置值是否容许担保失败。若容许, 继续检查老年代最大可用的空间是否大于 历次 晋升到老年代对象的平均大小
    • 若大于: 将尝试一次MinorGC,虽然这次MinorGC是有风险的.
    • 若小于或HandlePromotionFailure设置不容许冒险:则进行一次FullGC,经过清除老年代中废弃数据来扩大老年代空闲空间, 以便给新生代做担保。 

在jdk5后,晋升不须要连续空间了。

In 5.0 we added the ability in the low pause collector to start a young generation collection and then to back out of it if there was not enough space in the tenured generation. Being able to backout of a young generation collection allowed us to make a couple of changes. We now keep an average of the amount of space that is used for promotions and use that (with some appropriate padding to be on the safe side) as the requirement for the space needed in the tenured generation. Additionally we no longer need a single contiguous chunk of space for the promotions so we look at the total amount of free space in the tenured generation in deciding if we can do a young generation collection. Not having to have a single contiguous chunk of space to support promotions is where fragmentation comes in (or rather where it doesn't come in as often).
此外,咱们再也不须要一个连续的空间大块晋升,因此咱们经过老年代的自由空间量总数决定是否能够作一个新生代的回收。没必要拥有一个连续的空间来支持晋升活动是兼容碎片化的状况

https://blog.csdn.net/fei33423/article/details/70941113?utm_source=blogxgwz4

GC策略

回收算法

  1. 引用计数(Reference Counting): 
    原理是此对象有一个引用,即增长一个计数,删除一个引用则减小一个计数。垃圾回收时,只用收集计数为0的对象。此算法最致命的是没法处理循环引用的问题。
  2. 复制(Copying):  
    把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾回收时,遍历当前使用区域,把正在使用中的对象复制到另一个区域中。此算法每次只处理正在使用中的对象,所以只须要付出少许存活对象的复制成本就能够完成收集,同时复制过去之后还能进行相应的内存整理,不会出现“碎片”问题。固然,此算法的缺点也是很明显的,就是须要两倍内存空间
  3. 标记-清除(Mark-Sweep): 
    第一阶段从引用根节点开始标记全部被引用的对象,第二阶段遍历整个堆,把未标记的对象清除。此算法须要暂停整个应用,同时会产生内存碎片
  4. 标记-整理(Mark-Compact):
    结合了“标记-清除”和“复制”两个算法的优势。也是分两阶段,从根节点开始标记全部被引用对象完成后,不是清理掉须要回收的对象,而是将全部存活的对象向一端移动,而后将边界之外的内存所有清理掉。避免了“标记-清除”的碎片问题,同时也避免了“复制”算法的空间问题

根据JVM的内存结构对堆进行分代收集:

在新生代中,每次垃圾收集都是有大批对象死去,少许存活; 且分Eden区+2个Survivor区。 比较适合复制算法。
而老年代中,对象存活率高,没有额外空间供其分配,就必须用“标记-清理-整理”算法。 常见如: CMS(Concurrent Mark-Sweep)以牺牲吞吐量为代价来得到最短回收停顿时间。对于要求服务器响应速度的应用上,这种垃圾回收器很是适合。在启动JVM参数加上-XX:+UseConcMarkSweepGC 表示对于老年代的回收采用CMS。其基础算法是:标记—清除。因此须要增长-XX:+UseCMSCompactAtFullCollection、-XX:CMSFullGCsBeforeCompaction来开启“整理”操做。

收集器

串行(Serial)收集: 使用单线程处理全部垃圾回收工做; 

并行(ParNew)收集: 并行收集使用多线程处理垃圾回收工做,于是速度快,效率高。并且理论上CPU数目越多,越能体现出并行收集器的优点。

并发(Parallel)收集: 能够保证大部分工做都并发进行(应用不中止),垃圾回收只暂停不多的时间 。相对于串行收集和并行收集而言,前面两个在进行垃圾回收工做时,须要暂停整个运行环境,而只有垃圾回收程序在运行。所以,系统在垃圾回收时会有明显的暂停,并且暂停时间会由于堆越大而越长(ps: 因此堆内存大小分配须要合理分片,并非越大越好)

相关文章
相关标签/搜索