jvm系列--GC详解与实战

一.运行时数据区域

1.Heap Area

  • 存储的全都是Object对象实例,对象实例中一般都包含了其数据成员以及与该对象对应Class信息;
  • 一个JVM实例在运行的时候只有一个Heap区域,该区域被所有的线程共享;

2.Method Area

  • 方法区域又名静态成员区域,包含整个程序Class,static成员等;
  • 方法区被所有线程共享

3.Stack Area

  • Stack区域属于线程私有,每个线程都会包含一个Stack区域,Stack区域中含有基本的数据类型以及对象的引用,其它线程均不能直接访问该区域;
  • 分为三大部分:基本数据类型区域、操作指令区域、上下文等;

二.JVM线程引擎和内存共享交互

三.GC

1.GC的内存结构

Scavenge GC:

一般情况下,当新对象生成,并且在Eden申请空间失败时,就会触发Scavenge GC,对Eden区域进行GC,清除非存活对象,并且把尚且存活的对象移动到Survivor区。

Full GC:

对整个堆进行整理,包括Young、Tenured和Perm。Full GC因为需要对整个对进行回收,所以比Scavenge GC要慢,因此应该尽可能减少Full GC的次数。在对JVM调优的过程中,很大一部分工作就是对于FullGC的调节。

产生原因:

  • 年老代(Tenured)被写满
  • 持久代(Perm)被写满
  • System.gc()被显示调用
  • 上一次GC之后Heap的各域分配策略动态变化

Full GC日志:

Metaspace的存储空间。Metaspace使用的是本地内存,而不是堆内存

Metaspace:

  • 类的元数据
  • 用metaspace原因:permanent淘汰,情况复杂,类的信息,静态类常量池信息,类动态加载,空间不好评估,解决oom 用Metaspace;GC,持久代内存不够,full gc。
  • 用的os内存空间
  • 动态伸缩,调整自身大小
  • 足够大初始值,最大值,第一次gc,自动调整空间。
  • GC算法:所有实例被gc,ClassLoader不存在,判断类加载存不存在,实例和类加载绑定,内部是个链表;permanent 与年老代分开

2.GC时候内存管理

  • Heap空间大于50%
  • 绝大多数对象使用过后不再使用
  • 复制好处不会有垃圾碎片
  • 大对象直接进入年老代

3.Young Generation

  • 分代是因为GC性能原因
  • 年轻代和年老代算法不同
  • 年老代GC是迫不得已
  • 大部分Jvm对象生命周期比较短,如果体积大直接放到年老代中,对象一般产生Eden中
  • Eden对象拷贝到From,阈值到达一定次数会复制到年老代中,如果没有到达次数,会复制到To中
  • Full GC:From中生命周期达到一定阈值;年老代满了
  • 年老代回收速度慢
  • 年轻代和年老代比例,Eden和From/to比例:-XX:NewRatio 年轻代和年老代比例,设置比例扩展时候会产生消耗,如果知道具体值设置;-XX:SurvivorRatio  Eden和一个Survivor比例,Eden小的时候,会增加GC次数了 ; -XX:NewSize 年轻代大小不能比年老代大 ;-XX:MaxNewSize
  • 年龄问题:GC一次年龄加1;Eden,15岁晋升;Survivor中,有些对象年龄相同,所有年龄相同对象总和大于空间一半,进入年老代,动态调整
  • 新生代收集担保:在一次理想化的minor gc中,活跃对象会从Eden和First Survivor中被复制到Second Survivor。然而,Second Survivor不一定能容纳所有的活跃对象。为了确保minor gc能够顺利完成,需要在年老代中保留一块足以容纳所有活跃对象的内存空间。这个预留的操作,被称之为新生代收集担保(New Generation Guarantee)。当预留操作无法完成时,就会触发major gc(full gc)。

4.Minor GC 日志

  • PSYounGen:新生代收集算法
  • 2336k:新生代收集前占用的内存
  • 288:新生代收集后占用的内存
  • 2560k:新生代总共大小
  • 8274k:收集前堆的大小
  • 6418k:收集后堆大小
  • 9728k:整个堆大小
  • 0.01129926:整个minor GC消耗时间
  • Times:user 用户空间; sys 系统空间; real 真实时间 占user和sys 比较多,调度比较多,有可能并发

5.内存逃逸技术

简介:对象分配不在heap区域,降低GC发生次数,负担,提升回收效率

在java stack:

局部实例方法里面,但是被外部成员引用,会发生逃逸

class Worker{
 public Worker worker;
 public Worker getWorker(){
 //局部对象创建,对象逃逸
 return null==worker?new Worker():worker;
 }
 public void useWorker(){

 //这个不是,这是方法本身

 Worker obj=new Worker();

}}

6.对象标记算法

引用计数算法:被引用,加一,不使用减一,问题:两个都已经,两个或多个相互引用,都已经死亡,但计数不是0,一直不释放,会产生内存泄露。

根搜索算法:树根连接目标对象,如果不到达目标对象,已经死亡;树里面具体对象,栈中引用,本地方法栈,常量池,静态,所有对象。

三种基本算法:

复制算法:MinorGC(普通GC)

年轻代中使用的是 Minor GC,这种GC算法采用的是复制算法(Copying)

Minor GC会把Eden中的所有活的对象都移到 Survivor 区域中,如果 Survivor区中放不下,那么剩下的活的对象就被移到Oldgeneration中,也即一旦收集后,Eden是就变成空的了。

当对象在Eden(包括一个 Survivor区域,这里假设是from区域)出生后,在经过一次 Minor GC后,如果对象还存活,并且能够被另外一块 Survivor区域所容纳(上面已经假设为from区域,这里应为to区域,即to区域有足够的内存空间来存储Eden和from区域中存活的对象),则使用复制算法将这些仍然还存活的对象复制到另外一块 Survivor区域(即to区域)中,然后清理所使用过的Eden以及 Survivor区域(即fom区域),并且将这些对象的年龄设置为1,以后对象在 Survivor区每熬过一次 Minor GC,就将对象的年龄+1,当对象的年龄达到某个值时(默认是15岁,通过 -XX:MaxTenuringThreshold 来设定参数),这些对象就会成为老年代。

解释: 年轻代中的GC,主要是复制算法(Copying) Hotspot JVM把年轻代分为了三部分:1个Eden区和2个 Survivor区(分别叫from和to)。默认比例为8:1:1,一般情况下,新创建的对象都会被分配到Eden区(一些大对象特殊处理),这些对象经过第一次 Minor GC 后,如果仍然存活,将会被移到Survivor区。对象在 Survivor区中每熬过一次 Minor GC ,年龄就会增加1岁,当它的年龄增加到一定程度时,就会被移动到年老代中。因为年轻代中的对象基本都是朝生夕死的(80%以上),所以在年轻代的垃圾回收算法使用的是复制算法,复制算法的基本思想就是将内存分为两块,每次只用其中一块,当这一块内存用完,就将还活着的对象复制到另外一块上面。复制算法不会产生内存碎片。 在GC开始的时候,对象只会存在于Eden区和名为“From”的 Survivor区,Survivor区”To“是空的。紧接着进行GC,Eden区中所有存活的对象都会被复制到“To”,而在“From”区中,仍存活的对象会根据他们的年龄值来决定去向。年龄达到一定值(年龄阈值,可以通过 -XX:MaxTenuringThreshold来设置)的对象会被移动到年老代中,没有达到阈值的对象会被复制到 “To”区域。经过这次GC后,Eden区和“From”区已经被清空。这个时候,“From”和“To”会交换他们的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。不管怎样,都会保证名为“To”的 Survivor区域是空的。Minor GC 会一直重复这样的过程,直到“To”区被填满,“To”区被填满之后,会将所有对象移动到年老代中。

GC算法[通俗易懂]

GC算法[通俗易懂]

因为Eden区对象一般存活率较低,一般的,使用两块10%的内存作为空闲和活动区间,而另外80%的内存,则是用来给新建对象分配内存的。一旦发生GC,将10%(From)的活动区间与另外80%中存活的对象转移到10%(To)的空闲区间,接下来,将之前90%的内存全部释放,以此类推 。

口诀:

谁空谁是 to,复制要交换。

总结:

  1. 优点:不会产生内存碎片,完整度极高。
  2. 缺点:浪费了这10%(To)的内存空间。

复制算法弥补了标记/清除算法中,内存布局混乱的缺点。不过与此同时,它的缺点也是相当明显的

  1. 它浪费了一半(1:1的这个一半,就是那10%)的内存,这太要命了。
  2. 如果对象的存活率很高,我们可以极端一点,假设是100%存活,那么我们需要将所有对象都复制一遍,并将所有引用地址重置一遍。复制这一工作所花费的时间,在对象存活率达到一定程度时,将会变的不可忽视。所以从以上描述不难看出,复制算法要想使用,最起码对象的存活率要非常低才行,而且最重要的是,我们必须要克服50%内存的浪费。

标记清除/标记整理算法:FullGC又叫 MajorGC(全局GC)

老年代一般是由标记清除或者是标记清除标记整理的混合实现

标记清除(Mark-Sweep)

GC算法[通俗易懂]

GC算法[通俗易懂]

GC算法[通俗易懂]

GC算法[通俗易懂]

GC算法[通俗易懂]

GC算法[通俗易懂]

GC算法[通俗易懂]

GC算法[通俗易懂]

缺点:

  1. 首先,它的缺点就是效率比较低(递归与全堆对象遍历),而且在进行GC的时候,需要停止应用程序,这会导致用户体验非常差劲。
  2. 其次,主要的缺点则是这种方式清理出来的空闲内存是不连续的,这点不难理解,我们的死亡对象都是随机的出现在内存的各个角落的,现在把它们清除之后,内存的布局自然会乱七八糟。而为了应付这一点,JVM 就不得不维持一个内存的空闲列表,这又是一种开销。而且在分配数组对象的时候,寻找连续的内存空间会不太好找。

标记整理(Mark-Compact)

GC算法[通俗易懂]

GC算法[通俗易懂]

注释:上面标记的是活的。

GC算法[通俗易懂]

GC算法[通俗易懂]

GC算法[通俗易懂]

GC算法[通俗易懂]

GC算法[通俗易懂]

GC算法[通俗易懂]

缺点:

  1. 标记/整理算法唯一的缺点就是效率也不高,不仅要标记所有存活对象,还要整理所有存活对象的引用地址。从效率上来说,标记/整理算法要低于复制算法。

垃圾回收器

串行收集器:

最古老,最稳定

效率高

可能会产生较长的停顿,只使用一个线程。

-XX:+UseSerialGC:新生代、老年代使用串行回收;新生代复制算法;老年代标记-压缩;

应用程序需要GC时候,程序暂停,单线程开始GC

--------------------------------------------------------------------------------------

parNew(并行收集器)

-XX:+UseParNewGC:新生代并行;老年代串行。

Serial收集器新生代的并行版本。

复制算法。

多线程,需要多核支持,多线程不一定快。

-XX:ParallelGCThreads 限制线程数量。

应用程序需要GC时候,程序暂停,多线程开始GC。

--------------------------------------------------------------------------------------

parallel(并行收集器)

类似ParNew

新生代复制算法

老年代 标记-压缩

更加关注吞吐量

-XX:+UseParallelGC:使用parallel 收集器 老年代串行

-XX:+UseParallelOldGC:使用Parallel收集器 并行老年代

应用程序需要GC时候,程序暂停,多线程开始GC

-XX:MaxGCPauseMills:最大停顿时间,单位毫秒;GC尽力保证回收时间不超过设定值。

-XX:GCTimeRatio:0-100的取值范围;垃圾收集的时间占总时间的比;默认99,即最大允许1%时间做GC。

-XX:MaxGCPauseMills和-XX:GCTimeRatio这两个参数是矛盾的。因为停顿时间和吞吐量不可能同时调优。

CMS收集器

Concurrent Mark Sweep 并发标记清除

标记-清除算法。

并发阶段会降低吞吐量:跟应用程序线程一起执行,交替执行;并行是多个线程执行。

停顿时间少。

老年代收集器(新生代会使用ParNew)。

-XX:+UseConcMarkSweepGC。

CMS运行过程比较复杂,着重实现标记的过程,可分为:

初始标记:根可以直接关联到的对象;速度快,全局停顿。

并发标记(和用户线程一起):主要标记过程,标记全部对象,是否是垃圾。

重新标记:由于并发标记时,用户线程依然运行,因此正式清理前,再做修正,也会产生停顿。

并发清除(和用户线程一起):基于标记结果,直接清理对象。

标记过程,把全局停顿尽可能的缩小。

-XX:UseCMSCompactAtFullCollection Full GC后,进行一次整理,整理过程是独占的,会引起停顿时间变长。

-XX:+CMSFullGCsBeforeCompaction  设置进行几次Full GC,后进行一次碎片整理。

-XX:ParallelCMSThreads 设定CMS线程数量。

特点:

尽可能降低停顿,更加关注停顿。

会影响系统整体吞吐量和性能。比如,在用户线程运行过程中,分一半CPU去做GC,系统性能在GC阶段,反应速度就下降一半。

清理不彻底:因为在清理阶段,用户线程还在运行,会产生新的垃圾,无法清理。

因为和用户线程一起运行,不能在空间快满时候再清理:-XX:CMSInitiatingOccupancyFraction设置触发GC的阈值;如果不幸内存预留空间不够,就会引起concurrent mode failure;

G1收集器

-XX:+UseG1GC 开启G1垃圾收集器

G1将新生代,老年代的物理空间划分取消了,取而代之的是,G1算法将堆划分为若干个区域(Region),它仍然属于分代收集器。不过,这些区域的一部分包含新生代,新生代的垃圾收集依然采用暂停所有应用线程的方式,将存活对象拷贝到老年代或者Survivor空间。老年代也分成很多区域,G1收集器通过将对象从一个区域复制到另外一个区域,完成了清理工作。这就意味着,在正常的处理过程中,G1完成了堆的压缩(至少是部分堆的压缩),这样也就不会有cms内存碎片问题的存在了。

适用时间 Full GC次数太频繁或者消耗时间太长。对象分配的频率或代数提升显著。太长垃圾回收或内存整理(超过0.5~1秒)。

在G1中,还有一种特殊的区域,叫Humongous区域。 如果一个对象占用的空间超过了分区容量50%以上,G1收集器就认为这是一个巨型对象。这些巨型对象,默认直接会被分配在年老代,但是如果它是一个短期存在的巨型对象,就会对垃圾收集器造成负面影响。为了解决这个问题,G1划分了一个Humongous区,它用来专门存放巨型对象。如果一个H区装不下一个巨型对象,那么G1会寻找连续的H分区来存储。为了能找到连续的H区,有时候不得不启动Full GC。

------------------------------------------------------------------------------------------------

有关碎片

标记-清除和标记-压缩:

标记-清除产生大量碎片,连续空间没有。

标记-清除算法之后会进行,标记-压缩算法整理。

--------------------------------------------------------------------------------------

减轻GC压力

  • 软件如何设计架构
  • 代码如何去写
  • 堆空间如何分配

--------------------------------------------------------------------------------------

GC参数整理

-XX:UseSerialGC 设置新生代和老年代使用串行收集器

-XX:SurvivorRatio:设置eden区大小和survivior区大小的比例

-XX:NewRatio 新生代和老年代的比

-XX:+UseParNewGC 新生代使用并行收集器

-XX:+UseParallelGC 新生代使用并行回收收集器

-XX:+UseParallelOldGC 老年代使用并行回收收集器

-XX:ParallelGCThreads 设置用于垃圾回收的线程数

-XX:+UseConcMarkSweepGC  新生代使用并行收集器,老年代使用CMS+串行收集器

-XX:ParallelCMSThreads 设定CMS线程数量

-XX:CMSInitiatingOccupancyFraction 设置CMS收集器在老年代空间被使用多少后触发

-XX:+UseCMSCompactAtFullCollection 设置CMS收集器在完成垃圾收集后是否要进行一次内存碎片整理

-XX:CMSFullGCsBeforeCompaction 设定进行多少次CMS垃圾回收后,进行一次内存压缩

-XX:+CMSClassUnloadingEnabled 允许对类元数据进行回收

-XX:CMSInitiatingPermOccupancyFraction 当永久区占用率达到这一百分比时,启动CMS回收

-XX:UseCMSInitiatingOccupancyOnly 表示只在到达阀值的时候,才进行CMS回收

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
回答: jstat -gc 命令用于查看Java进程的垃圾回收情况。下面是各个参数的详细解释: S0C:第一个幸存区的大小,即Survivor0的大小。 S1C:第二个幸存区的大小,即Survivor1的大小。 S0U:第一个幸存区的使用大小,即Survivor0的使用大小。 S1U:第二个幸存区的使用大小***即Eden区的使用大小。 OC:老年代大小,即Old区的大小。 OU:老年代使用大小,即Old区的使用大小。 MC:元数据区大小,即Metaspace的大小。 MU:元数据区使用大小,即Metaspace的使用大小。 CCSC:压缩类空间大小,即Compressed Class Space的大小。 CCSU:压缩类空间使用大小,即Compressed Class Space的使用大小。 YGC:年轻代垃圾回收次数,即Young Generation GC的次数。 YGCT:年轻代垃圾回收消耗时间,即Young Generation GC的消耗时间。 FGC:老年代垃圾回收次数,即Full GC的次数。 FGCT:老年代垃圾回收消耗时间,即Full GC的消耗时间。 GCT:总垃圾回收消耗时间,即总的GC消耗时间。 [1 [2 [3<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [jstat -gc pid数据详解](https://blog.csdn.net/dhj199181/article/details/108415771)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [jvm jstat -gcutil 参数详解](https://blog.csdn.net/weixin_44371237/article/details/129546682)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [jstat -gc pid参数](https://blog.csdn.net/weixin_43923436/article/details/128240747)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值