jvm调优

2023-05-16

Jvm调优

  1. java代码是怎么运行起来的

 

 

  1. 类加载到使用的过程

 加载:用这个类的时候开始加载。类在使用的时候触发类加载器,把类加载到jvm中。

 

2.1验证阶段

类加载之前先进行验证是否符合JVM规范后交给JVM。比如final是否合规、类型是否正确、静态变量是否合理等

2.2准备阶段

加载完成后进入准备阶段,准备阶段是为static 字段分配内存,并设定默认值,解析类和方法确保类与类之间的相互引用正确性,完成内存结构布局

2.3解析阶段

解析阶段是将符号引用转化为直接引用的过程

 

 

符号引用:用一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时可以定位到目标即可

直接引用:直接引用可以直接指向目标的指针,相对偏移量或者一个能间接定位到目标的句柄.如果有了直接引用,那么引用的目标在虚拟机的内存中一定存在.

并未明确指出解析的具体时间,可以是在加载时就解析常量池中的符号引用,或者是等到第一个符号引用将要被使用前解析它,这个java虚拟机规范中没有明确说明.对同一个符号引用进行多次解析是存在的,虚拟机可以对第一次解析的结果进行缓存,譬如运行时直接引用常量池中的状态,并把常量标示为已解析状态,从而避免了重复动作.

解析动作主要针对类或接口,字段,类方法,接口方法,方法类型,方法句柄,调用点限定符等7类符号引用进行,分别对应了常量池中的constant_class_info,constant_firldref_info,constant_methodref_info,constant_interfacemethodref_info,constant_methodtype_info,constant_methodhandle_info,constant_dynamic_info和constant_invokedyanmic_info一共8种常量类型.

2.4核心阶段:初始化

其实就是为变量分配内存空间,并确定其初始值的过程。

初始化的内容:static修饰的变量、静态代码块、static中的new对象,main方法中的new对象,初始化类是如果父类没有初始化,必须先初始化父类。

2.5类加载器和双亲委派

类加载就是将磁盘上的class文件加载到内存中。

2.5.1启动类加载器 Bootstrap ClassLoader

负责加载我们机器上安装的java目录下的核心类,所以JVM启动就会委托启动类加载器去加载java安装目录下的“lib”目录中的核心类库。

2.5.2扩展类加载器Extension ClassLoader

Extension ClassLoader与Bootstrap ClassLoader 类似Extension ClassLoader加载的是“lib/ext”中的一些核心类。

2.2.3应用程序加载器 Application ClassLoader

Application ClassLoader负责加载”ClassPath”环境变量所指定的路径中的类。大致就是自己写的java代码加载到内存。

2.2.4自定义类加载器

自己定义的类加载器;继承ClassLoader进行自定义。

2.2.5双亲委派

当一个类加载器收到了类加载的请求的时候,它不会直接去加载指定的类,而是把这个请求委托给自己的父加载器去加载,每一个加载器都是如此。

  1. 如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行。
  2. 2、如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器。
  3. 3、如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式。
  1. JVM中有哪些内存区域,都是干嘛的?

Jvm在运行我们写好的代码时,它必须使用多块内存空间,不同的空间存放不同的数据,然后配合我们写的代码流程,让系统运行起来。

3.1存放类的方法区Metaspace 元空间

3.2程序计数器

Java代码编译成字节码.class后就会使用字节码执行引擎去执行.class字节码指令,程序计数器就是用来记录当前执行的字节码指令的位置。Java是多线程的所以一个线程会有一个程序计数器。

3.3 Java虚拟机栈

虚拟机栈也称为Java栈,每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧(Stack Frame),每个线程都有自己的java虚拟机栈。

Java虚拟机栈属于线程私有,它的生命周期与线程相同(随线程而生,随线程而灭);

虚拟机栈说明了线程运行时的瞬时状态;每次方法调用,都会产生对应的栈帧;

栈帧包括局部变量表、操作数栈、动态链接、方法返回地址和一些附加信息;

每个方法被调用至执行完毕的过程,就对应这个栈帧在虚拟机栈中从入栈到出栈的完整过程;

栈的深度有限制;

举例:

栈帧情况如下:

Java初始换后虚拟机中的几块内存:

Java方法执行完毕就会从虚拟机栈中出栈,对象就没有指向了,也就是说没有一个变量指向java堆内存中的ReplicaManager对象了。

执行完毕后,replicaManager中loadReplicasFromDisk()方法出栈,对象指向消失。对象留在内存中占用有限的资源。

3.4 JVM垃圾回收

占用的对象需要JVM的垃圾回收机制。JVM本身运行这一个自运动的线程,只要启动一个进程,jvm就自带一个垃圾回收的线程,垃圾回收线程就会不断的检查JVM内存中的各个实例对象。

如果实例没有指向jvm垃圾回收机制就会定期清理掉释放资源。

3.4.1年轻代、老年代

举例如下:两个ReplicaManager的存活时间,方法内的ReplicaManager存活时间极短,每次循环执行后就会在极短的时间内被回收;Static修饰的ReplicaManager创建后会一直被静态变量引用会一直留在内存中不会被回收ReplicaManager会长期存在老年代里。

快速被回收的就是年轻代,一直在内存中的就是老年代;上图里的方法区,其实就是所谓的永久代,你可以认为永久代就是放一些类信息的。

3.4.2新生代,老年代回收时机

新生代内存几乎满了又有新生代需要创建此时就会发生内存回收,也称之为“Minor Gc”也叫“Young GC”。他会尝试把新生代没有引用的对象全部回收。

老年代回收时机:一是“Young GC”前,一通检测发现“Young GC”之后要进入老年代的对象太多了,老年代放不下,此时需要提前触发Full GC,然后在再带着进行“Young GC”,要不就是在“Young GC”后发现老年代放不下了,再进行Full GC

向上面被static修饰的ReplicaManager每次回收都没有回收掉他的对象就会加1(年龄),新生代的对象年龄超过10岁就会被认为是“老年人”就会被转移到老年代中。具体所多少岁进入老年代可以通过JVM参数“-XX:MaxTenuringThreshold”来设置,默认是15岁。还有一种情况就是Survivor里存在的对象内存大于Survivor的50%,大于的那部分会进入老年代;还有就是年轻代中回收完的对象大于Survivor内存,那么就直接放入老年代。

老年代的 Old GC 则分成初始标记、并发标记、重新标记和并发清理这四个阶段。其中并发标记阶段需要追踪老年代中中所有存活的对象,老年代中存活的对象比新生代多得多,用的时间也就更多。

在并发清理阶段也不是一次向回收一大片对象,一点点分散在各处的垃圾对象。清理完之后还需要整理一次内存碎片,将大量存活的对象移动到一起,此时还会「Stop the world」就更加慢了。

最后,并发清理阶段会有新的对象进入老年代,此时如果老年代的内存不足会引发了“Concurrent Mode Failure”问题,就会使用“Serial Old”垃圾回收器,“Stop the World”之后慢慢重新来一遍回收的过程。

  1. 系统如何设置JVM内存大小

-Xms: java堆内存大小

-Xmx: java堆内存最大大小

-Xmn: java堆内存中新生代大小,扣除新生代就是老年代的内存大小了

-XX:PermSize:永久代大小

-XX:MaxPermSize:永久代最大大小

-Xss:每个线程的栈内存大小

‐XX:SurvivorRatio:Eden区与Survivor区的大小比值

-XX:MaxTenuringThreshold 设置年轻代‘岁数’,默认是15

-XX:CMSInitiatingOccupancyFaction 可以设置老年代占用多少空间后进行垃圾回收,JDK1.6默认是92%,预留8%让对象能够进入老年代。

-XX:CMSFullGCsBeforeCompaction,这个意思是执行多少次Full GC之后再进行内存整理。默认是0。

案例1:日均1百万订单三、四台机器4核8G设置如下:内存4G,堆内存3G,堆内存新生代1.5G或者2G,每个线程1M,永久代256M。

‐Xms3072M ‐Xmx3072M ‐Xmn1536M ‐Xss1M ‐XX:PermSize=256M ‐XX:MaxPermSize=256M ‐XX:SurvivorRatio=8

案例二:百万秒杀或大促,三台机器,整个内存使用4G,堆内存3G,新生代2G,老年代1G,永久代256M

‐Xms3072M ‐Xmx3072M ‐Xmn2048M ‐Xss1M ‐XX:PermSize=256M ‐XX:MaxPermSize=256M ‐XX:SurvivorRatio=8

案例二:日均上亿大型数据计算分布式主要是从各种数据库取数据,服务器是4核8G的配置,JVM分配4G。新生代老年代都是1.5G。平均每分钟负责执行100次数据计算,每次1万条需要10秒的时间,每条平均包含20个字段平均每条数据大概在1Kb左右,1万条数据就对应了10MB的大小,按每分钟100次计算一分钟以后1000Mb,Eden区里就基本全满了。按照80%年轻代被回收,就是还剩余200MB,Survivor中本身就是100MB所以200MB就进入了老年代,系统运行7分钟后老年代就快满了,JVM最终大概8分钟一次FULL GC非常影响性能,解决方式是3G堆内存2G给新生代,1G给老年代。Survivor大概就有200MB内存了。

‐Xms3072M ‐Xmx3072M ‐Xmn2048M ‐Xss1M ‐XX:PermSize=256M ‐XX:MaxPermSize=256M ‐XX:SurvivorRatio=8

4.1 JVM通用的优化方案(参数设置)

‐Xms3072M ‐Xmx3072M ‐Xmn2048M ‐Xss1M ‐XX:PermSize=256M ‐XX:MaxPermSize=256M ‐XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=5

4.2 大对象进入老年代

‐Xms3072M ‐Xmx3072M ‐Xmn2048M ‐Xss1M ‐XX:PermSize=256M ‐XX:MaxPermSize=256M ‐XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=5 -XX:PretenureSizeThreshold=1M

4.3 指定回收器

新生代使用ParNew,老年代使用CMS

‐Xms3072M ‐Xmx3072M ‐Xmn2048M ‐Xss1M ‐XX:PermSize=256M ‐XX:MaxPermSize=256M ‐XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=5 -XX:PretenureSizeThreshold=1M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC

4.4 Concurrent Model Failure并发模型故障

并发故障出现是因为年轻代有200M的对象进入老年代,老年代没有足够的内存来存放200M的对象,此时就会导致立马进入STW状态,并切换CMS为Serial Old,直接进制程序运行,然后单线程进行老年代垃圾回收,回收掉后再让系统运行,此时优化为:

‐Xms3072M ‐Xmx3072M ‐Xmn2048M ‐Xss1M ‐XX:PermSize=256M ‐XX:MaxPermSize=256M ‐XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=5 -XX:PretenureSizeThreshold=1M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFaction=92

4.5 CMS垃圾回收之后内存碎片整理频率应该多高?

‐Xms3072M ‐Xmx3072M ‐Xmn2048M ‐Xss1M ‐XX:PermSize=256M ‐XX:MaxPermSize=256M ‐XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=5 -XX:PretenureSizeThreshold=1M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFaction=92 -XX:+UserCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=0

4.6.垂直电商性能优化

-Xms4096M Xmx4096M -Xmn3072M -Xss1M -XX:PermSize=256M -XX:MaxPermSize=265M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFaction=92 -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompation=0 -XX:+CMSParallelInitialMarkEnable -XX:+CMSScavengBeforeRemark

首先8G的机器上给JVM堆分配4G,年轻代分配3G尽量让年轻代大一些,CMSInitiatingOccupancyFaction当老年代达到92%后进行回收,CMSFullGCsBeforeCompation在执行多少次Full Gc后进行内存压缩。CMSParallelInitialMarkEnable在CMS初始标记阶段开启多线程并发执行,CMSScavengBeforeRemark在CMS的重新标记阶段之前进行一次Young GC

4.7 Full GC频繁原因

内存分配不合理,导致对象频繁进入老年代,进而引发FULL GC

内存泄露等问题,就是内存里驻留了大量的对象塞满了老年代,导致稍微有一些对象进入老年代就会FULL GC

永久代中类太多,触发FULL GC

使用System.gc()

  1. JVM中的算法
    1. 可达性分析算法

可达性分析算法就是对每个对象分析一下有谁在引用他,然后一层层往上判断,看看是否有一个GC Roots,GC Roots可以理解为对象的根节点,来引出他指向的下一个节点,再以下一个节点为起点引出下一个节点。

5.2复制算法

复制算法的核心就是,将原有的内存空间一分为二,每次只用其中的一块,在垃圾回收时,将正在使用的对象复制到另外一个内存空间中,然后将该内存空间清空,交换两个内存的角色,完成垃圾回收。

如果内存中的垃圾对象较多,需要复制的对象就较少,这种情况下适合使用该方式并且效率比较高,反之,则不适合。

优点:在垃圾对象多的情况下,效率较高。清理后,内存无碎片。

缺点:在垃圾对象少的情况下,不适用,如:老年代内存。分配的2块内存空间,在同一个时刻,只能使用一般,内存使用率较低。

复制算法一般情况下分配比例是8:1   ‐XX:SurvivorRatio=8

Jvm有个参数:-XX:PretenureSizeThreshold 可以设置成

5.3老年代标记清理算法

老年代使用的算法是标记整理算法,老年代垃圾回收算法速度至少比新生代速度慢10倍。如果频繁FullGc会导致系统性能被严重影响。

所谓的JVM调优,就是尽可能的让对象都在新生代中分配和回收,尽量别让太多对象就如老年代,避免频繁对老年代进行垃圾回收,同时给系统充足的内存大小,避免新生代频繁的进行垃圾回收。

  1. 垃圾回收器

JDK1.8 使用的是Parallel Scavenge 和 Parallel Old ,G1是用在内存cpu大的服务器上

 Stop the word,在垃圾回收的时候系统会停止运行,等待垃圾回收完再启动运行。所有无论新生代GC还是老年代GC,都尽量不要让频率过高,也避免持续时间过长,避免影响系统正常运行,这也是再使用过程中最需要优化的地方。也是最大的痛点。

    1. Serial 和Serial old 新生代、老年代垃圾回收器

工作原理就是单线程运行,垃圾回收的时候会停止我们自己写的系统的工作线程,让我们的系统直接卡死不动,然后让他们垃圾回收,现在一般不用这两个。

    1. ParNew和CMS垃圾回收器

6.2.1 ParNew

ParNew现在一般都是用在新生代垃圾回收器,CMS是用在老年代垃圾回收器。他们都是多线程机制,性能更好,现在一般是线上系统标配。

以下是ParNew原理图:

使用 -XX:+UserParNewGC命令后JVM启动之后就会使用ParNew回收器了

使用 -XX:ParallelGCThreads 可以设置ParNew线程数。

6.2.2 CMS

因为Full GC比较长,长时间Stop the word 系统卡死时间过长很多,相应无法处理。所以CMS采用的方式是:垃圾回收现场和系统工作线程同时执行。

CMS垃圾回收分为4个阶段:初始标记、并发标记、重新标记、并发清理。

初始标记阶】段会让系统的工作线程全部停止运行,进入STW(stop the word)状态,并标记出GC root直接引用对象。这个阶段运行很快。

并发标记】并发标记阶段会继续对老年代中的对象进行GC Root追踪。这个阶段是最耗时、耗CPU的,因为是并发运行所以不会对系统运行造成影响。

重新标记】并发标记完成后系统又会有许多对象需要标记回收,需要再次计入STW(stop the word)状态标记新的对象,这个阶段运行很快。

并发清理】清理掉标记的对象,时间长,和系统并发运行并不影响。

问题1:并发处理阶段非常耗费CPU资源,CMS默认启动垃圾回收线程的数量是(CPU+3)- 4。如2核4G服务器消耗CPU就是 2+3-4=1

问题2:浮动垃圾(并发清理期间产生的垃圾)如何避免,保证CMS回收期间还有一定的空间让对象进入老年代。-XX:CMSInitiatingOccupancyFaction 可以设置老年代占用多少空间后进行垃圾回收,JDK1.6默认是92%,预留8%让对象能够进入老年代。

问题3:内存碎片问题,CMS标记清理完后会产生大量内存碎片,因为内存碎片产生实际上会导致更加频繁的Full GC,CMS有个参数是 -XX:UseCMSCompactAtFullCollenction 默认打开,意思是Full GC后再次STW ,对内存碎片进行整理,空出大片连续的内存空间。还有一个参数是 -XX:CMSFullGCsBeforeCompaction,这个意思是执行多少次Full GC之后再进行内存整理。默认是0。

-XX:CMSInitiatingOccupancyFaction 如果老年代可用内存大于历次新生代GC后进入老年代的对象平均大小,老年代使用的内存超过了这个参数指定比例,也会自动触发FULL GC

    1. G1

统一收集新生代和老年代,采用了更加优秀的算法和设计机制。G1垃圾回收器可以同时收回新生代和老年代的对象,一个人负责所有。

      1. 内存结构

把JAVA堆内存拆分为多个大小相等的Regin,新生代可能包含了某些Region,老年代可能包含某些Region。新生代和老年代是逻辑上的概念。

      1. 特点

G1很大的一个特点就是:可以设置垃圾回收器的预期停顿时间,G1是如何做到这一点的呢?他就必须追踪每个Region里的回收价值。他必须搞清楚每个Region里的对象有多少是垃圾,如果对这个Region进行垃圾回收,需要耗费多长时间,可以回收掉多少垃圾。

G1可以做到让你来设定垃圾回收对系统的影响,他自己通过把内存拆分为大量小Region,以及追踪每个Region中可以回收的对象大小和预估时间,最后在垃圾回收的时候尽量在你指定的时间内回收更多的垃圾。Region可能是新生代也可能是老年代。

      1. G1设置参数

-XX:InitiatingHeapOccupancyPercent 默认45%,意思是老年代占据堆内存45%的Region时,此时就会尝试触发老年代+年轻代一起回收的阶段。

-XX:MaxTenuringThreshold 设置G1默认年龄

-XX:MaxGCPauseMillis=time 制定垃圾回收停顿时间,默认是200ms

-XX:G1MixedGCCountTarget 可以设置在一次混合回收的过程中,最后一个阶段执行几次混合回收默认8次。

-XX:G1HeapWastePercent默认值是5%,意思是当混合回收的时候,一旦空闲出来的Region数量达到了堆内存的5%,此时就会立即停止混合回收。

-XX:G1MixedGCLiveThresholdPerent 默认85%,意思是确定回收Region的时候必须是存活的对象低于85%。

        1. 案例一

线上百万学生课程项目5台机器都是4核8G,每天集中在晚上3个小时的时间内,大约估算1秒600个请求,1秒大概产生3M左右的空间

-Xms4096M -Xmx4096M -Xss1M -XX:PermSize=256M -XX:MaxPermSize=256 -XX:+UseG1GC意思是JVM配置4G,每个java现成栈的内存为1MB,永久代256M。

-XX:G1NewSizePerent默认新生代初始占比为5%

-XX:G1MaxNewSizePerent默认新生代最大占比60%

-XX:MaxGCPauseMillis

      1. G1回收过程

【初始标记】这个过程需要进入STW状态,仅仅只是标记一下GC Root直接能引用的对象,这个过程速度很快

【并发标记】这个阶段允许程序运行,同时进行GC Root追踪,从GC Root 开始追踪所有存活的对象。标记耗时,和程序并发运行影响不大。

【最终标记】这个阶段会进入SWT,系统禁止运行,但会根据【并发标记】中记录的对象进行最终标记,最终标记那些是垃圾的对象,那些是存活的对象。

【混合回收】这个阶段会计算老年代中每个Region中对象存活的数量,存活对象的占比,还有执行垃圾回收的预期性能和效率。接着系统进入STW状态,然后全力以赴进行垃圾回收,此时会选择部分Region进行回收,因为必须让回收的停顿时间掌握在我们的控制范围内。老年代对堆内存占比达到45%的时候会触发【混合回收】

比如说老年代此时有1000个Region都满了,但是咱们规定的预期回收时间是200毫秒,那么通过计算得知,可能回收800个Region刚好需要200ms,那么就会回收800个Region。

[ Full GC 回收失败 ] 如果在进行Mixed回收的时候,老年代、年轻代都使用复制算法回收,把各个Region中对象拷贝到Region里去。此时万一出现在拷贝过程中发现没有空闲的Region可以承载自己存活的对象,就会发生Gc失败,一旦失败立马进入STW状态并且启动单线程回收来空出一批Region,过程极为缓慢。

    1. Parallel Scavenge和 Parallel Old
      1. Parallel Scavenge

Parallel Scavenge 收集齐是新生代收集齐,他也是使用的复制算法,又是并行的多线程收集齐,触发Parallel后STW 多线程处理垃圾。Parallel Scanveng更关注系统吞吐量。

吞吐量=运行用户代码的时间/(运行用户代码的时间+垃圾收集的时间)

-XX:MaxGCPauseMillis 控制最大垃圾收集停顿时间

-XX:GCTimeRatio 直接设置吞吐量的大小

-XX:+UserAdaptiveSziePolicy 新生代自适应调整

      1. Paralle Old

Paralle Old 收集齐是Paralle Scavenge 收集齐的老年代版本,使用多线程和标记整理算法进行垃圾回收,也是更加关注吞吐量。

    1. ZGC

ZGC收集器(Z Garbage Collector)由Oracle公司研发.2018年提交了JEP 333将ZGC提交给了OpenJDK,推动进入OpenJDK11的发布清单中。ZGC收集器是基于Region内存布局,暂时不设分代,使用读屏障,着色指针和内存多重映射等技术来实现并发的标记整理算法,以低延迟为目标的一款收集器。

  • 与G1一样,ZGC也采用基于Region的堆内存布局
  • ZGC的Region具有动态性
  • 动态的创建和销毁
  • 动态的Region容量大小

大小分类:

小型Region(Small Region),固定大小2MB,存放小于256KB的小对象

中型Region(Medium Region),固定大小32MB,存放大于256KB小于4MB的对象

大型Region(Large Region),大小不固定,可以动态变化,但必须是2MB的整数倍,用于放大于4MB的大对象,每个大型Region只会放一个大对象,所以实际容量可能会小于中型Region,最小到4MB。大型Region在ZGC实现中不会被重分配,因为复制一个大对象代价太高。

  1. JVM性能调优到底在优化什么?

系统因为内存分配、参数设置不合理导致你的对象频繁的进入老年代,然后频繁触发老年代GC,导致系统频繁卡顿甚至卡死。

案例一:百万级商家系统,

优化方式是更换大运存16核32G,使用G1

  1. 线上jvmGC情况查看工具
    1. jstat工具

Linux查看jar包运行PID :ps -ef | grep xxx.jar

Linux查看jar包gc情况:jstat -gc PID 1000 30 意思是每个1秒打印GC情况,打印30次。

S0C:这个是Form Survivor区的大小

S1C:这个是To Survivor区的大小

S0U:这个是Form Survivor区当前使用的内存大小

S1U:这个是To Survivor区当前使用的内存大小

EC:这是Eden区内存大小

EU:这个是Eden区当前使用的内存大小

OC:这是老年代的大小

OU:这是老年代当前使用的内存大小

MC:这是方法区(永久代、元数据区)的内存大小

MU:这是方法区(永久代、元数据区)当前使用的内存大小

YGC:这是系统运行迄今为止Young GC的次数

YGCT:Young GC的耗时

FGC:系统运行迄今为止FULL GC的此时

FGCT:FULL GC 耗时

FCT:GC总耗时

linux查看占用内存最多的程序

ps aux|head -1;ps aux|grep -v PID|sort -rn -k +4|head

查看占用cpu最多的程序

ps aux|head -1;ps aux|grep -v PID|sort -rn -k +3|head

    1. jmap和jhat摸清线上系统对象分部

Jmap -histo PID查看线程内存使用情况

    1. JAVA VisualVM

Window

    1. 阿里Arthas???

8.5MAT???

    1. Zabbix,open-Falcon???
    2. Eclipse Memory Analyzer????  89
  1. 垃圾回收日志
  2. jvm优化到底怎么做?

系统因为内存分配、参数设置不合理导致你的对象频繁的进入老年代,然后频繁触发老年代GC,导致系统频繁卡顿甚至卡死。

10.1优化原则

尽可能大让每次YoungGC后存活对象远远小于Survivor区域,避免对象频繁进入老年代触发Full GC,最理想的状态是系统几乎不发生FULL GC老年代应该是稳定占用一定的空间,就是那些长期存活的躲过15次young Gc后升入老年带自然占用,然后就是几分钟发生young GC ,耗时几毫秒。

10.2 在压测之后合理调整线上参数

任何一个系统上线都要进行压测,此时在模拟线上压力的场景小,可以用jstat等工具进行JVM的运行内存模型,

Eden区的对象增长速率多块?

YoungGc 频率多高?

一次YoungGC多长耗时?

老年代增长速率多高?

FullGc频率多高?

一次FullGc的耗时?

尽可能的让系统仅有YoungGc。

10.3 线上系统监控优化

系统上线务必进行一定的监控,最高大上的做法就是Zabbix Open-Falcon之类的系统监控工具,频繁Full Gc 就要报警。

比较差的就是在机器上运行jstat,让其他监控写入一个文件,每天定时检查一下。

10.3.1 频繁Full Gc的几种常见原因

一、系统承载高并发请求,或者处理数量过大,导致Young GC很频繁,而每次YOUNG GC后存活对象太多,内存分配不合理,Survivor区域过小,导致对象频繁进入老年代,频繁FULL GC.

二、系统一次性加载过多数据进内存,搞出许多大对象出来,导致频繁有大对象进入老年代,必然频繁FULL gc

三、Metaspace(永久代)因为加载类过多触发FULL GC

四、误调用System.gc()触发FULL GC

常见的频繁FULL GC原因就是以上几种,从这几个角度分析即可,核心利器就是jstat。

Jstat分析发现FULL GC 原因是第一种,那么就合理分配内存,调大Survivor内存即可;如果jstat分析发现FULL GC 原因是第二种或者第三种,也就是老年代有大量对象无法回收掉,年轻代升入老年代的对象不多,那么就DUMP出来内存快照,然后MAT工具进行分析即可。

10.3.2 一个统一的JVM参数模板

为了简化JVM的参数设置和优化,建议各个公司和团队leader做一份JVM参数模板出来,设置一些常见参数即可。核心就是一些内存区域的分配,垃圾回收器的指定,CMS性能优化的一些参数,(比如压缩、并发、等等),包括禁止System.gc()打印GC日志等等。

10.3.3 JAVA程序员平时最常遇到的故障:系统OOM

内存溢出(oom)就是JVM内存就那么大,结果你拼命的塞东西,结果内存塞不下了,导致内存溢出。

10.3.3.1.Metaspace 区域是如何触发内存溢出的?

以下两个参数用来设置Metaspace的:

-XX:MetaspaceSize=512m

-XX:MaxMetaspaceSize=512m

Metaspace区域满了此时就会触发Full GC 连带这回收Metaspace里的类,那么什么样的类才会被回收呢?这个条件和苛刻包括不限于一下这些。比如类的加载器要先被回收;比如这个类的所有对象实例都要回收,所以Metaspace区域满了未必被回收。

Metaspace一般很少发生内存溢出,如果发生一般有连个原因:一、很多工程师没有给Metaspace设置大小,使用默认参数导致Metaspace区域才几十MB,对于大一点的系统因为他自己有很多类,又依赖很多JAR包,几十MB很容易满。二、很多人写系统会用cglib之类的技术动态生成的一些类,一旦代码没控制好导致类过多容易引发Metaspace内存溢出。

第一种情况一般设置512m就够。

10.3.3.2 线程栈内存溢出问题

什么情况下导致内存溢出?既然一个线程虚拟机栈的内存大小是有限的,比如1MB,假设你不停的让线程去调用各种方法,然后不停的把方法调用的栈帧压如栈中,就会占用这个线程栈的内存。大于这个内存就会导致内存溢出。

什么情况下回导致内存溢出呢?一般情况下是不会内存溢出的,如果走递归方法就不一定了。

10.3.3.3 堆内存溢出

发生堆内存溢出就一句话:有限的内存中放了过多的对象,而且大多数都是存活的,所以放入更多的对象是不可能了,因此引发内存溢出。

情况有两种:一、系统承载高并发,因为请求量过大导致大量对象都是存活的,所以继续放入心得对象就不行了;二、系统内存泄露问题,就是莫名奇妙弄了很多对象,结果对象都存活下来了,没有及时取消对他们的引用,导致触发GC.

10.3.3.4 .OOM内存快照

系统在OOM是设置两个参数就可以保存快照:

-XX:+HeapDumpOnOutOfMemoryError

-XX:HeapDumpPath=/usr/local/app/oom

第一个意思是OOM的时候自动dump内存快照出来,第二个意识是说那内存快照发到哪里去。

  1. JVM参数模板

《O基础开始带你成为JVM高手》中给出的jvm参数模板:

-Xms4096M -Xmx4096 -Xmn3072M -Xss1M -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSParallelInitialMarkEnabled -XX:+CMSScavengeBeforeRemark -XX:+DisableExplicitGC

-XX:+PrintGCDetails -Xloggc:gc.log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/loacl/app/oom

   

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

jvm调优 的相关文章

随机推荐