几张图可以理解GC JVM调优的内容


public class ApiPurchaseOrderServiceApp {

	public static void main(String[] args) throws Exception {

		ApiPurchaseOrderServiceApp m=new ApiPurchaseOrderServiceApp();
		m.compute();//栈
		System.out.println("ok");//方法出口
	}
	public  int compute()
	{
		int a=1;//局部变量表
		int b=2;
		int c=(a+b)*10;//操作数栈
		return c;
		
	}
}

Javap -c ApiPurchaseOrderServiceApp.class > aa.txt或Javap -v ApiPurchaseOrderServiceApp.class > aa.txt 可以查看.class文件的源码文件到指令码,

概念

程序计数器:code部分是 程序计数器的部分 代表当前执行的行

操作数栈:是从局部变量表里拿出变量计算使用的临时栈

动态链接:栈内执行方法与方法区里加在类的方法 映射使用的指针

方法出口:就是栈帧结束后,返回到上一方法的code地址

堆:放new 的方法

本地方法栈:比如thread下native的start0  是调用windows下c++产生的start0方法,时候,临时使用的栈

方法区:元空间 存放 常量 静态变量 类元信息等 就是Java的要被执行的方法和类被定义的地方 使用物理直接内存,比如4G内存物理,3G xmx内存,剩下1G物理内存,方法区使用的内存就是1G里

eden survivor0 survivor1 :新生代 依次慢了被GC回收

old:老年代 一直存在的,如线程池 数据库连接池

GC ROOT:类装载器 jvm虚拟映射指针

  • 通过System Class Loader或者Boot Class Loader加载的class对象,通过自定义类加载器加载的class不一定是GC Root
  • 处于激活状态的线程
  • 栈中的对象
  • JNI栈中的对象
  • JNI中的全局对象
  • 正在被用于同步的各种锁对象
  • JVM自身持有的对象,比如系统类加载器等。
  • 常用的GC算法

  • 标记回收算法
    从GC root进行遍历,把可达对象都标记,剩下那些不可达的进行回收,这种方式需要中断其他线程,并且可能产生内存碎片

  • 复制算法
    把内存区域分为两块,每次使用一块,GC的时候把一块中的内容移动到另一块中,原始内存中的对象就可以被回收了。

  • 标记压缩算法
    和标记回收差不多,但是在回收的时候会对可达对象进行整理,将其压缩到内存的一段,避免内存碎片

  • 分代算法
    将内存区域分代,对不同的代使用不同的回收算法,通常分为新生代,老年代,和永久带。
    新生代一般包含三个区域,Eden区和两个Survivor区,新生代一般采用复制算法

 

 

    JVM调优主要就是调整下面两个指标
    停顿时间:垃圾收集器做垃圾回收中断应用执行的时间。
    -XX:Max GC Pause Millis
    吞吐量:垃圾收集的时间和总时间的占比:1/(1+n),吞吐量为1-1/(1+n)。
    -XX:GCTimeRatio=n
    GC调优步骤
    打印GC日志
    -XX:+PrintGCDetails-XX:+PrintGCTimeStamps -XX:+PrntGCDateStamps -Xloggc:./go.log
    Tomcat则直接加在JAVA_OPTS变量里
              分析日志得到关键性指标
              分析GC原因, 调优JVM参数
    1、Parallel Scavenge收集器(默认)
    分析parallel-gc.log
    调优:
    第一次调优, 设置Meta space大小:增大元空间大小-XX:MetaspaceSize=64M-XX:MaxMetaspaceSize=64M
    第二次调优, 增大年轻代动态扩容增量, 默认是20(%) , 可以减少young gc:-XX:YoungGenerationSizeIncrement=30
    比较下几次调优效果:
    吞吐量   最大停顿  页  平均停  Young gc  Full gc
    98.356%  120ms     1    19ms   19        2
    99.252%  20ms           10ms   16        0

 2、配置CMS收集器
    -XX:+Use Conc Mark Sweep GC
    分析cms-gc.log
3、配置G1收集器
    -XX:+UseG1GC
    分析g 1-gc.log
    young GC:[GC pause(G 1 Evacuation Pause) (young)
    initial-mark:[GC pause(Metadata GC Threshold) (young) (initial-mark)) (参数:Initiating Heap Occupancy Percent)
    mixed GC:[GC pause(G 1 Evacuation Pause) (mixed) (参数:G1Heap Waste Percent)
    full GC:[Full GC(Allocation Failure) (无可用region)
    (G 1内部, 前面提到的混合GC是非常重要的释放内存机制, 它避免了G 1出现Region没有可用的情况, 否则就会触发Full GC事件。
    CMS, Parallel.Serial GC都需要通过Full GC去压缩老年代并在这个过程中扫描整个老年代。G 1的Full GC算法和Serial GC收集器完全一致, 当一个
    Full GC发生时, 整个Java堆执行一个完整的压缩, 这样确保了最大的空余内存可用。G 1的Full GC是一个单线程, 它可能引起一个长时间的停顿时
    间, G 1的设计目标是减少Full GC, 满足应用性能目标。)
    查看发生Mixed GC的阈值:j info-flag Initiating Heap Occupancy Percent进程id
    调优:
    第一次调优, 设置Meta space大小:增大元空间大小-XX:MetaspaceSize=64M-XX:MaxMetaspaceSize=64M
    第二次调优, 添加严叶量和停顿肚间参数-yy-GC timeR atic-80-XX Mar GC Pause Mi= 100

    井行收集器设置
    -XX:ParallelGCThreads:设置并行收集器收集时使用的CPU数。并行收集线程数。
    -XX:MaxGCPauseMilis:设置并行收集最大暂停时间
    -XX:GCTimeRatio:设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)
    -XX:YoungGenerationSizeIncrement:年轻代gc后扩容比例,默认是20(%)
    CMS收集器设置
    -XX:+Use Conc Mark Sweep GC:设置CMS井发收集器
    -XX:+CMS Incremental Mode:设置为增量模式, 适用于单CPU情况。
    -XX:ParallelGCThreads:设置并发收集器新生代收集方式为并行收集时, 使用的CPU数。并行收集线程数。
    -XX:CMSFullGCsBeloreCompacion:设定进行多少次CMS垃圾回收后,进行一次内存压缩
    -XX:+CMS Class Ur loading Enabled:允许对元数据进行回收
    -XX:UseCMSIniiatingQccupancyOnly:表示只在到达阀值的时候,才进行CMS回收
    -XX:+CMS In creme nl al Mode:设置为增量模式。适用于单CPU情况
    -XX:ParallelCMSThreads:设定CMS的线程数量
    -XX:CMSInitiaingOccupancyFraction:设置CMS收集器在老年代空间被使用多少后触发
    -XX:+Use CMS Compact At Full Colection:设置CMS收集器在完成垃圾收集后是否要进行一次内存碎片的整理

      G1收集器设置
    -XX:+UseG1GC:使用G 1收集器
    -XX:ParallelGCThreads:指定GC工作的线程数量
    -XX:G1HeapRegionSize:指定分区大小(1MB-32MB,且必须是2的霉),默认将整堆划分为2048个分区
    -XX:GCTimeRatio:吞吐量大小,0-100的整数(默认9),值为n则系统将花费不超过1/(1+n)的时间用于垃圾收集
    -XX:MaxGCPauseMillis:目标暂停时间(默认200ms)
    -XX:G1NewSizePercent:新生代内存初始空间(默认整堆5%)
    -XX:G1MaxNewSizePercent新生代内存最大空间
    -XX:TargetSurvvorRatio;Survivor填充容量(默认50%)
    -XX:MaxTenuringThreshold:最大任期值(默认15)
    -xX:InitiatingHeapOccupancyPercen:老年代占用空间超过整堆比IHOPI值(默认45%)超过则执行混合收集
    XX:G1HeapWastePercent:堆废物百分比(默认5%)

 

分代收集的概念是指在不同的代上使用不同的垃圾回收算法,一般来讲 在新生代上使用复制算法,在老年代上使用标记清理或者标记压缩算法。在永久代上使用标记压缩算法(JVM规范并没有要求要对永久代进行回收)。

垃圾收集器是GC的具体实现,不同的垃圾收集器针对的代也是不一样的,简单介绍一下。

Serial收集器:主要针对针对新生代,什么都不配置的话JVM默认的收集器,采用复制算法
ParNew收集器:主要 针对新生代, Serial的多线程版本。

Parallel Scanvenge: 主要针对新生代,主要用在服务器上,这种垃圾收集器也是采用复制算法,但是关注CPU吞吐量,强调CPU运行垃圾回收的时间和CPU执行用户代码的时间的比例。

Serial Old收集器:主要针对老年代,采用标记清理和标记压缩算法,单线程版本

Parallel Old收集器:主要针对老年代,多线程版本。

CMS(Concurrent Mark Sweep):大名鼎鼎的并行标记清理收集器,主要针对老年代,多线程,主要关注停顿时间,这个在嵌入式设备上非常有名,因为用户对于停顿时间很敏感。

CMS的原理是三次标记一次清除。
第一次标记先找到应用中所有的GC root,这次标记需要暂停用户线程。但是时间非常非常短。
第二次标记不需要暂停用户线程,根据第一次标记的结果去寻找不可达对象。
第三次标记也需要暂停用户线程,因为在第二次标记的过程中,GC root可能发生了变化,这个时候就要在把变化的重新标记一下。

三次标记完成之后就会执行清理过程。

由于在第二次并行标记的时候用户线程仍然在执行,所以需要预留足够的内存给用户线程使用,所以CMS并不会在老年代满了之后才执行Full GC, 一般是在老年代使用了一大半的时候就会执行一次Full GC.

同样的,CMS由于采用标记清理算法,也会导致内存碎片的产生。

并发清理是指:垃圾清理的过程中用户线程还可以继续工作,所以CMS是并发收集器。

并行清理是指:有多个垃圾回收线程在执行清理,所以Parallel收集器是并行收集器

-------------------------------常见调优:

 

1. 串行收集器

串行收集器是最古老,最稳定以及效率高的收集器
可能会产生较长的停顿,只使用一个线程去回收
-XX:+UseSerialGC

  • 新生代、老年代使用串行回收
  • 新生代复制算法
  • 老年代标记-压缩

串行收集器的日志输出:

 

 
  1. 0.844: [GC 0.844: [DefNew: 17472K->2176K(19648K), 0.0188339 secs] 17472K->2375K(63360K), 0.0189186 secs] [Times: user=0.01 sys=0.00, real=0.02 secs]
    
    
    8.259: [Full GC 8.259: [Tenured: 43711K->40302K(43712K), 0.2960477 secs] 63350K->40302K(63360K), [Perm : 17836K->17836K(32768K)], 0.2961554 secs] [Times: user=0.28 sys=0.02, real=0.30 secs]

     

  2.  

2. 并行收集器

2.1 ParNew

-XX:+UseParNewGC(new代表新生代,所以适用于新生代)

  • 新生代并行
  • 老年代串行

Serial收集器新生代的并行版本
在新生代回收时使用复制算法
多线程,需要多核支持
-XX:ParallelGCThreads 限制线程数量

并行收集器的日志输出:

0.834: [GC 0.834: [ParNew: 13184K->1600K(14784K), 0.0092203 secs] 13184K->1921K(63936K), 0.0093401 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

2.2 Parallel收集器

类似ParNew 
新生代复制算法 
老年代标记-压缩 
更加关注吞吐量 
-XX:+UseParallelGC  

  • 使用Parallel收集器+ 老年代串行

-XX:+UseParallelOldGC 

  • 使用Parallel收集器+ 老年代并行

Parallel收集器的日志输出:

1.500: [Full GC [PSYoungGen: 2682K->0K(19136K)] [ParOldGen: 28035K->30437K(43712K)] 30717K->30437K(62848K) [PSPermGen: 10943K->10928K(32768K)], 0.2902791 secs] [Times: user=1.44 sys=0.03, real=0.30 secs]

2.3 其他GC参数

-XX:MaxGCPauseMills

  • 最大停顿时间,单位毫秒
  • GC尽力保证回收时间不超过设定值

-XX:GCTimeRatio 

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

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

3. CMS收集器

  • Concurrent Mark Sweep 并发标记清除(应用程序线程和GC线程交替执行)
  • 使用标记-清除算法
  • 并发阶段会降低吞吐量(停顿时间减少,吞吐量降低)
  • 老年代收集器(新生代使用ParNew)
  • -XX:+UseConcMarkSweepGC

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

1. 初始标记(会产生全局停顿)

  • 根可以直接关联到的对象
  • 速度快

2. 并发标记(和用户线程一起) 

  • 主要标记过程,标记全部对象

3. 重新标记 (会产生全局停顿) 

  • 由于并发标记时,用户线程依然运行,因此在正式清理前,再做修正

4. 并发清除(和用户线程一起) 

  • 基于标记结果,直接清理对象

这里就能很明显的看出,为什么CMS要使用标记清除而不是标记压缩,如果使用标记压缩,需要多对象的内存位置进行改变,这样程序就很难继续执行。但是标记清除会产生大量内存碎片,不利于内存分配。 

CMS收集器的日志输出:

 

 
  1. 1.662: [GC [1 CMS-initial-mark: 28122K(49152K)] 29959K(63936K), 0.0046877 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
    
    1.666: [CMS-concurrent-mark-start]
    
    1.699: [CMS-concurrent-mark: 0.033/0.033 secs] [Times: user=0.25 sys=0.00, real=0.03 secs]
    
    1.699: [CMS-concurrent-preclean-start]
    
    1.700: [CMS-concurrent-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
    
    1.700: [GC[YG occupancy: 1837 K (14784 K)]1.700: [Rescan (parallel) , 0.0009330 secs]1.701: [weak refs processing, 0.0000180 secs] [1 CMS-remark: 28122K(49152K)] 29959K(63936K), 0.0010248 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
    
    1.702: [CMS-concurrent-sweep-start]
    
    1.739: [CMS-concurrent-sweep: 0.035/0.037 secs] [Times: user=0.11 sys=0.02, real=0.05 secs]
    
    1.739: [CMS-concurrent-reset-start]
    
    1.741: [CMS-concurrent-reset: 0.001/0.001 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

     

  2.  

CMS收集器特点:

尽可能降低停顿
会影响系统整体吞吐量和性能

  • 比如,在用户线程运行过程中,分一半CPU去做GC,系统性能在GC阶段,反应速度就下降一半

清理不彻底 

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

因为和用户线程一起运行,不能在空间快满时再清理(因为也许在并发GC的期间,用户线程又申请了大量内存,导致内存不够) 

  • -XX:CMSInitiatingOccupancyFraction设置触发GC的阈值
  • 如果不幸内存预留空间不够,就会引起concurrent mode failure
 
  1. 33.348: [Full GC 33.348: [CMS33.357: [CMS-concurrent-sweep: 0.035/0.036 secs] [Times: user=0.11 sys=0.03, real=0.03 secs]
    
    (concurrent mode failure): 47066K->39901K(49152K), 0.3896802 secs] 60771K->39901K(63936K), [CMS Perm : 22529K->22529K(32768K)], 0.3897989 secs] [Times: user=0.39 sys=0.00, real=0.39 secs]

     

一旦 concurrent mode failure产生,将使用串行收集器作为后备。

CMS也提供了整理碎片的参数:

-XX:+ UseCMSCompactAtFullCollection Full GC后,进行一次整理

  • 整理过程是独占的,会引起停顿时间变长

-XX:+CMSFullGCsBeforeCompaction  

  • 设置进行几次Full GC后,进行一次碎片整理

-XX:ParallelCMSThreads 

  • 设定CMS的线程数量(一般情况约等于可用CPU数量)

CMS的提出是想改善GC的停顿时间,在GC过程中的确做到了减少GC时间,但是同样导致产生大量内存碎片,又需要消耗大量时间去整理碎片,从本质上并没有改善时间。 

4. G1收集器

G1是目前技术发展的最前沿成果之一,HotSpot开发团队赋予它的使命是未来可以替换掉JDK1.5中发布的CMS收集器。

与CMS收集器相比G1收集器有以下特点:

1. 空间整合,G1收集器采用标记整理算法,不会产生内存空间碎片。分配大对象时不会因为无法找到连续空间而提前触发下一次GC。

2. 可预测停顿,这是G1的另一大优势,降低停顿时间是G1和CMS的共同关注点,但G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为N毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒,这几乎已经是实时Java(RTSJ)的垃圾收集器的特征了。

上面提到的垃圾收集器,收集的范围都是整个新生代或者老年代,而G1不再是这样。使用G1收集器时,Java堆的内存布局与其他收集器有很大差别,它将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔阂了,它们都是一部分(可以不连续)Region的集合。

G1的新生代收集跟ParNew类似,当新生代占用达到一定比例的时候,开始出发收集。

和CMS类似,G1收集器收集老年代对象会有短暂停顿。

步骤:

  1. 标记阶段,首先初始标记(Initial-Mark),这个阶段是停顿的(Stop the World Event),并且会触发一次普通Mintor GC。对应GC log:GC pause (young) (inital-mark)
  2. Root Region Scanning,程序运行过程中会回收survivor区(存活到老年代),这一过程必须在young GC之前完成。
  3. Concurrent Marking,在整个堆中进行并发标记(和应用程序并发执行),此过程可能被young GC中断。在并发标记阶段,若发现区域对象中的所有对象都是垃圾,那个这个区域会被立即回收(图中打X)。同时,并发标记过程中,会计算每个区域的对象活性(区域中存活对象的比例)。
  4. Remark, 再标记,会有短暂停顿(STW)。再标记阶段是用来收集 并发标记阶段 产生新的垃圾(并发阶段和应用程序一同运行);G1中采用了比CMS更快的初始快照算法:snapshot-at-the-beginning (SATB)。
  5. Copy/Clean up,多线程清除失活对象,会有STW。G1将回收区域的存活对象拷贝到新区域,清除Remember Sets,并发清空回收区域并把它返回到空闲区域链表中。
  6. 复制/清除过程后。回收区域的活性对象已经被集中回收到深蓝色和深绿色区域。

5. 安全点

GC的停顿主要来源于可达性分析上,程序执行时并非在所有地方都能停顿下来开始GC,只有在到达安全点时才能暂停。

安全点的选定基本上是以程序“是否具有让程序长时间执行的特征”为标准进行选定的——因为每条指令执行的时间都非常短暂,程序不太可能因为指令流长度太长这个原因而过长时间运行,“长时间执行”的最明显特征就是指令序列复用,例如方法调用、循环跳转、异常跳转等,所以具有这些功能的指令才会产生安全点。

接下来的问题就在于,如何让程序在需要GC时都跑到安全点上停顿下来,大多数JVM的实现都是采用主动式中断的思想。

主动式中断的思想是当GC需要中断线程的时候,不直接对线程操作,仅仅简单地设置一个标志,各个线程执行时主动去轮询这个标志,发现中断标志为真时就自己中断挂起,轮询标志的地方和安全点是重合的,另外再加上创建对象需要分配内存的地方。

其他:

只要记住流行的组合就这几种情况
Serial  一般情况
ParNew + CMS 
ParallelYoung + ParallelOld
G1GC

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

如果是吞吐量优先,可以考虑Parrallel Scavenge和Parrallel Old垃圾回收器组合;这种一般出现在科学计算、数据挖掘、thrput等场景。如果是响应时间优先,那么要考虑JDK的版本,如果是JDK1.8那么可以使用G1,或者ParNew和CMS垃圾回收器组合(效果比G1要差一点);这种一般出现在网页GUI、API等场景。

股票日均百万交易系统 JVM堆栈大小设置与调优
热力站亿级别流量系统堆内年轻代与老年代回收设置与调优
高并发系统如果使用G1垃圾回收优化性能
每秒10万并发秒杀系统为什么会频繁发生GC
订单年结算 严重full GC导致系统卡死
线上OOM监控及定位与解决

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

杨航 AI

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值