一.运行时数据区域

1.Heap Area

 

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

2.Method Area

 

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

3.Stack Area

 

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

 

jvm系列--GC_老年代

 

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

jvm系列--GC_用户线程_02

 

三.GC

1.GC的内存结构

jvm系列--GC_jvm_03

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日志:

jvm系列--GC_java_04

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 日志

jvm系列--GC_用户线程_05

 

  • 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,一直不释放,会产生内存泄露。

 

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

 

三种基本算法:

标记/清除算法

标记活跃对象,清除不可达对象,得停止情况,算法生效,效率低,递归整个树,停止整个程序生效算法,释放不连续,产生内存碎片。

 

复制

空闲区和活动区,区域交换,速度快,耗内存

 

标记/整理

标记活跃对象,整理连续空间

 

分代收集算法(重要)

新生代:

复制算法;

Eden/from to 8:1;

一般转到to ,如果根算法可达,就复制到to,清空Eden和from,所有对象都在to中,此时from和to互换位置;

比较高效,占用空间;

老年代,时效性长;

 

老年代:

指针碰撞,新对象,放的位置

指针碰撞技术跟踪分配给Eden区上最新的对象。该对象将位于Eden 区的顶部。如果之后有一个对象被创建,只需检查Eden区是否有足够大的空间存放该对象。如果空间够用,它将被放置在Eden区,存放在空间的顶部。因此,在创建新对象时,只需检查最后被添加对象,看是否还有更多的内存空间允许分配。然而,如果考虑多线程的环境,则是另外一种情况。为了实现多线程环境下,在Eden 区线程安全的去创建保存对象,那么必须加锁,因此性能会下降。在HotSpot虚拟机中TLABs能够解决这一问题。它允许每个线程在Eden区有自己的一小块私有空间。因为每一个线程只能访问自己的TLAB,所以在这个区域甚至可以使用无锁的指针碰撞技术进行内存分配。

TLABs(线程本地分配缓冲)

 

7.垃圾回收器

串行收集器:

最古老,最稳定

效率高

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

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

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

jvm系列--GC_用户线程_06

 

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

parNew(并行收集器)

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

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

复制算法。

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

-XX:ParallelGCThreads 限制线程数量。

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

jvm系列--GC_老年代_07

 

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

parallel(并行收集器)

类似ParNew

新生代复制算法

老年代 标记-压缩

更加关注吞吐量

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

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

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

jvm系列--GC_jvm_08

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

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

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

 

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

CMS收集器

Concurrent Mark Sweep 并发标记清除

标记-清除算法。

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

停顿时间少。

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

-XX:+UseConcMarkSweepGC。

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

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

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

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

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

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

jvm系列--GC_用户线程_09

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

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

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

 

特点:

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

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

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

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

jvm系列--GC_用户线程_10

 

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

G1收集器

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

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

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

jvm系列--GC_java_11

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

 

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

有关碎片

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

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

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

jvm系列--GC_多线程_12

 

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

减轻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回收

 

未完待续!