范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文
国学影视

GC垃圾回收器

  寻找垃圾算法引用计数法
  引用计数法(Reference Count)会给对象中添加一个引用计数器,每当有一个地方引用它的时候,计数器的值就 +1 ,当引用失效时,计数器值就 -1 ,计数器的值为 0 的对象不可能在被使用,这个时候就可以判定这个对象是垃圾。
  当图中的数值变成0时,这个时候使用引用计数算法就可以判定它是垃圾了,但是引用计数法不能解决一个问题,就是当对象是循环引用的时候,计数器值都不为0,这个时候引用计数器无法通知GC收集器来回收他们,如下图所示:
  这个时候就需要使用到我们的根可达算法。 可达性分析
  根可达算法(Root Searching)的意思是说从根上开始搜索,当一个程序启动后,马上需要的那些个对象就叫做根对象,所谓的根可达算法就是首先找到根对象,然后跟着这根线一直往外找到那些有用的。常见的GC roots如下: 线程栈变量: 线程里面会有线程栈和main栈帧,从这个main() 里面开始的这些对象都是我们的根对象 静态变量: 一个class 它有一个静态的变量,load到内存之后马上就得对静态变量进行初始化,所以静态变量到的对象这个叫做根对象 常量池: 如果你这个class会用到其他的class的那些个类的对象,这些就是根对象 JNI: 如果我们调用了 C和C++ 写的那些本地方法所用到的那些个类或者对象
  图中的 object5 和object6 虽然他们之间互相引用了,但是从根找不到它,所以就是垃圾,而object8没有任何引用自然而然也是垃圾,其他的Object对象都有可以从根找到的,所以是有用的,不会被垃圾回收掉。
  GC Root
  GC Roots 是一组必须活跃的引用。用通俗的话来说,就是程序接下来通过直接引用或者间接引用,能够访问到的潜在被使用的对象。GC Roots 包括: Java 线程中,当前所有正在被调用的方法的引用类型参数、局部变量、临时值等。也就是与我们栈帧相关的各种引用 所有当前被加载的 Java 类 Java 类的引用类型静态变量 运行时常量池里的引用类型常量(String 或 Class 类型) JVM 内部数据结构的一些引用,比如 sun.jvm.hotspot.memory.Universe 类 用于同步的监控对象,比如调用了对象的 wait() 方法 JNI handles,包括 global handles 和 local handles
  GC Roots 大体可以分为三大类: 活动线程相关的各种引用 类的静态变量的引用 JNI 引用 清理垃圾算法
  清理垃圾算法又叫内存回收算法。 标记(Mark)
  垃圾回收的第一步,就是找出活跃的对象。根据 GC Roots 遍历所有的可达对象,这个过程,就叫作标记。
  如图所示,圆圈代表的是对象。绿色的代表 GC Roots,红色的代表可以追溯到的对象。可以看到标记之后,仍然有多个灰色的圆圈,它们都是被回收的对象。 清除(Sweep)
  清除阶段就是把未被标记的对象回收掉。
  但是这种简单的清除方式,有一个明显的弊端,那就是碎片问题。比如我申请了 1k、2k、3k、4k、5k 的内存。
  由于某种原因 ,2k 和 4k 的内存,我不再使用,就需要交给垃圾回收器回收。
  这个时候,我应该有足足 6k 的空闲空间。接下来,我打算申请另外一个 5k 的空间,结果系统告诉我内存不足了。系统运行时间越长,这种碎片就越多。在很久之前使用 Windows 系统时,有一个非常有用的功能,就是内存整理和磁盘整理,运行之后有可能会显著提高系统性能。这个出发点是一样的。 复制(Copying)
  优点 因为是对整个半区进行内存回收,内存分配时不用考虑内存碎片等情况。实现简单,效率较高
  不足之处 既然要复制,需要提前预留内存空间,有一定的浪费 在对象存活率较高时,需要复制的对象较多,效率将会变低 整理(Compact)
  其实,不用分配一个对等的额外空间,也是可以完成内存的整理工作。可以把内存想象成一个非常大的数组,根据随机的 index 删除了一些数据。那么对整个数组的清理,其实是不需要另外一个数组来进行支持的,使用程序就可以实现。它的主要思路,就是移动所有存活的对象,且按照内存地址顺序依次排列,然后将末端内存地址以后的内存全部回收。
  但是需要注意,这只是一个理想状态。对象的引用关系一般都是非常复杂的,我们这里不对具体的算法进行描述。你只需要了解,从效率上来说,一般整理算法是要低于复制算法的。 扩展回收算法
  目前JVM的垃圾回收器都是对几种朴素算法的发扬光大(没有最优的算法,只有最合适的算法): 复制算法(Copying):复制算法是所有算法里面效率最高的,缺点是会造成一定的空间浪费 标记-清除(Mark-Sweep):效率一般,缺点是会造成内存碎片问题 标记-整理(Mark-Compact):效率比前两者要差,但没有空间浪费,也消除了内存碎片问题
  标记清除(Mark-Sweep)
  首先从 GC Root 开始遍历对象图,并标记(Mark)所遇到的每个对象,标识出所有要回收的对象。然后回收器检查堆中每一个对象,并将所有未被标记的对象进行回收。
  不足之处 标记、清除的效率都不高 清除后产生大量的内存碎片,空间碎片太多会导致在分配大对象时无法找到足够大的连续内存,从而不得不触发另一次垃圾回收动作 标记整理(Mark-Compact)
  与标记清除算法类似,但不是在标记完成后对可回收对象进行清理,而是将所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。
  优点 消除了标记清除导致的内存分散问题,也消除了复制算法中内存减半的高额代价
  不足之处 效率低下,需要标记所有存活对象,还要标记所有存活对象的引用地址。效率上低于复制算法 标记复制(Mark-Coping)
  标记-复制算法将内存分为两块相同大小的区域(比如新生代的Survivor区),每次在其中一块区域分配元素,当这块区域内存占满时,就会将存活下来的元素复制到另一块内存区域并清空当前内存区域。 缺点:浪费一半的内存空间 优点:简单高效
  JVM在Eden区保存新对象,在GC时,将Eden和Survivor中存活对象复制到Survivor的另一个分区。这是JVM对复制算法的一个优化。只浪费了1/10的内存空间【JVM的Eden区和Survivor区的比例为 8:2】
  分代收集(Generational Collection)
  分代收集就是根据对象的存活周期将内存分为新生代和老年代。 新生代对象"朝生夕死",每次收集都有大量对象(99%)死去,所以可以选择标记-复制算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集 老年代对象生存几率比较高,存活对象比较多,如果选择复制算法需要付出较高的IO成本,而且没用额外的空间可以用于复制,此时选择标记-清除或者标记-整理就比较合理
  研究表明大部分对象可以分为两类: 大部分对象的生命周期都很短 其他对象则很可能会存活很长时间
  根据对象存活周期的不同将内存划分为几块。对不同周期的对象采取不同的收集算法: 新生代:每次垃圾收集会有大批对象回收,所以采取复制算法 老年代:对象存活率高,采取标记清理或者标记整理算法
  ① 年轻代(Young Generation)
  年轻代使用的垃圾回收算法是复制算法。因为年轻代发生 GC 后,只会有非常少的对象存活,复制这部分对象是非常高效的。但复制算法会造成一定的空间浪费,所以年轻代中间也会分很多区域。
  如图所示,年轻代分为:1个伊甸园空间(Eden ),2个幸存者空间(Survivor )。当年轻代中的 Eden 区分配满的时候,就会触发年轻代的 GC(Minor GC)。具体过程如下: 在 Eden 区执行了第一次 GC 之后,存活的对象会被移动到其中一个 Survivor 分区(以下简称from) Eden 区再次 GC,这时会采用复制算法,将 Eden 和 from 区一起清理。存活的对象会被复制到 to 区,然后只需要清空 from 区就可以了
  在这个过程中,总会有1个 Survivor 分区是空置的。Eden、from、to 的默认比例是 8:1:1,所以只会造成 10% 的空间浪费。这个比例,是由参数 -XX:SurvivorRatio 进行配置的(默认为 8)。
  ② 老年代(Old/Tenured Generation)
  老年代一般使用"标记-清除"、"标记-整理"算法,因为老年代的对象存活率一般是比较高的,空间又比较大,拷贝起来并不划算,还不如采取就地收集的方式。对象进入老年代的途径如下: 提升(Promotion) 如果对象够老,会通过"提升"进入老年代 分配担保 年轻代回收后存活的对象大于10%时,因Survivor空间不够存储,对象就会直接在老年代上分配 大对象直接在老年代分配 超出某个大小的对象将直接在老年代分配 动态对象年龄判定 有的垃圾回收算法,并不要求 age 必须达到 15 才能晋升到老年代,它会使用一些动态的计算方法。 比如,如果幸存区中相同年龄对象大小的和,大于幸存区的一半,大于或等于 age 的对象将会直接进入老年代。 分区收集GC垃圾收集器
  GC垃圾收集器的JVM配置参数: -XX:+UseSerialGC:年轻代和老年代都用串行收集器 -XX:+UseParNewGC:年轻代使用 ParNew,老年代使用 Serial Old -XX:+UseParallelGC:年轻代使用 ParallerGC,老年代使用 Serial Old -XX:+UseParallelOldGC:新生代和老年代都使用并行收集器 -XX:+UseConcMarkSweepGC:表示年轻代使用 ParNew,老年代的用 CMS -XX:+UseG1GC:使用 G1垃圾回收器 -XX:+UseZGC:使用 ZGC 垃圾回收器 年轻代收集器Serial收集器
  处理GC的只有一条线程,并且在垃圾回收的过程中暂停一切用户线程。最简单的垃圾回收器,但千万别以为它没有用武之地。因为简单,所以高效,它通常用在客户端应用上。因为客户端应用不会频繁创建很多对象,用户也不会感觉出明显的卡顿。相反,它使用的资源更少,也更轻量级。
  ParNew收集器
  ParNew是Serial的多线程版本。由多条GC线程并行地进行垃圾清理。清理过程依然要停止用户线程。ParNew 追求"低停顿时间",与 Serial 唯一区别就是使用了多线程进行垃圾收集,在多 CPU 环境下性能比 Serial 会有一定程度的提升;但线程切换需要额外的开销,因此在单 CPU 环境中表现不如 Serial。
  Parallel Scavenge收集器
  另一个多线程版本的垃圾回收器。它与ParNew的主要区别是: Parallel Scavenge:追求CPU吞吐量,能够在较短时间完成任务,适合没有交互的后台计算。弱交互强计算 ParNew:追求降低用户停顿时间,适合交互式应用。强交互弱计算 老年代收集器Serial Old收集器
  与年轻代的 Serial 垃圾收集器对应,都是单线程版本,同样适合客户端使用。年轻代的 Serial,使用复制算法。老年代的 Old Serial,使用标记-整理算法。
  Parallel Old收集器
  Parallel Old 收集器是 Parallel Scavenge 的老年代版本,追求 CPU 吞吐量。
  CMS收集器
  并发标记清除(Concurrent Mark Sweep,CMS)垃圾回收器,是一款致力于获取最短停顿时间的收集器,使用多个线程来扫描堆内存并标记可被清除的对象,然后清除标记的对象。在下面两种情形下会暂停工作线程: 在老年代中标记引用对象的时候 在做垃圾回收的过程中堆内存中有变化发生
  对比与并行垃圾回收器,CMS回收器使用更多的CPU来保证更高的吞吐量。如果我们可以有更多的CPU用来提升性能,那么CMS垃圾回收器是比并行回收器更好的选择。使用 -XX:+UseParNewGCJVM 参数来开启使用CMS垃圾回收器。
  主要流程如下: 初始标记(CMS initial mark):仅标记出GC Roots能直接关联到的对象。需要Stop-the-world 并发标记(CMS concurrenr mark):进行GC Roots遍历的过程,寻找出所有可达对象 重新标记(CMS remark):修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录。需要Stop-the-world 并发清除(CMS concurrent sweep):清出垃圾
  CMS触发机制:当老年代的使用率达到80%时,就会触发一次CMS GC。 -XX:CMSInitiatingOccupancyFraction=80 -XX:+UseCMSInitiatingOccupancyOnly
  优点 并发收集 停顿时间最短
  缺点 并发回收导致CPU资源紧张
  在并发阶段,它虽然不会导致用户线程停顿,但却会因为占用了一部分线程而导致应用程序变慢,降低程序总吞吐量。CMS默认启动的回收线程数是:(CPU核数 + 3)/ 4,当CPU核数不足四个时,CMS对用户程序的影响就可能变得很大。 无法清理浮动垃圾
  在CMS的并发标记和并发清理阶段,用户线程还在继续运行,就还会伴随有新的垃圾对象不断产生,但这一部分垃圾对象是出现在标记过程结束以后,CMS无法在当次收集中处理掉它们,只好留到下一次垃圾收集时再清理掉。这一部分垃圾称为"浮动垃圾"。 并发失败(Concurrent Mode Failure)
  由于在垃圾回收阶段用户线程还在并发运行,那就还需要预留足够的内存空间提供给用户线程使用,因此CMS不能像其他回收器那样等到老年代几乎完全被填满了再进行回收,必须预留一部分空间供并发回收时的程序运行使用。默认情况下,当老年代使用了 80% 的空间后就会触发 CMS 垃圾回收,这个值可以通过 -XX*   CMSInitiatingOccupancyFraction 参数来设置。
  这里会有一个风险:要是CMS运行期间预留的内存无法满足程序分配新对象的需要,就会出现一次"并发失败"(Concurrent Mode Failure),这时候虚拟机将不得不启动后备预案:Stop The World,临时启用 Serial Old 来重新进行老年代的垃圾回收,这样一来停顿时间就很长了。 内存碎片问题
  CMS是一款基于"标记-清除"算法实现的回收器,这意味着回收结束时会有内存碎片产生。内存碎片过多时,将会给大对象分配带来麻烦,往往会出现老年代还有很多剩余空间,但就是无法找到足够大的连续空间来分配当前对象,而不得不提前触发一次 Full GC 的情况。
  为了解决这个问题,CMS收集器提供了一个 -XX:+UseCMSCompactAtFullCollection 开关参数(默认开启),用于在 Full GC 时开启内存碎片的合并整理过程,由于这个内存整理必须移动存活对象,是无法并发的,这样停顿时间就会变长。还有另外一个参数 -XX:CMSFullGCsBeforeCompaction,这个参数的作用是要求CMS在执行过若干次不整理空间的 Full GC 之后,下一次进入 Full GC 前会先进行碎片整理(默认值为0,表示每次进入 Full GC 时都进行碎片整理)。
  作用内存区域:老年代
  适用场景:对停顿时间敏感的场合
  算法类型:标记-清除 新生代和老年代收集G1收集器
  G1(Garbage First)回收器采用面向局部收集的设计思路和基于Region的内存布局形式,是一款主要面向服务端应用的垃圾回收器。G1设计初衷就是替换 CMS,成为一种全功能收集器。G1 在JDK9 之后成为服务端模式下的默认垃圾回收器,取代了 Parallel Scavenge 加 Parallel Old 的默认组合,而 CMS 被声明为不推荐使用的垃圾回收器。G1从整体来看是基于 标记-整理 算法实现的回收器,但从局部(两个Region之间)上看又是基于 标记-复制 算法实现的。G1 回收过程,G1 回收器的运作过程大致可分为四个步骤: 初始标记(会STW):仅仅只是标记一下 GC Roots 能直接关联到的对象,并且修改TAMS指针的值,让下一阶段用户线程并发运行时,能正确地在可用的Region中分配新对象。这个阶段需要停顿线程,但耗时很短,而且是借用进行Minor GC的时候同步完成的,所以G1收集器在这个阶段实际并没有额外的停顿。 并发标记:从 GC Roots 开始对堆中对象进行可达性分析,递归扫描整个堆里的对象图,找出要回收的对象,这阶段耗时较长,但可与用户程序并发执行。当对象图扫描完成以后,还要重新处理在并发时有引用变动的对象。 最终标记(会STW):对用户线程做短暂的暂停,处理并发阶段结束后仍有引用变动的对象。 清理阶段(会STW):更新Region的统计数据,对各个Region的回收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划,可以自由选择任意多个Region构成回收集,然后把决定回收的那一部分Region的存活对象复制到空的Region中,再清理掉整个旧Region的全部空间。这里的操作涉及存活对象的移动,必须暂停用户线程,由多条回收器线程并行完成的。
  G1收集器中的堆内存被划分为多个大小相等的内存块(Region),每个Region是逻辑连续的一段内存,结构如下:
  每个Region被标记了E、S、O和H,说明每个Region在运行时都充当了一种角色,其中H是以往算法中没有的,它代表Humongous(巨大的),这表示这些Region存储的是巨型对象(humongous object,H-obj),当新建对象大小超过Region大小一半时,直接在新的一个或多个连续Region中分配,并标记为H。
  Region
  堆内存中一个Region的大小可以通过 -XX:G1HeapRegionSize参数指定,大小区间只能是1M、2M、4M、8M、16M和32M,总之是2的幂次方。如果G1HeapRegionSize为默认值,则在堆初始化时计算Region的实际大小,默认把堆内存按照2048份均分,最后得到一个合理的大小。
  GC模式
  young gc 发生在年轻代的GC算法,一般对象(除了巨型对象)都是在eden region中分配内存,当所有eden region被耗尽无法申请内存时,就会触发一次young gc,这种触发机制和之前的young gc差不多,执行完一次young gc,活跃对象会被拷贝到survivor region或者晋升到old region中,空闲的region会被放入空闲列表中,等待下次被使用。 -XX:MaxGCPauseMillis:设置G1收集过程目标时间,默认值200ms -XX:G1NewSizePercent:新生代最小值,默认值5% -XX:G1MaxNewSizePercent:新生代最大值,默认值60%
  mixed gc
  当越来越多的对象晋升到老年代old region时,为了避免堆内存被耗尽,虚拟机会触发一个混合的垃圾收集器,即mixed gc,该算法并不是一个old gc,除了回收整个young region,还会回收一部分的old region,这里需要注意:是一部分老年代,而不是全部老年代,可以选择哪些old region进行收集,从而可以对垃圾回收的耗时时间进行控制
  full gc
  如果对象内存分配速度过快,mixed gc来不及回收,导致老年代被填满,就会触发一次full gc,G1的full gc算法就是单线程执行的serial old gc,会导致异常长时间的暂停时间,需要进行不断的调优,尽可能的避免full gc
  G1垃圾回收器 应用于大的堆内存空间。它将堆内存空间划分为不同的区域,对各个区域并行地做回收工作。G1在回收内存空间后还立即对空闲空间做整合工作以减少碎片。CMS却是在全部停止(stop the world,STW)时执行内存整合工作。对于不同的区域G1根据垃圾的数量决定优先级。使用 -XX:UseG1GCJVM 参数来开启使用G1垃圾回收器。
  主要流程如下: 初始标记(Initial Marking):标记从GC Root可达的对象。会发生STW 并发标记(Concurrenr Marking):标记出GC Root可达对象衍生出去的存活对象,并收集各个Region的存活对象信息。整个过程gc collector线程与应用线程可以并行执行 最终标记(Final Marking):标记出在并发标记过程中遗漏的,或内部引用发生变化的对象。会发生STW 筛选回收(Live Data Counting And Evacution):垃圾清除过程,如果发现一个Region中没有存活对象,则把该Region加入到空闲列表中
  -XX:InitiatingHeapOccupancyPercent:当老年代大小占整个堆大小百分比达到该阈值时,会触发一次mixed gc。
  优点: 并行与并发,充分发挥多核优势 分代收集,所以不需要与其它收集器配合即可工作 空间整合,整体来看基于"标记-整理算法",局部采用"复制算法"都不会产生内存碎片 可以指定GC最大停顿时长
  缺点: 需要记忆集来记录新生代和老年代之间的引用关系 需要占用大量的内存,可能达到整个堆内存容量的20%甚至更多
  作用内存区域:跨代
  适用场景:作为关注停顿时间的场景的收集器备选方案
  算法类型:整体来看基于"标记-整理算法",局部采用"复制算法" ZGC收集器
  Z Garbage Collector,简称 ZGC,是 JDK 11 中新加入的尚在实验阶段的低延迟垃圾收集器。它和 Shenandoah 同属于超低延迟的垃圾收集器,但在吞吐量上比 Shenandoah 有更优秀的表现,甚至超过了 G1,接近了"吞吐量优先"的 Parallel 收集器组合,可以说近乎实现了"鱼与熊掌兼得"。
  与CMS中的ParNew和G1类似,ZGC也采用标记-复制算法,不过ZGC对该算法做了重大改进:ZGC在标记、转移和重定位阶段几乎都是并发的,这是ZGC实现停顿时间小于10ms目标的最关键原因。ZGC垃圾回收周期如下图所示:
  ZGC只有三个STW阶段:初始标记,再标记,初始转移。其中,初始标记和初始转移分别都只需要扫描所有GC Roots,其处理时间和GC Roots的数量成正比,一般情况耗时非常短;再标记阶段STW时间很短,最多1ms,超过1ms则再次进入并发标记阶段。即,ZGC几乎所有暂停都只依赖于GC Roots集合大小,停顿时间不会随着堆的大小或者活跃对象的大小而增加。与ZGC对比,G1的转移阶段完全STW的,且停顿时间随存活对象的大小增加而增加。
  ZGC 的内存布局
  与 Shenandoah 和 G1 一样,ZGC 也采用基于 Region 的堆内存布局,但与它们不同的是, ZGC 的 Region 具有动态性,也就是可以动态创建和销毁,容量大小也是动态的,有大、中、小三类容量:
  小型 Region (Small Region):容量固定为 2MB,用于放置小于 256KB 的小对象 中型 Region (M edium Region):容量固定为 32MB,用于放置大于等于 256KB 但小于 4MB 的对象 大型 Region (Large Region):容量不固定,可以动态变化,但必须为 2MB 的整数倍,用于放置 4MB 或以上的大对象。每个大型 Region 中只会存放一个大对象,这也预示着虽然名字叫作"大型 Region",但它的实际容量完全有可能小于中型 Region,最小容量可低至 4MB
  在 JDK 11 及以上版本,可以通过以下参数开启 ZGC:-XX:+UnlockExperimentalVMOptions -XX:+UseZGC 。 Shenandoah收集器
  Shenandoah 与 G1 有很多相似之处,比如都是基于 Region 的内存布局,都有用于存放大对象的 Humongous Region,默认回收策略也是优先处理回收价值最大的 Region。不过也有三个重大的区别: Shenandoah支持并发的整理算法,G1整理阶段虽是多线程并行,但无法与用户程序并发执行 默认不使用分代收集理论 使用连接矩阵 (Connection Matrix)记录跨Region的引用关系,替换掉了G1中的记忆级(Remembered Set),内存和计算成本更低
  Shenandoah 收集器的工作原理相比 G1 要复杂不少,其运行流程示意图如下:
  可见Shenandoah的并发程度明显比G1更高,只需要在初始标记、最终标记、初始引用更新和最终引用更新这几个阶段进行短暂的"Stop The World",其他阶段皆可与用户程序并发执行,其中最重要的并发标记、并发回收和并发引用更新详情如下: 并发标记( Concurrent Marking) 并发回收( Concurrent Evacuation) 并发引用更新( Concurrent Update Reference)
  Shenandoah 的高并发度让它实现了超低的停顿时间,但是更高的复杂度也伴随着更高的系统开销,这在一定程度上会影响吞吐量,下图是 Shenandoah 与之前各种收集器在停顿时间维度和系统开销维度上的对比:
  OracleJDK 并不支持 Shenandoah,如果你用的是 OpenJDK 12 或某些支持 Shenandoah 移植版的 JDK 的话,可以通过以下参数开启 Shenandoah:-XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC 。 GC日志日志格式
  ParallelGC YoungGC日志
  ParallelGC FullGC日志
  最佳实践
  在不同的 JVM 的不垃圾回收器上,看参数默认是什么,不要轻信别人的建议,命令行示例如下: java -XX:+PrintFlagsFinal -XX:+UseG1GC  2>&1 | grep UseAdaptiveSizePolicy
  PrintCommandLineFlags:通过它,你能够查看当前所使用的垃圾回收器和一些默认的值。 # java -XX:+PrintCommandLineFlags -version -XX:InitialHeapSize=127905216 -XX:MaxHeapSize=2046483456 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC openjdk version "1.8.0_41" OpenJDK Runtime Environment (build 1.8.0_41-b04) OpenJDK 64-Bit Server VM (build 25.40-b25, mixed mode)
  G1垃圾收集器JVM参数最佳实践: # 1.基本参数 -server                  # 服务器模式 -Xmx12g                  # 初始堆大小 -Xms12g                  # 最大堆大小 -Xss256k                 # 每个线程的栈内存大小 -XX:+UseG1GC             # 使用 G1 (Garbage First) 垃圾收集器    -XX:MetaspaceSize=256m   # 元空间初始大小 -XX:MaxMetaspaceSize=1g  # 元空间最大大小 -XX:MaxGCPauseMillis=200 # 每次YGC / MixedGC 的最多停顿时间 (期望最长停顿时间)  # 2.必备参数 -XX:+PrintGCDetails            # 输出详细GC日志 -XX:+PrintGCDateStamps         # 输出GC的时间戳(以日期的形式,如 2013-05-04T21:53:59.234+0800) -XX:+PrintTenuringDistribution # 打印对象分布:为了分析GC时的晋升情况和晋升导致的高暂停,看对象年龄分布日志 -XX:+PrintHeapAtGC                 # 在进行GC的前后打印出堆的信息 -XX:+PrintReferenceGC              # 打印Reference处理信息:强引用/弱引用/软引用/虚引用/finalize方法万一有问题 -XX:+PrintGCApplicationStoppedTime # 打印STW时间 -XX:+PrintGCApplicationConCurrentTime # 打印GC间隔的服务运行时长  # 3.日志分割参数 -XX:+UseGCLogFileRotation   # 开启日志文件分割 -XX:NumberOfGCLogFiles=14   # 最多分割几个文件,超过之后从头文件开始写 -XX:GCLogFileSize=32M       # 每个文件上限大小,超过就触发分割 -Xloggc:/path/to/gc-%t.log  # GC日志输出的文件路径,使用%t作为日志文件名,即gc-2021-03-29_20-41-47.log
  CMS垃圾收集器JVM参数最佳实践: # 1.基本参数 -server   # 服务器模式 -Xmx4g    # JVM最大允许分配的堆内存,按需分配 -Xms4g    # JVM初始分配的堆内存,一般和Xmx配置成一样以避免每次gc后JVM重新分配内存 -Xmn256m  # 年轻代内存大小,整个JVM内存=年轻代 + 年老代 + 持久代 -Xss512k  # 设置每个线程的堆栈大小 -XX:+DisableExplicitGC                # 忽略手动调用GC, System.gc()的调用就会变成一个空调用,完全不触发GC -XX:+UseConcMarkSweepGC               # 使用 CMS 垃圾收集器 -XX:+CMSParallelRemarkEnabled         # 降低标记停顿 -XX:+UseCMSCompactAtFullCollection    # 在FULL GC的时候对年老代的压缩 -XX:+UseFastAccessorMethods           # 原始类型的快速优化 -XX:+UseCMSInitiatingOccupancyOnly    # 使用手动定义初始化定义开始CMS收集 -XX:LargePageSizeInBytes=128m         # 内存页的大小 -XX:CMSInitiatingOccupancyFraction=70 # 使用cms作为垃圾回收使用70%后开始CMS收集  # 2.必备参数 -XX:+PrintGCDetails                # 输出详细GC日志 -XX:+PrintGCDateStamps             # 输出GC的时间戳(以日期的形式,如 2013-05-04T21:53:59.234+0800) -XX:+PrintTenuringDistribution     # 打印对象分布:为分析GC时的晋升情况和晋升导致的高暂停,看对象年龄分布 -XX:+PrintHeapAtGC                 # 在进行GC的前后打印出堆的信息 -XX:+PrintReferenceGC              # 打印Reference处理信息:强引用/弱引用/软引用/虚引用/finalize方法万一有问题 -XX:+PrintGCApplicationStoppedTime # 打印STW时间 -XX:+PrintGCApplicationConCurrentTime # 打印GC间隔的服务运行时长  # 3.日志分割参数 -XX:+UseGCLogFileRotation   # 开启日志文件分割 -XX:NumberOfGCLogFiles=14   # 最多分割几个文件,超过之后从头文件开始写 -XX:GCLogFileSize=32M       # 每个文件上限大小,超过就触发分割 -Xloggc:/path/to/gc-%t.log  # GC日志输出的文件路径,使用%t作为日志文件名,即gc-2021-03-29_20-41-47.log
  test、stage 环境jvm使用CMS 参数配置(jdk8) -server -Xms256M -Xmx256M -Xss512k -Xmn96M -XX:MetaspaceSize=128M -XX:MaxMetaspaceSize=128M -XX:InitialHeapSize=256M -XX:MaxHeapSize=256M  -XX:+PrintCommandLineFlags -XX:+UseConcMarkSweepGC -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=80 -XX:+CMSClassUnloadingEnabled -XX:+CMSParallelRemarkEnabled -XX:+CMSScavengeBeforeRemark -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=2 -XX:+CMSParallelInitialMarkEnabled -XX:+CMSParallelRemarkEnabled -XX:+UnlockDiagnosticVMOptions -XX:+ParallelRefProcEnabled -XX:+AlwaysPreTouch -XX:MaxTenuringThreshold=8  -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+PrintGC -XX:+PrintGCApplicationConcurrentTime -XX:+PrintGCApplicationStoppedTime -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintHeapAtGC  -XX:+PrintTenuringDistribution  -XX:SurvivorRatio=8 -Xloggc:../logs/gc.log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=../dump
  online 环境jvm使用CMS参数配置(jdk8)  -server -Xms4G -Xmx4G -Xss512k  -Xmn1536M -XX:MetaspaceSize=128M -XX:MaxMetaspaceSize=128M -XX:InitialHeapSize=4G -XX:MaxHeapSize=4G  -XX:+PrintCommandLineFlags -XX:+UseConcMarkSweepGC -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=80 -XX:+CMSClassUnloadingEnabled -XX:+CMSParallelRemarkEnabled -XX:+CMSScavengeBeforeRemark -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=2 -XX:+CMSParallelInitialMarkEnabled -XX:+CMSParallelRemarkEnabled -XX:+UnlockDiagnosticVMOptions -XX:+ParallelRefProcEnabled -XX:+AlwaysPreTouch -XX:MaxTenuringThreshold=10  -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+PrintGC -XX:+PrintGCApplicationConcurrentTime -XX:+PrintGCApplicationStoppedTime -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintHeapAtGC  -XX:+PrintTenuringDistribution  -XX:SurvivorRatio=8 -Xloggc:../logs/gc.log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=../dumpGC场景Full GC场景
  场景一:System.gc()方法的调用
  此方法的调用是建议JVM进行Full GC,虽然只是建议而非一定,但很多情况下它会触发 Full GC,从而增加Full GC的频率,也即增加了间歇性停顿的次数。强烈影响系建议能不使用此方法就别使用,让虚拟机自己去管理它的内存,可通过通过 -XX:+ DisableExplicitGC 来禁止RMI调用System.gc()。
  场景二:老年代代空间不足 原因分析:新生代对象转入老年代、创建大对象或数组时,执行FullGC后仍空间不足 抛出错误:Java.lang.OutOfMemoryError: Java heap space 解决办法:尽量让对象在YoungGC时被回收让对象在新生代多存活一段时间不要创建过大的对象或数组
  场景三:永生区空间不足 原因分析:JVM方法区因系统中要加载的类、反射的类和调用的方法较多而可能会被占满 抛出错误:java.lang.OutOfMemoryError: PermGen space 解决办法:增大老年代空间大小使用CMS GC
  场景四:CMS GC时出现promotion failed和concurrent mode failure 原因分析:promotion failed:是在进行Minor GC时,survivor space放不下、对象只能放入老年代,而此时老年代也放不下造成concurrent mode failure:是在执行CMS GC的过程中同时有对象要放入老年代,而此时老年代空间不足造成的 抛出错误:GC日志中存在promotion failed和concurrent mode 解决办法:增大幸存区或老年代
  场景五:堆中分配很大的对象 原因分析:创建大对象或长数据时,此对象直接进入老年代,而老年代虽有很大剩余空间,但没有足够的连续空间来存储 抛出错误:触发FullGC 解决办法:配置-XX:+UseCMSCompactAtFullCollection开关参数,用于享受用完FullGC后额外免费赠送的碎片整理过程,但同时停顿时间不得不变长。可以使用-XX:CMSFullGCsBeforeCompaction参数来指定执行多少次不压缩的FullGC后才执行一次压缩 CMS GC场景
  场景一:动态扩容引起的空间震荡 现象
  服务刚刚启动时 GC 次数较多,最大空间剩余很多但是依然发生 GC,这种情况我们可以通过观察 GC 日志或者通过监控工具来观察堆的空间变化情况即可。GC Cause 一般为 Allocation Failure,且在 GC 日志中会观察到经历一次 GC ,堆内各个空间的大小会被调整,如下图所示:
  原因
  在 JVM 的参数中 -Xms 和 -Xmx 设置的不一致,在初始化时只会初始 -Xms 大小的空间存储信息,每当空间不够用时再向操作系统申请,这样的话必然要进行一次 GC。另外,如果空间剩余很多时也会进行缩容操作,JVM 通过 -XX:MinHeapFreeRatio 和 -XX:MaxHeapFreeRatio 来控制扩容和缩容的比例,调节这两个值也可以控制伸缩的时机。整个伸缩的模型理解可以看这个图,当 committed 的空间大小超过了低水位/高水位的大小,capacity 也会随之调整:
  策略
  观察 CMS GC 触发时间点 Old/MetaSpace 区的 committed 占比是不是一个固定的值,或者像上文提到的观察总的内存使用率也可以。尽量 将成对出现的空间大小配置参数设置成固定的 ,如 -Xms 和 -Xmx,-XX:MaxNewSize 和 -XX:NewSize,-XX:MetaSpaceSize 和 -XX:MaxMetaSpaceSize 等。
  场景二:显式GC的去与留 现象
  除了扩容缩容会触发 CMS GC 之外,还有 Old 区达到回收阈值、MetaSpace 空间不足、Young 区晋升失败、大对象担保失败等几种触发条件,如果这些情况都没有发生却触发了 GC ,这种情况有可能是代码中手动调用了 System.gc 方法,此时可以找到 GC 日志中的 GC Cause 确认下。 原因
  保留 System.gc:CMS中使用 Foreground Collector 时将会带来非常长的 STW,在应用程序中 System.gc 被频繁调用,那就非常危险。增加 -XX:+DisableExplicitGC 参数则可以禁用。去掉 System.gc:禁用掉后会带来另一个内存泄漏的问题,为 DirectByteBuffer 分配空间过程中会显式调用 System.gc ,希望通过 Full GC 来强迫已经无用的 DirectByteBuffer 对象释放掉它们关联的 Native Memory,如Netty等。 策略
  无论是保留还是去掉都会有一定的风险点,不过目前互联网中的 RPC 通信会大量使用 NIO,所以建议保留。此外 JVM 还提供了 -XX:+ExplicitGCInvokesConcurrent 和 -XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses 参数来将 System.gc 的触发类型从 Foreground 改为 Background,同时 Background 也会做 Reference Processing,这样的话就能大幅降低了 STW 开销,同时也不会发生 NIO Direct Memory OOM。
  场景三:MetaSpace区OOM 现象
  JVM 在启动后或者某个时间点开始, MetaSpace 的已使用大小在持续增长,同时每次 GC 也无法释放,调大 MetaSpace 空间也无法彻底解决 。 原因
  在讨论为什么会 OOM 之前,我们先来看一下这个区里面会存什么数据,Java 7 之前字符串常量池被放到了 Perm 区,所有被 intern 的 String 都会被存在这里,由于 String.intern 是不受控的,所以 -XX:MaxPermSize 的值也不太好设置,经常会出现 java.lang.OutOfMemoryError: PermGen space 异常,所以在 Java 7 之后常量池等字面量(Literal)、类静态变量(Class Static)、符号引用(Symbols Reference)等几项被移到 Heap 中。而 Java 8 之后 PermGen 也被移除,取而代之的是 MetaSpace。
  由场景一可知,为了避免弹性伸缩带来的额外 GC 消耗,我们会将 -XX:MetaSpaceSize 和 -XX:MaxMetaSpaceSize 两个值设置为固定的,但这样也会导致在空间不够的时候无法扩容,然后频繁地触发 GC,最终 OOM。所以关键原因是 ClassLoader不停地在内存中load了新的Class ,一般这种问题都发生在动态类加载等情况上。 策略
  可以 dump 快照之后通过 JProfiler 或 MAT 观察 Classes 的 Histogram(直方图) 即可,或者直接通过命令即可定位, jcmd 打几次 Histogram 的图,看一下具体是哪个包下的 Class 增加较多就可以定位。如果无法从整体的角度定位,可以添加 -XX:+TraceClassLoading 和 -XX:+TraceClassUnLoading 参数观察详细的类加载和卸载信息。
  场景四:过早晋升 现象
  这种场景主要发生在分代的收集器上面,专业的术语称为"Premature Promotion"。90% 的对象朝生夕死,只有在 Young 区经历过几次 GC 的洗礼后才会晋升到 Old 区,每经历一次 GC 对象的 GC Age 就会增长 1,最大通过 -XX:MaxTenuringThreshold 来控制。过早晋升一般不会直接影响 GC,总会伴随着浮动垃圾、大对象担保失败等问题,但这些问题不是立刻发生的,我们可以观察以下几种现象来判断是否发生了过早晋升:
  分配速率接近于晋升速率 ,对象晋升年龄较小。
  GC 日志中出现"Desired survivor size 107347968 bytes, new threshold 1(max 6) "等信息,说明此时经历过一次 GC 就会放到 Old 区。
  Full GC 比较频繁 ,且经历过一次 GC 之后 Old 区的 变化比例非常大 。
  如Old区触发回收阈值是80%,经历一次GC之后下降到了10%,这说明Old区70%的对象存活时间其实很短。 过早晋升的危害:
  Young GC 频繁,总的吞吐量下降
  Full GC 频繁,可能会有较大停顿 原因
  主要的原因有以下两点:
  Young/Eden 区过小: 过小的直接后果就是 Eden 被装满的时间变短,本应该回收的对象参与了 GC 并晋升,Young GC 采用的是复制算法,由基础篇我们知道 copying 耗时远大于 mark,也就是 Young GC 耗时本质上就是 copy 的时间(CMS 扫描 Card Table 或 G1 扫描 Remember Set 出问题的情况另说),没来及回收的对象增大了回收的代价,所以 Young GC 时间增加,同时又无法快速释放空间,Young GC 次数也跟着增加
  分配速率过大: 可以观察出问题前后 Mutator 的分配速率,如果有明显波动可以尝试观察网卡流量、存储类中间件慢查询日志等信息,看是否有大量数据被加载到内存中
  策略如果是 Young/Eden 区过小 ,可以在总的 Heap 内存不变的情况下适当增大Young区。一般情况下Old的大小应当为活跃对象的2~3倍左右,考虑到浮动垃圾问题最好在3倍左右,剩下的都可以分给Young区过早晋升优化来看,原配置为Young 1.2G+Old 2.8G,通过观察CMS GC的情况找到存活对象大概为 300~400M,于是调整Old 1.5G左右,剩下2.5G分给Young 区。仅仅调了一个Young区大小参数(-Xmn),整个 JVM 一分钟Young GC从26次降低到了11次,单次时间也没有增加,总的GC时间从1100ms降低到了500ms,CMS GC次数也从40分钟左右一次降低到了7小时30分钟一次: 如果是分配速率过大:偶发较大 :通过内存分析工具找到问题代码,从业务逻辑上做一些优化一直较大 :当前的 Collector 已经不满足 Mutator 的期望了,这种情况要么扩容 Mutator 的 VM,要么调整 GC 收集器类型或加大空间 小结
  过早晋升问题一般不会特别明显,但日积月累之后可能会爆发一波收集器退化之类的问题,所以我们还是要提前避免掉的,可以看看自己系统里面是否有这些现象,如果比较匹配的话,可以尝试优化一下。一行代码优化的 ROI 还是很高的。如果在观察 Old 区前后比例变化的过程中,发现可以回收的比例非常小,如从 80% 只回收到了 60%,说明我们大部分对象都是存活的,Old 区的空间可以适当调大些。
  场景五:CMS Old GC频繁 现象
  Old 区频繁的做 CMS GC,但是每次耗时不是特别长,整体最大 STW 也在可接受范围内,但由于 GC 太频繁导致吞吐下降比较多。 原因
  这种情况比较常见,基本都是一次 Young GC 完成后,负责处理 CMS GC 的一个后台线程 concurrentMarkSweepThread 会不断地轮询,使用 shouldConcurrentCollect() 方法做一次检测,判断是否达到了回收条件。如果达到条件,使用 collect_in_background() 启动一次 Background 模式 GC。轮询的判断是使用 sleepBeforeNextCycle() 方法,间隔周期为 -XX:CMSWaitDuration 决定,默认为 2s。 策略
  处理这种常规内存泄漏问题基本是一个思路,主要步骤如下:
  Dump Diff 和 Leak Suspects 比较直观,这里说下其它几个关键点:内存 Dump: 使用 jmap、arthas 等 dump 堆进行快照时记得摘掉流量,同时 分别在 CMS GC 的发生前后分别 dump 一次分析 Top Component: 要记得按照对象、类、类加载器、包等多个维度观察Histogram,同时使用 outgoing和incoming分析关联的对象,其次Soft Reference和Weak Reference、Finalizer 等也要看一下分析 Unreachable: 重点看一下这个,关注下 Shallow 和 Retained 的大小。如下图所示的一次 GC 优化,就根据 Unreachable Objects 发现了 Hystrix 的滑动窗口问题。
  场景六:单次CMS Old GC耗时长 现象
  CMS GC 单次 STW 最大超过 1000ms,不会频繁发生,如下图所示最长达到了 8000ms。某些场景下会引起"雪崩效应",这种场景非常危险,我们应该尽量避免出现。
  原因
  CMS在回收的过程中,STW的阶段主要是 Init Mark 和 Final Remark 这两个阶段,也是导致CMS Old GC 最多的原因,另外有些情况就是在STW前等待Mutator的线程到达SafePoint也会导致时间过长,但这种情况较少。 策略
  知道了两个 STW 过程执行流程,我们分析解决就比较简单了,由于大部分问题都出在 Final Remark 过程,这里我们也拿这个场景来举例,主要步骤:
  【方向】 观察详细 GC 日志,找到出问题时 Final Remark 日志,分析下 Reference 处理和元数据处理 real 耗时是否正常,详细信息需要通过 -XX:+PrintReferenceGC 参数开启。 基本在日志里面就能定位到大概是哪个方向出了问题,耗时超过 10% 的就需要关注 。 2019-02-27T19:55:37.920+0800: 516952.915: [GC (CMS Final Remark) 516952.915: [ParNew516952.939: [SoftReference, 0 refs, 0.0003857 secs]516952.939: [WeakReference, 1362 refs, 0.0002415 secs]516952.940: [FinalReference, 146 refs, 0.0001233 secs]516952.940: [PhantomReference, 0 refs, 57 refs, 0.0002369 secs]516952.940: [JNI Weak Reference, 0.0000662 secs] [class unloading, 0.1770490 secs]516953.329: [scrub symbol table, 0.0442567 secs]516953.373: [scrub string table, 0.0036072 secs][1 CMS-remark: 1638504K(2048000K)] 1667558K(4352000K), 0.5269311 secs] [Times: user=1.20 sys=0.03, real=0.53 secs]小结
  正常情况进行的 Background CMS GC,出现问题基本都集中在 Reference 和 Class 等元数据处理上,在 Reference 类的问题处理方面,不管是 FinalReference,还是 SoftReference、WeakReference 核心的手段就是找准时机 dump快照,然后用内存分析工具来分析。Class处理方面目前除了关闭类卸载开关,没有太好的方法。在 G1 中同样有 Reference 的问题,可以观察日志中的 Ref Proc,处理方法与 CMS 类似。
  场景七:内存碎片&收集器退化 现象
  并发的 CMS GC 算法,退化为 Foreground 单线程串行 GC 模式,STW 时间超长,有时会长达十几秒。其中 CMS 收集器退化后单线程串行 GC 算法有两种:
  带压缩动作的算法,称为 MSC,上面我们介绍过,使用标记-清理-压缩,单线程全暂停的方式,对整个堆进行垃圾收集,也就是真正意义上的 Full GC,暂停时间要长于普通 CMS
  不带压缩动作的算法,收集 Old 区,和普通的 CMS 算法比较相似,暂停时间相对 MSC 算法短一些 原因
  CMS 发生收集器退化主要有以下几种情况:
  晋升失败(Promotion Failed)
  增量收集担保失败
  显式 GC
  并发模式失败(Concurrent Mode Failure) 策略
  分析到具体原因后,我们就可以针对性解决了,具体思路还是从根因出发,具体解决策略:
  内存碎片: 通过配置 -XX:UseCMSCompactAtFullCollection=true 来控制 Full GC 的过程中是否进行空间的整理(默认开启,注意是 Full GC,不是普通 CMS GC),以及 -XX: CMSFullGCsBeforeCompaction=n 来控制多少次 Full GC 后进行一次压缩
  增量收集: 降低触发 CMS GC 的阈值,即参数 -XX:CMSInitiatingOccupancyFraction 的值,让 CMS GC 尽早执行,以保证有足够的连续空间,也减少 Old 区空间的使用大小,另外需要使用 -XX:+UseCMSInitiatingOccupancyOnly 来配合使用,不然 JVM 仅在第一次使用设定值,后续则自动调整
  浮动垃圾: 视情况控制每次晋升对象的大小,或者缩短每次 CMS GC 的时间,必要时可调节 NewRatio 的值。另外使用 -XX:+CMSScavengeBeforeRemark 在过程中提前触发一次Young GC,防止后续晋升过多对象 小结
  正常情况下触发并发模式的 CMS GC,停顿非常短,对业务影响很小,但 CMS GC 退化后,影响会非常大,建议发现一次后就彻底根治。只要能定位到内存碎片、浮动垃圾、增量收集相关等具体产生原因,还是比较好解决的,关于内存碎片这块,如果 -XX:CMSFullGCsBeforeCompaction 的值不好选取的话,可以使用 -XX:PrintFLSStatistics 来观察内存碎片率情况,然后再设置具体的值。最后就是在编码的时候也要避免需要连续地址空间的大对象的产生,如过长的字符串,用于存放附件、序列化或反序列化的 byte 数组等,还有就是过早晋升问题尽量在爆发问题前就避免掉。
  场景八:堆外内存OOM 现象
  内存使用率不断上升,甚至开始使用 SWAP 内存,同时可能出现 GC 时间飙升,线程被 Block 等现象, 通过 top 命令发现 Java 进程的 RES 甚至超过了  -Xmx  的大小 。出现这些现象时,基本可确定是出现堆外内存泄漏。 原因
  JVM 的堆外内存泄漏,主要有两种的原因:
  通过 UnSafe#allocateMemory,ByteBuffer#allocateDirect 主动申请了堆外内存而没有释放,常见于 NIO、Netty 等相关组件
  代码中有通过 JNI 调用 Native Code 申请的内存没有释放 策略
  原因一:主动申请未释放
  原因二:通过 JNI 调用的 Native Code 申请的内存未释放
  场景九:JNI引发的GC问题 现象
  在 GC 日志中,出现 GC Cause 为 GCLocker Initiated GC。 2020-09-23T16:49:09.727+0800: 504426.742: [GC (GCLocker Initiated GC) 504426.742: [ParNew (promotion failed): 209716K->6042K(1887488K), 0.0843330 secs] 1449487K->1347626K(3984640K), 0.0848963 secs] [Times: user=0.19 sys=0.00, real=0.09 secs]2020-09-23T16:49:09.812+0800: 504426.827: [Full GC (GCLocker Initiated GC) 504426.827: [CMS: 1341583K->419699K(2097152K), 1.8482275 secs] 1347626K->419699K(3984640K), [Metaspace: 297780K->297780K(1329152K)], 1.8490564 secs] [Times: user=1.62 sys=0.20, real=1.85 secs]原因
  JNI(Java Native Interface)意为 Java 本地调用,它允许 Java 代码和其他语言写的 Native 代码进行交互。JNI 如果需要获取 JVM 中的 String 或者数组,有两种方式:
  拷贝传递
  共享引用(指针),性能更高
  由于 Native 代码直接使用了 JVM 堆区的指针,如果这时发生 GC,就会导致数据错误。因此,在发生此类 JNI 调用时,禁止 GC 的发生,同时阻止其他线程进入 JNI 临界区,直到最后一个线程退出临界区时触发一次 GC。
  策略添加 -XX+PrintJNIGCStalls 参数,可以打印出发生 JNI 调用时的线程,进一步分析,找到引发问题的 JNI 调用JNI 调用需要谨慎,不一定可以提升性能,反而可能造成 GC 问题升级 JDK 版本到 14,避免 JDK-8048556 导致的重复 GC

你的美好,全靠自己去成全世界上没有一条道路是重复的,没有一个人生是可以替代的。每一个人都在经历着只属于自己的生活。Nopathintheworldisrepeated,andnolifeisreplace愿你出走半生,归来仍是少年1hr所行之事,年岁虚晃,有永葆童真的善良,有缠绕心头的悔楚,有一腔孤勇的热血,更重要的是,出走半生,仍保少年之心。七岁的时候,你和小伙伴们看见了一只微微振翅的胖蝴蝶。为了找到它,什么是消费金融公司,消费金融公司是干什么的,合不合规首先,大家肯定有疑问,到底什么是消费金融公司,他和其他的银行或者小贷公司到底有什么区别,根据中国银保监发布的针对消费金融公司试点办法第二条定义,消费金融公司是指经中国银保监会批准,在一个空壳公司里有点股份,没流水,如何抵押贷款?案例分享前言这是作者第10篇原创文章分享。原先有跟朋友合伙一起开了一家公司,但是后面不景气,公司业务停止了,但执照没注销,现在在上班了,但是最近又需要一笔资金,这种情况如果抵押贷款,能享受凝聚起上下同心奋发进取的磅礴力量石家庄市建筑设计院有限责任公司把握新机遇实现新发展2022年,石家庄城发投集团所属石家庄市建筑设计院有限责任公司紧跟集团发展步伐,大力弘扬实干兴石舍我其谁的担当精神,深入实施创新驱动发展战略,以新发展理念引领高质量发展,各项经营指冬季不冷!眉山瓦屋山景区年营业收入首次突破1个亿封面新闻记者李庆王越欣12月23日,记者从四川眉山市瓦屋山投资有限公司获悉,瓦屋山实现了自景区开放接待以来年度最佳业绩,年营业收入首次突破1亿元,创历史新高!瓦屋山位于东坡故里眉山父与子的对话冬日生活打卡季2022生机大会在我的生活里,自从有了孩子,就多了一份生活的乐趣。快中午的时候,5岁的儿子在客厅转了一圈问爸爸,谁厉害?爸爸正在清理水培花上的尘土,抬头说道你觉得谁厉无痛分娩只有止痛的作用吗?按照010级的疼痛程度来分,分娩是10级疼痛的王者级别,直接感受相当于21根肋骨同时断掉。那么顺产时所打的无痛,仅仅只是为了缓解疼痛吗?缓解疼痛下带来的连锁反应才是它的重点。不打无大理唯一的5A级景区并不是苍山洱海头条创作挑战赛一提起大理,相信大多数都知道风花雪月,都知道苍山洱海,更向外和心爱之人租车环洱海拍照,一同看日升日落,在大理古城吃美食,住民宿,感受不一样的慢生活。但是如果说起大理的听说明年开始回国入境会完全免隔离?最近一个月以来回国签证政策进一步放松,航班熔断取消,上机前的检测进一步简化(喜大普奔!熔断取消,登机前核酸检测规定调整,入境隔离期缩短),机票价格也有所下降,转机更加容易。各种政策杨氏太极拳实战用法系列(二十四)双峰贯耳太极拳作为一种武术,具有丰富的攻防内涵,有独特的法身法和术(攻防技巧),了解了每一拳势的技击含义,就能在套路练习时有章可循,出手抬足有的放矢,动作更有针对性,不仅能为将来的推手散手
iOS16beta8好评如潮?iPhone11续航极大改善,信号完美,值得升级苹果在上周发布了ios16beta8系统更新,不少新款机型升级后都有不错的用户体验,那么像iPhone11这样的老机型升级后体验到底怎么样呢?今天就给大家分享iPhone11升级i电视盒子哪个好?测评师深度分析目前最强的电视盒子我们宅在家的时间变得多了起来,好在有电视的陪伴,少了些许枯燥无趣,也正是在此情形下,大家都意识到电视盒子已然是生活中重要的存在,怎么选购不踩雷?电视盒子哪个好?作为专业数码产品测评4款3000元档旗舰手机,款款有口皆碑,总有你想要的很多年轻人选购旗舰手机,预算一般在3000元左右,这个价位段已经能够买到配置很高的智能手机了,足够满足年轻用户的性能需求了,因为年轻用户喜欢玩手游,对性能要求高,2000元左右可能这4款高性价比的手机,最低1589元,基础体验让人满意这4款高性价比的手机,最低1589元,基础体验让人满意第一款魅族18s参考价格2899元魅族18s手感轻薄,握持感不错。屏幕方面120Hz刷新率搭配2K的屏幕体验很棒,三零系统新机TCL电视为什么受到那么多人的喜爱?世界前三TCL成立于1982年,是一家集研发生产销售于一体的高科技公司,以半导体电子产品显示器为主要研发方向。TCL如今的业务非常广泛,电视冰箱空调今年甚至还发布了首款笔记本电脑。今天主要厚度仅有9。8厘米,小米再创超薄隐藏式智能晾衣机从普通晾衣架到电动晾衣机,再到如今的智能晾衣机,十几年时间,持续性蜕变晾衣机取代传统晾衣架,带来全新升级的晾晒革命近日小米推出全新超薄隐藏式智能晾衣机pro,打响晾晒新征程!小米米小米超窄边框显示器399元超值价文周澄澄责编吕东兴总编唐迪小米产品,必出精品,这款redmi显示器,效率办公不卡顿,坚实品质购放心,到手价仅为399元。感兴趣的点击链接了解一下吧。FHD全高清画质,展现精彩新视界小米发布超薄晾衣架Pro电动晾衣架几乎是很多家庭必备的产品,其中原因是方便收纳,老人也可以轻松使用。小米一直致力于精品智能产品的设计和研发,这次带来了米家超薄晾衣架Pro。采用超薄设计的晾衣架Pro,回收推荐一款各方面配置都顶配的性能手机大家伙,今天就不给大家推荐性价比高的手机了这一款手机不能说不性价比,只能说钱到位也算性价比,那么这一款能够超越大部分手机的神机是哪一款呢?它就是iQOO10proiQOO10pro酷睿i97700K,工作站CPU的悲伤性能越好,价格越便宜,天下有这么好的事情么?一位读者留言咨询为什么i97900X比i77700K还便宜?试比较2017年,酷睿i77700K(四核八线程)发售价格为2499元,i98月次旗舰手机性能排行榜vivoS15Pro排名第二头条创作挑战赛近日,根据多家科技媒体的消息,国内手机评测机构安兔兔公布了最新一期的安卓手机性能排行榜。8月份的又一波骁龙8Gen1新机潮过去之后,陆续再发布的新机厂商可能就要少一些