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
- 新生代、老年代使用串行回收
- 新生代复制算法
- 老年代标记-压缩
串行收集器的日志输出:
-
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.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.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]
CMS收集器特点:
尽可能降低停顿
会影响系统整体吞吐量和性能
- 比如,在用户线程运行过程中,分一半CPU去做GC,系统性能在GC阶段,反应速度就下降一半
清理不彻底
- 因为在清理阶段,用户线程还在运行,会产生新的垃圾,无法清理
因为和用户线程一起运行,不能在空间快满时再清理(因为也许在并发GC的期间,用户线程又申请了大量内存,导致内存不够)
- -XX:CMSInitiatingOccupancyFraction设置触发GC的阈值
- 如果不幸内存预留空间不够,就会引起concurrent mode failure
-
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收集器收集老年代对象会有短暂停顿。
步骤:
- 标记阶段,首先初始标记(Initial-Mark),这个阶段是停顿的(Stop the World Event),并且会触发一次普通Mintor GC。对应GC log:GC pause (young) (inital-mark)
- Root Region Scanning,程序运行过程中会回收survivor区(存活到老年代),这一过程必须在young GC之前完成。
- Concurrent Marking,在整个堆中进行并发标记(和应用程序并发执行),此过程可能被young GC中断。在并发标记阶段,若发现区域对象中的所有对象都是垃圾,那个这个区域会被立即回收(图中打X)。同时,并发标记过程中,会计算每个区域的对象活性(区域中存活对象的比例)。
- Remark, 再标记,会有短暂停顿(STW)。再标记阶段是用来收集 并发标记阶段 产生新的垃圾(并发阶段和应用程序一同运行);G1中采用了比CMS更快的初始快照算法:snapshot-at-the-beginning (SATB)。
- Copy/Clean up,多线程清除失活对象,会有STW。G1将回收区域的存活对象拷贝到新区域,清除Remember Sets,并发清空回收区域并把它返回到空闲区域链表中。
- 复制/清除过程后。回收区域的活性对象已经被集中回收到深蓝色和深绿色区域。
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监控及定位与解决