JVM调优相关

JVM调优相关:

一、运行时数据区

1、堆

①:线程共享区域(线程不安全)
②:存储new出来的对象(垃圾回收器主要工作区域)
③:年轻代、老年代

Java堆区可以划分为新生代和老年代,新生代又可以进一步划分为Eden(生成)区、Survivor (幸存者)1区、Survivor (幸存者)2区
年轻代:新new出来的对象存放在年轻代Eden区中,当Eden区满了触发Minor GC,存活下来的对象存放到Survivor (幸存者)1区,当Eden区再次满了触发Minor GC,会扫描Eden区和Survivor (幸存者)1区,存活对象移动到Survivor (幸存者)2区(如果此时该区空间不够,由老年代分担),清空Eden区和Survivor (幸存者)1区对象,保证一段时间内总有一个Survivor是空的,达到Minor GC限制次数的对象存储到老年代中(由JVM参数MaxTenuringThreshold决定)。
老年代:老年代存储长期存活的对象,占满时会触发Major GC=Full GC,GC期间会停止所有线程等待GC完成,所以对响应要求高的应用尽量减少发生Major GC,避免响应超时。
分代的目的是:根据对象存活时间进行分类,对存活时间长的对象,放到固定区,从而减少扫描垃圾时间及GC频率

④:堆内存常用参数
参数描述
-Xms堆内存初始大小,单位m、g
-Xmx(MaxHeapSize)堆内存最大允许大小,一般不要大于物理内存的80%
-XX:PermSize非堆内存初始大小,一般应用设置初始化200m,最大1024m就够
-XX:MaxPermSize非堆内存最大允许大小
-XX:NewSize(-Xns)年轻代内存初始大小
-XX:MaxNewSize(-Xmn)年轻代内存最大允许大小,也可以缩写
-XX:SurvivorRatio=8年轻代中Eden区与Survivor区的容量比例值,默认为8,即8:1
-Xss堆栈内存大小
–XX:+PrintGCDetails打印 GC 信息
-XX:+HeapDumpOnOutOfMemoryError让虚拟机在发生内存溢出时 Dump 出当前的内存堆转储快照,以便分析用

2、方法区

①:线程共享区域(线程不安全)
②:存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据

3、虚拟机栈

①:每个线程在创建的时候都会创建一个虚拟机栈,其内部保存一个个栈帧。每个方法被执行的时候,java虚拟机都会同步创建一个栈帧
②:栈帧包括局部变量表(8种基本数据类型,对象引用地址)、操作数栈、动态链接、方法返回地址和一些附加信息。
 每个栈帧包括5个组成部分:局部变量表(Local Variables)、操作数栈(Operand Stack)、动态链接(Dynamic Linking)、方法返回地(Return Address)和一些附加信息
 1.局部变量表:局部变量表式一个数字数组,用于存储方法参数和方法体内部的局部变量
 2.每一个独立的栈帧中除了包含局部变量表外,还包含一个FILO的操作数栈,用于保存计算过程中的中间结果,同时作为计算过程中变量临时的存储空间
 3.在Java源文件被编译成字节码文件时,所有的变量和方法引用都作为符号引用保存在class文件的常量池里,动态链接的作用就是为了将这些符号引用转换为调用方法的直接引用
③:每一个方法被调用直至执行完毕的过程,就对相应这一个栈帧在虚拟机从入栈到出栈的过程。

4、本地方法栈

为虚拟机使用到的Native方法服务

5、程序计数器

它的作用就是记录当前线程所执行的位置。 这样,当线程重新获得CPU的执行权的时候,就直接从记录的位置开始执行,分支、循环、跳转、异常处理也都依赖这个程序计数器来完成。

二、垃圾回收算法

java使用可达分析法来判断对象是否存活,GC ROOTS对象包括以下几种
1、虚拟机栈中的引用的对象(例如正在运行方法所使用的参数、局部变量、临时变量等)
2、方法区中静态属性引用的对象(例如java类的引用类型静态变量)
3、字符串常量池里的引用
4、Native引用的对象
5、所有被同步锁(synchronized)持有的对象
6、java虚拟机内部的引用

GC Roots 枚举的过程中,是需要暂停用户线程的,对栈进行扫描,找到哪些地方存储了对象的引用。
然而,栈存储的数据不止是对象的引用,因此对整个栈进行全量扫描,显然是很耗费时间,影响性能的。
因此,在 HotSpot 中采取了空间换时间的方法,使用 OopMap 来存储栈上的对象引用的信息。在类加载完成的时候,HotSpot就会把对象内什么偏移量上是什么类型的数据计算出来,在即时编译过程中,也会在特定的位置(安全点 主要包括1、循环的末尾 2、方法临返回前 / 调用方法的call指令后 3、可能抛异常的位置)记录下栈里和寄存器里哪些位置是引用。
在 GC Roots 枚举时,只需要遍历每个栈桢的 OopMap,通过 OopMap 存储的信息,快捷地找到 GC Roots。
为了避免引用关系变化或导致OopMap内容变化的指令,引入了安全点和安全区域的概念
安全点: 强制用户程序执行到安全点后,才能暂停程序,开始垃圾收集
安全区域: 程序不执行时无法使用安全点(即线程没有分配到 CPU 片),为了解决用户线程处于sleep状态等,线程无法响应虚拟机的中断请求,不能再走到安全的地方中断自己的情况,引入安全区域的概念(在某一片代码片段中,引用关系不会变化),当用户线程执行到安全区域里面的代码时,首先会辨识自己已经进入了安全区域,当要发起垃圾回收时就会忽略已声明了自己在安全区域的线程。当线程要离开安全区域时,会检查虚拟机是否完成了根节点枚举,如果未完成,则要等待收到可以离开安全区域的信号才能离开。
在这里插入图片描述

https://zhuanlan.zhihu.com/p/441867302

1、标记清除算法

标记清除算法是最基础的回收算法,分为标记和清除两个部分:首先标记出所有需要回收的对象,这一过程在可达性分析过程中进行。在标记完之后统一回收所有被标记的对象。
不足:会出现内存碎片,内存碎片太多会导致以后的程序运行中无法分配出较大的内存,从内不得不触发另外的垃圾回收。

2、复制算法

将新生代内存分为2个等量内存空间,每次只使用其中一块内存空间,触发GC时,会将该空间存活的对象复制到另一块空间,并将该空间的对象全部删除

3、标记整理算法(老年代回收算法)

复制算法在对象存活率较高的老年代会进行很多次的复制操作,效率很低,所以在栈的老年代不适用复制算法。
针对老年代对象存活率高的特点,提出了一种称之为”标记-整理算法”。标记过程仍与”标记-清除”过程一致,但后续步骤不是直接对可回收对象进行清理,而是让所   有存活对象都向一端移动,然后直接清理掉端边界以外的内存。
发生在老年代的GC称为Full GC,又称为Major GC,其经常会伴随至少一次的Minor GC(并非绝对,在Parallel Scavenge收集器中就有直接进行Full GC的策略选择过程)。Major GC的速度一般会比Minor GC慢10倍以上。

三、垃圾收集器

1、分代收集理论

① 跨代引用问题

进行分代收集后,垃圾回收器可以根据不同区域利用不同算法进行垃圾回收,但是会有存在对象之间跨代引用问题
例如:只进行Minor GC时新生代对象被老年代引用,此时就需要遍历整个老年代所有对象来确保可达性分析法的正确,为了解决这个问题,需要引入 ‘跨代引用假说法则’:跨代引用相对于同代引用来说仅占极少数,只需要在新生代上建立一个记忆集(Remembered Set),这个结构把老年代分为若干小块,标识出那一块存在跨代引用,此后发生Minor GC,只扫描这一小块就可以了。

②卡表

最常用的记忆集实现形式是卡表:卡表最简单的实现形式是字节数组 字节数组(CARD_TABLE)的每一个元素都对应着其标识的内存区域中一块特定大小的内存块,这个内存块称作"卡页"(Card Page),一个卡页的内存中通常包含不止一个对象,只要卡页内有一个(或更多)对象的字段存在跨代指针,就标识为1(变脏),没有则标识为0.垃圾收集时只要筛选出卡表中变脏的元素加入GC Roots就可以了

③写屏障

卡表元素何时变脏、如何变脏
何时变脏:发生在引用类型字段赋值的那一刻
如何变脏:
解释执行的场景中,在运行过程中就能操作。编译执行的场景经过编译后的代码已经是纯粹的机器指令流了,Hot Spot通过写屏障在机器码层面来维护卡表状态的,写屏障可看做在虚拟机层面对“引用类型字段赋值”这个动作的AOP切面,赋值前的屏障叫写前屏障,赋值后的屏障叫写后屏障,赋值后虚拟机就会对卡表状态进行更新

2、收集器对比

收集器串行/并行/并发新生代/老年代算法目标适用场景
Serial串行新生代复制算法响应速度优先单cpu
Serial Old串行老年代标记-整理算法响应速度优先单cpu、cms后备方案
Par New并行新生代复制算法响应速度优先多cpu与cms配合
Parallel Scavenge并行新生代复制算法吞吐量优先后台运算不需要太多交互
Parallel Old并行老年代标记-整理算法吞吐量优先后台运算不需要太多交互
CMS并发老年代标记-清除算法响应速度优先互联网或B/S系统服务
G1并发both标记-整理算法+复制算法响应速度优先面向服务端应用

3、CMS收集器

CMS收集器是缩短暂停应用时间为目标而设计的,是基于标记-清除算法实现,整个过程分为4个步骤包括:

①:初始标记(需要暂停应用线程)

初始标记只是标记一下GC Roots能直接关联到的对象,速度很快

②:并发标记

并发标记是为了解决由GC Root往下遍历对象图时,进行标记过程,由于堆越大,存储的对象越多,对象图结构越复杂,要标记更多对象,所以产生的停顿时间也自然就长了,所以需要让垃圾回收器和用户线程同时运行,并发工作(并发标记)来解决该问题。
由于可达性分析法必须要求全过程都基于一个能保障一致性的快照中才能进行对象图遍历,需要引入三色标记概念
白色: 表示对象尚未被垃圾回收器访问过。显然,在可达性分析刚刚开始的阶段,所有的对象都是白色的,若在分析结束的阶段,仍然是白色的对象,即代表不可达
黑色: 表示对象已经被垃圾回收器访问过,且这个对象的所有引用都已经扫描过。黑色的对象代表已经扫描过,它是安全存活的,如果有其它的对象引用指向了黑色对象,无须重新扫描一遍。黑色对象不可能直接(不经过灰色对象)指向某个白色对象。
灰色: 表示对象已经被垃圾回收器访问过,但这个对象至少存在一个引用还没有被扫描过。

三色标记概念可知,垃圾回收器标记颜色时,如果用户线程修改了引用关系(对象图结构)就会出现两种后果
①:把原本消亡的对象错误的标记为存活,这不是好事,但是其实是可以容忍的,只不过产生了一点逃过本次回收的浮动垃圾而已,下次清理就可以。
②:把原本存活的对象错误的标记为已消亡,这就是非常严重的后果了,一个程序还需要使用的对象被回收了,那程序肯定会因此发生错误。
第二种情况会产生对象消失,对象消失存在两种必然条件
条件一: 赋值器插入了一条或者多条从黑色对象到白色对象的新引用。
条件二: 赋值器删除了全部从灰色对象到该白色对象的直接或间接引用。
于是产生了两种解决方案:增量更新(Incremental Update)和原始快照(Snapshot At The Beginning,SATB)
增量更新要破坏的是第一个条件(赋值器插入了一条或者多条从黑色对象到白色对象的新引用),当黑色对象插入新的指向白色对象的引用关系时,就将这个新插入的引用记录下来,等并发扫描结束之后,再将这些记录过的引用关系中的黑色对象为根,重新扫描一次。
可以简化的理解为:黑色对象一旦插入了指向白色对象的引用之后,它就变回了灰色对象

原始快照要破坏的是第二个条件(赋值器删除了全部从灰色对象到该白色对象的直接或间接引用),当灰色对象要删除指向白色对象的引用关系时,就将这个要删除的引用记录下来,在并发扫描结束之后,再将这些记录过的引用关系中的灰色对象为根,重新扫描一次。
这个可以简化理解为:无论引用关系删除与否,都会按照刚刚开始扫描那一刻的对象图快照开进行搜索。

③:重新标记(需要暂停应用线程)

重新标记阶段则是为了修正并发标记期间因用户程序继续运作导致标记产生变动的那一部分对象的标记记录,这个阶段暂停时间比初始标记阶段稍长一点,但远比并发标记时间短

④:并发清除

由于整个过程中消耗最长的并发标记和并发清除过程收集器线程都可以与用户线程一起工作,所以,CMS收集器内存回收与用户一起并发执行的,大大减少了暂停时间。

4、G1收集器

G1收集器将堆内存划分多个大小相等的独立区域(Region),并且能预测暂停时间,能预测原因它能避免对整个堆进行全区收集。G1跟踪各个Region里的垃圾堆积价值大小(所获得空间大小以及回收所需时间),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region,从而保证了再有限时间内获得更高的收集效率。G1收集器标记步骤与CMS类似,并发清除时会对各个Region回收价值和成本进行排序,根据用户所期望的GC暂停时间来执行回收。

5、垃圾收集器参数

参数描述
-XX:+UseSerialGC虚拟机运行在Client模式下的默认值,打开此开关后,使用Serial + Serial Old的收集器组合进行内存回收
-XX:+UseParNewGC打开此开关后,使用ParNew + Serial Old的收集器组合进行内存回收
-XX:+UseConcMarkSweepGC打开此开关后,使用ParNew+ CMS + Serial Old的收集器组合进行内存回收。Serial Old收集器将作为CMS收集器出现Concurrent Mode Failure失败后的后备收集器使用
-XX:+UseParallelGC虚拟机运行在Server模式下的默认值,打开此开关后,使用Parallel Scavenge + Serial Old (PS Mark Sweep)的收集器组合进行内存回收
-XX:+UseParallelOldGC打开此开关后,使用Parallel Scavenge + Parallel Old的收集器组合进行内存回收
-XX:+UseParallelGCThreads=8并行收集器线程数,同时有多少个线程进行垃圾回收,一般与CPU数量相等
-XX:+UseParallelOldGC指定老年代为并行收集
-XX:+UseCMSCompactAtFullCollection开启内存空间压缩和整理,防止过多内存碎片
-XX:CMSFullGCsBeforeCompaction=0表示多少次Full GC后开始压缩和整理,0表示每次Full GC后立即执行压缩和整理
-XX:CMSInitiatingOccupancyFraction=80%表示老年代内存空间使用80%时开始执行CMS收集,防止过多的Full GC
-XX:+UseG1GCG1收集器
-XX:MaxTenuringThreshold=0在年轻代经过几次GC后还存活,就进入老年代,0表示直接进入老年代
-XX:PretenureSizeThreshold直接晋升到老年代的对象大小,设置这个参数后,大于这个参数的对象将直接在老年代分配
-XX:UseAdaptiveSizePolicy动态调整Java堆中各个区域的大小以及进入老年代的年龄
-XX:HandlePromotionFailure是否允许分配担保失败,即老年代的剩余空间不足以应付新生代的整个Eden和Survivor区的所有对象都存活的极端情况
-XX:MaxTenuringThreshold=0在年轻代经过几次GC后还存活,就进入老年代,0表示直接进入老年代
-XX:GCTimeRatioGC时间占总时间的比率,默认值是99, 即允许1%的GC时间。仅在使用Parallel Scavenge收集器时生效
-XX:MaxGCPauseMillis设置GC的最大停顿时间。仅在使用Parallel Scavenge收集器时生效

6、GC日志

参数描述
-XX:+PrintGC开启GC日志 默认关闭
-XX:+PrintGCDetails开启更详细GC日志 默认关闭
-XX:+PrintGCDateStamps用于分析GC时间间隔 默认关闭
-Xloggc:filenameGC日志输出到哪里 自定义
-XX:+PrintGC
-XX:+PrintGC

Minor GC日志:
Full GC日志:

四、JVM常用命令

1、jps

jps -l 输出主类或者jar的完全路径名
jps -v 输出jvm参数
jps -q 仅仅显示java进程号
jps -m 输出JVM启动时传递给main()的参数
jps -lv 显示jar+jvm参数,一般部署多个服务就要用这个,边看服务名边看对应jvm参数
jps -mlv 即m+l+v的都输出来

2、jstat

(JVM statistics Monitoring)是用于监视虚拟机运行时状态信息的命令,它可以显示出虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据。

3、jmap

jmap -heap pid 与堆相关的详细信息
jmap -histo:live pid 显示堆中对象的统计信息
jmap -clstats 显示Java堆中元空间的类加载器的统计信息
jmap -dump:live,format=b,file=
(live参数是可选的,如果指定,则只转储堆中的活动对象;如果没有指定,则转储堆中的所有对象。
format=b表示以hprof二进制格式转储Java堆的内存。
file=用于指定快照dump文件的文件名。)

4、jhat

jhat(JVM Heap Analysis Tool)命令是与jmap搭配使用,用来分析jmap生成的dump,jhat内置了一个微型的HTTP/HTML服务器,生成dump的分析结果后,可以在浏览器中查看。在此要注意,一般不会直接在服务器上进行分析,因为jhat是一个耗时并且耗费硬件资源的过程,一般把服务器生成的dump文件复制到本地或其他机器上进行分析。

5、jstack

jstack用于生成java虚拟机当前时刻的线程快照。线程快照是当前java虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等。线程出现停顿的时候通过jstack来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做什么事情,或者等待什么资源。

五、性能检测工具

1、jconsole

在JDK中自带的java监控和管理控制台,用于对JVM中内存,线程和类等的监控,是一个基于JMX(java management extensions)的GUI性能监测工具。jconsole使用jvm的扩展机制获取并展示虚拟机中运行的应用程序的性能和资源消耗等信息。

2、VisualVM

VisualVM(All-in-One Java Troubleshooting Tool)是功能最强大的运行监视和故障处理程序之一,曾经在很长一段时间内是Oracle官方主力发展的虚拟机故障处理工具。
相比一些第三方工具,VisualVM有一个很大的优点:不需要被监视的程序基于特殊Agent去运行,因此它的通用性很强,对应用程序实际性能的影响也较小,使得它可以直接应用在生产环境中。
Visual GC 是常常使用的一个功能,需要通过插件按照,可以明显的看到年轻代、老年代的内存变化,以及gc频率、gc的时间等

JVM调优各种现象

1、GC很频繁

两种可能 ①、堆内存太小了,需要频繁GC才能释放空间 ②、OOM了,没空间了,导致频繁GC

2、间接性的卡顿

设置合理的停顿时间

3、某一区域频繁GC,其他正常

如果对应区域空间不足,导致需要频繁GC来释放空间,在JVM堆内存无法增加的情况下,可以调整对应区域的大小比率,也有可能是OOM的原因

4、老年代频繁GC,每次回收的对象很多

如果升代年龄小,新生代的对象很快就进入老年代了,导致老年代对象变多,而这些对象其实在随后的很短时间内就可以回收,这时候可以调整对象的升级代年龄,让对象不那么容易进入老年代解决老年代空间不足频繁GC问题。

注意:增加了年龄之后,这些对象在新生代的时间会变长可能导致新生代的GC频率增加,并且频繁复制这些对象新生的GC时间也可能变长。

5、老年代频繁GC,每次回收的对象很多,而且单个对象的体积都比较大。

如果大量的大对象直接分配到老年代,导致老年代容易被填满而造成频繁GC,可设置对象直接进入老年代的标准。

注意:这些大对象进入新生代后可能会使新生代的GC频率和时间增加。

-XX:PretenureSizeThreshold=1000000 新生代可容纳的最大对象,大于则直接会分配到老年代,0代表没有限制。

6、CMS,G1 经常 Full GC,程序卡顿严重。

G1和CMS 部分GC阶段是并发进行的,业务线程和垃圾收集线程一起工作,也就说明垃圾收集的过程中业务线程会生成新的对象,所以在GC的时候需要预留一部分内存空间来容纳新产生的对象,如果这个时候内存空间不足以容纳新产生的对象,那么JVM就会停止并发收集暂停所有业务线程(STW)来保证垃圾收集的正常运行。这个时候可以调整GC触发的时机(比如在老年代占用60%就触发GC),这样就可以预留足够的空间来让业务线程创建的对象有足够的空间分配。

//使用多少比例的老年代后开始CMS收集,默认是68%,如果频繁发生SerialOld卡顿,应该调小
-XX:CMSInitiatingOccupancyFraction

//G1混合垃圾回收周期中要包括的旧区域设置占用率阈值。默认占用率为 65%
-XX:G1MixedGCLiveThresholdPercent=65

7、GC的次数、时间和回收的对象都正常,堆内存空间充足,但是报OOM

JVM除了堆内存之外还有一块堆外内存,这片内存也叫本地内存,可是这块内存区域不足了并不会主动触发GC,只有在堆内存区域触发的时候顺带会把本地内存回收了,而一旦本地内存分配不足就会直接报OOM异常。

注意:本地内存异常的时候除了上面的现象之外,异常信息可能是OutOfMemoryError:Direct buffer memory。解决方式除了调整本地内存大小之外,也可以在出现此异常时进行捕获,手动触发GC(System.gc())。

8、访问量暴增后,网站反应页面响很慢

jstat -gc pid查看GC情况,如果频率非常高,GC所占用的时间非常长,大部分是因为对象创建速度非常快,导致堆内存容易填满从而频繁GC,所以这里问题在于新生代内存太小,所以这里可以增加JVM内存就行了

9、偶发性的引发OOM异常

①、加大堆内存试试
②、获得堆内存的dump文件,跟踪着大内存对象找到其引用的地方

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值