verysu 设计模式 设计模式 响应式编程 百度开发平台 codeforces leetcode usfca

导航

jvm内存调优

FROM 87b1696e3cb5
WORKDIR /app
COPY sb-system-gateway-1.4.0.jar /app
EXPOSE 8080
EXPOSE 9090
ENTRYPOINT ["java", "-server", "-XX:+PrintGCDetails", "-Xms800m","-Xmx1500m","-Xmn1500m","-Xss500K","-XX:ParallelGCThreads=20","-XX:+UseConcMarkSweepGC","-XX:+UseCMSCompactAtFullCollection","-XX:MaxTenuringThreshold=8","-XX:CompressedClassSpaceSize=100m","-XX:InitialBootClassLoaderMetaspaceSize=100m","-XX:+UseParNewGC","-XX:SurvivorRatio=4", "-XX:NewSize=500m","-XX:NewSize=700m", "-XX:MetaspaceSize=200M","-XX:MaxMetaspaceSize=350m","-XX:+HeapDumpBeforeFullGC","-XX:+HeapDumpAfterFullGC", "-Dcom.sun.management.jmxremote.port=9090", "-Dcom.sun.management.jmxremote.ssl=false", "-Dcom.sun.management.jmxremote.authenticate=false", "-jar", "sb-system-gateway-1.4.0.jar", "--server.port=8080"]
查看使用的什么类型垃圾回收器
java -XX:+PrintFlagsFinal -version | grep :或者java -XX:+PrintCommandLineFlags -version
配置相关服务启动参数
-Xms1000m -Xmx1000m -XX:MaxNewSize=256m -XX:ThreadStackSize=256
-XX:MetaspaceSize=38m -XX:MaxMetaspaceSize=380m
调试远程连接端口-Dcom.sun.management.jmxremote.port=9090
java线上服务超时的原因
根据jdk8的metaspace的fullgc的触发条件,初始metaspacesize是38m意味着当第一次加载的class达到38m的时候进行第一次gc(根据JDK 8的特性,G1和CMS都会收集Metaspace区(一般都伴随着Full GC)。),然后jvm会动态调整(gc后会进行调整)metaspacesize的大小。下面几个参数可以对Metaspace进行控制:

-XX:MetaspaceSize=N
这个参数是初始化的Metaspace大小,该值越大触发Metaspace GC的时机就越晚。随着GC的到来,虚拟机会根据实际情况调控Metaspace的大小,可能增加上线也可能降低。
-XX:MaxMetaspaceSize=N
这个参数用于限制Metaspace增长的上限,防止因为某些情况导致Metaspace无限的使用本地内存,影响到其他程序。
-XX:MinMetaspaceFreeRatio=N
当进行过Metaspace GC之后,会计算当前Metaspace的空闲空间比,如果空闲比小于这个参数,那么虚拟机将增长Metaspace的大小。设置该参数可以控制Metaspace的增长的速度,太小的值会导致Metaspace增长的缓慢,Metaspace的使用逐渐趋于饱和,可能会影响之后类的加载。而太大的值会导致Metaspace增长的过快,浪费内存。
-XX:MaxMetasaceFreeRatio=N
当进行过Metaspace GC之后, 会计算当前Metaspace的空闲空间比,如果空闲比大于这个参数,那么虚拟机会释放Metaspace的部分空间。在本机该参数的默认值为70,也就是70%。
-XX:MaxMetaspaceExpansion=N
Metaspace增长时的最大幅度。
-XX:MinMetaspaceExpansion=N
Metaspace增长时的最小幅度。
-XX:+PrintFlagsFinal
可以命令行执行java -XX:+PrintFlagsFinal -version and -XX:PrintFlagsInitial打印所有的JVM参数
-XX:MaxTenuringThreshold=1复制到老年的的年龄阈值
-XX:TargetSurvivorRatio=80 Survivor区对象使用率80%,默认是50%
-XX:-UseAdaptiveSizePolicy 禁用Survivor区自适应策略
-Xnoclassgc关闭虚拟机对class的垃圾回收功能
-XX:MaxGCPauseMillis=100设置每次年轻代垃圾回收的最长时间,如果无法满足此时间,JVM会自动调整年轻代大小,以满足此值
-XX:+PrintGCDetails
-XX:+PrintTenuringDistribution每次新生代GC时,打印出幸存区中对象的年龄分布
-XX:InitialTenuringThreshold 和 -XX:MaxTenuringThreshold 可以设定老年代阀值的初始值和最大值
-XX:CMSInitiatingOccupancyFraction=80 设定CMS在对内存占用率达到70%的时候开始GC(因为CMS会有浮动垃圾,所以一般都较早启动GC)
-XX:UseCMSInitiatingOccupancyOnly用设定的回收阈值(上面指定的70%),如果不指定,JVM仅在第一次使用设定值,后续则自动调整,与上一个参数配置使用
-XX:CMSInitiatingPermOccupancyFraction=70当UseCMSInitiatingOccupancyOnly参数该标志被开启时,JVM通过CMSInitiatingOccupancyFraction的值进行每一次CMS收集
-XX:+UseParallelGC
-XX:+UseParallelOldGC
-XX:ParallelGCThreads=24并行GC线程的数量
-XX:CICompilerCount=threads编译器线程数(将字节码编译成机器码成为热点代码,当一段代码执行达到一定阈值就会编译成机器码。大部分热点代码被编译后就不在长期占用cpu了)
-XX:ConcGCThreads=24设置并行标记的线程数。将 n 设置为并行垃圾回收线程数 (ParallelGCThreads) 的 1/4 左右。
-XX:+CMSParallelRemarkEnabled开启并行的Remark,加快remark的速度
-XX:+CMSScavengeBeforeRemark重新标记Remark之前,强制对新生代执行一次MinorGC
-XX:+CMSPermGenSweepingEnabled这个参数表示是否会清理持久代。默认是不清理的,因此我们需要明确设置这个参数来调试持久代内存溢出问题。这个参数在Java6中被移除了,因此你需要使用XX:+CMSClassUnloadingEnabled
-XX:SoftRefLRUPolicyMSPerMB=0代表每一MB空闲内存空间可以允许SoftReference对象存活多久
-XX:-ReduceInitialCardMarks//配置让cms能够有回收堆外内存能力
-XX:+ExplicitGCInvokesConcurrent调整Full gc的时间间隔进而减少Full gc次数添加ExplicitGCInvokesConcurrent选项后,整个Full gc的停机时间(mark+remark)只有0.22秒,几乎感觉不到
-XX:+UseTLAB启用线程本地缓存区
-XX: DisableExplicitGC禁用system.gc触发fullgc
-XX:TLABSize=64K, -verbose:gc (Thread Local Allocation Buffer)
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-XX:+PrintGCTimeStamps
-XX:+PrintHeapAtGC
-XX:+PrintGCApplicationConcurrentTime
-XX:+PrintGCApplicationStoppedTime
-XX:-HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/data/applogs/HeapDumpOnOutOfMemoryError
-Xloggc:/data/applogs/heap_trace.txt
-XX:+UseCMSCompactAtFullCollection对老年代进行压缩整理,处理掉内存碎片
-XX:CMSFullGCsBeforeCompaction=1配置进行了多少次Full GC之后执行一次内存压缩
-XX:ParallelGCThreadsCMS收集器配置并行垃圾回收线程数
-Xloggc:./gc.log
Minor GC 是清理年轻代。 Major GC 是清理永久代
XX:+UseSerialGC
使用串行回收器进行回收,这个参数会使新生代和老年代都使用串行回收器,新生代使用复制算法,老年代使用标记-整理算法
-XX:+UseParNewGC

1、UseParNewGC:使用这个参数后会在新生代进行并行回收,老年代仍旧使用串行回收。新生代S区任然使用复制算法.并发串行收集器,它是工作在新生代的垃圾收集器,它只是将串行收集器多线程化,除了这个并没有太多创新之处,而且它们共用了相当多的代码。它与串行收集器一样,也是独占式收集器,在收集过程中,应用程序会全部暂停。但它却是许多运行在Server模式下的虚拟机中首选的新生代收集器,其中有一个与性能无关但很重要的原因是,除了Serial收集器外,目前只有它能与CMS收集器配合工作。
2、UseParallelGC:代表新生代使用Parallel收集器,老年代使用串行收集器.并行收集器,同时运行在多个cpu之间,物理上的并行收集器,跟上面的收集器一样也是独占式的,但是它最大化的提高程序吞吐量,同时缩短程序停顿时间,另外它不能与CMS收集器配合工作。Parallel Scavenge收集器在各个方面都很类似ParNew收集器,它的目的是达到一个可以控制的吞吐量-XX:MaxGCPauseMillis参数用于设置最大停顿时间毫秒数。-XX:GCTimeRatio参数用户控制垃圾回收时间占比它运行的参数值是0-100的整数,如果参数设置为19,代表最大GC时间占总时间的5%,自适应的调节策略-XX:UseAdaptiveSizePolicy根据实际运行情况动态调整新生代大小、新生代和s区比例、晋升老年代对象大小等细节参数
-XX:+UseParallelOldGC新生代和老年代都使用并行收集器。打印出的GC会带PSYoungGen、ParOldGen
CMS为基于标记清除算法实现的多线程老年代垃圾回收器。CMS为响应时间优先的垃圾回收器,适合于应用服务器,如网络游戏,电商等和电信领域的应用。
为了实现这个目的,与其他垃圾回收器不同的是,CMS是与应用程序并发执行的,即在CMS对老年代进行垃圾回收时,应用程序大部分时间里是可以继续执行的,应用程序只需进行非常短暂的停顿。由于与应用程序并发执行,同一时刻同时存在垃圾回收线程和应用线程,故对服务器的CPU消耗较大,需要保证服务器CPU资源充足。
执行过程
初始标记:应用程序需要停顿,主要是收集GC roots直接引用的对象并标记这些对象为存活对象,这些对象可能在新生代或者老年代,即需要对整个堆进行扫描,不过由于直接引用的对象较少,故不需要消耗很长的时间;
并发标记:与应用程序并发执行,从初始标记阶段所收集到的GC roots直接引用的对象出发,继续扫描查找和标记可达的对象,这个过程涉及到整个堆,即包括新生代和老年代,故需要耗费较长时间,所以与应用程序并发执行,不会对应用程序造成干扰;
并发预清理:并发预清理主要是将新生代中在并发标记阶段中晋升到老年代的对象进行清理,减少新生代中对象的数量,减少下一阶段重新标记需要扫描的对象数量。并发预清理阶段默认执行时间不能超过5s,否则直接进入重新标记阶段,默认值5s可以通过JVM参数:-XX:CMSMaxAbortablePrecleanTime 来调整。除此之外,还可以通过JVM参数:-XX:+CMSScavengeBeforeRemark 来开启在进行重新标记Remark之前,强制对新生代执行一次MinorGC,从而减少重新标记阶段需要扫描的对象数量,减少Remark的执行时间;
重新标记:应用程序需要停顿,由于并发标记阶段,应用程序在并发执行,故可能会产生新的垃圾对象,或者原来的垃圾对象重新变为可达,故在该阶段暂停应用,然后对在并发标记阶段中状态发生了变化的对象进行重新标记,此过程涉及到整个堆中,即从GC roots引用的对象出发,对新生代和老年代中发生了变化的对象进行重新标记。具体remark阶段的执行时间,可以通过GC日志的CMS-remark日志内存来查看。
并发清除:将以上过程没有被标记为可达的对象进行清除,此阶段是与应用程序并发执行的;
并发重置:对该次CMS垃圾回收中的数据结构进行重置,以便下次进行CMS
3、-XX:+UseConcMarkSweepGC Concurrent Mark Sweep 并发标记清除
-XX:ParallelCMSThreads并行GC时进行内存回收的线程数量
-XX:PreternureSizeThreshold直接晋升老年代的对象大小
-XX:MaxTenuringThreshold对象在每一次Minor GC后年龄增加一岁,超过这个值后进入到老年代
-XX:NativeMemoryTracking=detail配置后可以jcmd pid VM.native_memory detail查看jvm相关参数
XX:+UseConcMarkSweepGC -jar /jar包路径 指定路径使用CMS回收
具体场景分析
gc日志分析:
第一次fullgc:
[Heap Dump (before full gc): , 0.4032181 secs]2018-01-10T16:37:44.658+0800: 21.673:
[Full GC (Metadata GC Threshold) [PSYoungGen: 14337K->0K(235520K)]
[ParOldGen: 18787K->30930K(761856K)] 33125K->30930K(997376K),
[Metaspace: 37827K->37827K(1083392K)], 0.1360661 secs]
[Times: user=0.65 sys=0.04, real=0.14 secs]
 主要是Metaspace这里:[Metaspace: 37827K->37827K(1083392K)]达到了设定的初始值38m,并且gc并没有回收掉内存。1083392K这个值是使用了CompressedClassSpaceSize = 1073741824 (1024.0MB)这个导致的。
  从gc日志可以看出来(37827K->37827K),发生full gc时metaspace并没有达到阈值,没有达到阈值为什么还触发了full gc呢?这是因为metaspace committed内存达到阈值,说明metaspace有内存碎片。

第四次fullgc:
[Heap Dump (before full gc): , 5.3642805 secs]2018-01-10T16:53:43.811+0800: 980.825:
[Full GC (Metadata GC Threshold) [PSYoungGen: 21613K->0K(231424K)]
[ParOldGen: 390439K->400478K(761856K)] 412053K->400478K(993280K),
[Metaspace: 314108K->313262K(1458176K)], 1.2320834 secs]
[Times: user=7.86 sys=0.06, real=1.23 secs]
 主要是Metaspace这里:[Metaspace: 314108K->313262K(1458176K)]达到了设定的MinMetaspaceFreeRatio,并且gc几乎没有回收掉内存。1458176K这个值是CompressedClassSpaceSize = 1073741824 (1024.0MB)和 MaxMetaspaceSize = 503316480 (480.0MB)的和。后面就是频率很快的重复fullgc。

导致Full GC的罪魁祸首
  对于metaspace内存碎片化,有一个场景倒是可以满足,那就是创建了大量的classloader。目前就一次出现full gc时间点的heap dump不太能看出来问题,我通过增加jvm参数-XX:+HeapDumpBeforeFullGC、-XX:+HeapDumpAfterFullGC分别在发生full gc前后做heap dump。通过对比分析full gc发生前后的heap dump,发现在full gc前创建了大量的sun.reflect.DelegatingClassLoader,full gc后该classloader也减少了约1000个。
  到这里,导致该问题的罪魁祸首就找到了,那就是sun.reflect.DelegatingClassLoader,但是为什么类加载器过多就会导致内存碎片化呢?在类加载器第一次加载类的时候,都会在metaspace区域为其分配一块内存,并且每个类加载器的内存区域都是独立的,当然咯,一定要走出这个误区,类加载器的内存分配跟加载类的数量是没有关系的,即使类加载器只加载一个类,也是会在metaspace为其分配一块内存的。当出现频繁的类加载器创建的时候,这个时候就可能会出现metaspace内存使用率低,但是committed的内存已经达到了full gc的阈值从而触发了full gc。

总结下原因:classloader不断创建,classloader不断加载class,之前的classloader和class在fullgc的时候没有回收掉。

程序避免创建重复classloader,减少创建classLoader的数量。
增大XX:MinMetaspaceFreeRatio(默认40)的大小,可以看到现在是(100-67.19)。
设置更大的maxmetaspaceSize。
-XX:ReservedCodeCacheSize=64m对象大小大于64m直接进入老年代
-XX:MaxTenuringThreshold,设置将新生代对象转到老年代时需要经过多少次垃圾回收
-------垃圾回收两条规则
1、在YGC执行前,min(目前新生代已使用的大小,之前平均晋升到old的大小中的较小值) > 旧生代剩余空间大小 ? 不执行YGC,直接执行Full GC : 执行YGC;
2、在YGC执行后,平均晋升到old的大小 > 旧生代剩余空间大小 ? 触发Full GC : 什么都不做。
-XX:-ScavengeBeforeFullGC 也还是会先Young GC (Full GC (Allocation Failure)之前)。
-XX:PretenureSizeThreshold启动参数控制,若对象大小大于此值,就会绕过新生代, 直接在老年代中分配
3空间分配担保:在YGC之前,会先检查老年代最大可用的连续空间是否大于新生代所有对象的总空间。如果小于,说明YGC是不安全的,则会查看参数 HandlePromotionFailure 是否被设置成了允许担保失败,如果不允许则直接触发Full GC;如果允许,那么会进一步检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果小于也会触发 Full GC
4Metaspace(元空间)在空间不足时会进行扩容,当扩容到了-XX:MetaspaceSize 参数的指定值时,也会触发FGC
5System.gc() 或者Runtime.gc() 被显式调用时,触发FGC
-XX:MinHeapFreeRatio=40
意义:GC后,如果发现空闲堆内存占到整个预估上限值的40%,则增大上限值。
默认值:40 。
-XX:MaxHeapFreeRatio=70
意义:GC后,如果发现空闲堆内存占到整个预估上限值的70%,则收缩预估上限值。
默认值:70。
-XX:NativeMemoryTracking=detail查看具体内存占用情况(添加配置参数后用jcmd pid VM.native_memory detail查看)
-XX:MaxDirectMemorySize=100M ByteBuffer有两种heap ByteBuffer和direct ByteBuffer
direct ByteBuffer是通过jni在虚拟机外内存中分配的。通过jmap无法查看该快内存。此参数的含义是当Direct ByteBuffer分配的堆外内存到达指定大小后,即触发Full GC

中间件调优
设置文件临时存储路径-Djava.io.tmpdir
设置tomcat文件临时目录防止被删除文件读取异常server.tomcat.basedir:/stsvc/fms/temp
排队数(程序线程都在工作时,允许最大连接数)server.tomcat.accept-count=50
最大连接数server.tomcat.max-connections=2000
线程池最大线程数server.tomcat.max-threads=200
最小线程数server.tomcat.min-spare-threads超时时间
编码方式 server.tomcat.uri-encoding=UTF-8
post提交数据最大大小,设置为0不限制 server.tomcat.max-http-post-size=0

内存溢出的几种情况
第一类内存溢出,也是大家认为最多,第一反应认为是的内存溢出,就是堆栈溢出:
那什么样的情况就是堆栈溢出呢?当你看到下面的关键字的时候它就是堆栈溢出了:
java.lang.OutOfMemoryError: ......java heap space.....
也就是当你看到heap相关的时候就肯定是堆栈溢出了,此时如果代码没有问题的情况下,适当调整-Xmx和-Xms是可以避免的,不过一定是代码没有问题的前提,为什么会溢出呢,要么代码有问题,要么访问量太多并且每个访问的时间太长或者数据太多,导致数据释放不掉,因为垃圾回收器是要找到那些是垃圾才能回收,这里它不会认为这些东西是垃圾,自然不会去回收了;主意这个溢出之前,可能系统会提前先报错关键字为:
java.lang.OutOfMemoryError:GC over head limit exceeded
这种情况是当系统处于高频的GC状态,而且回收的效果依然不佳的情况,就会开始报这个错误,这种情况一般是产生了很多不可以被释放的对象,有可能是引用使用不当导致,或申请大对象导致,但是java heap space的内存溢出有可能提前不会报这个错误,也就是可能内存就直接不够导致,而不是高频GC.
第二类内存溢出,PermGen的溢出,或者PermGen 满了的提示,你会看到这样的关键字:
关键信息为:
java.lang.OutOfMemoryError: PermGen space
原因:系统的代码非常多或引用的第三方包非常多、或代码中使用了大量的常量、或通过intern注入常量、或者通过动态代码加载等方法,导致常量池的膨胀,虽然JDK 1.5以后可以通过设置对永久带进行回收,但是我们希望的是这个地方是不做GC的,它够用就行,所以一般情况下今年少做类似的操作,所以在面对这种情况常用的手段是:增加-XX:PermSize和-XX:MaxPermSize的大小。

第三类内存溢出:在使用ByteBuffer中的allocateDirect()的时候会用到,很多javaNIO的框架中被封装为其他的方法
溢出关键字:
java.lang.OutOfMemoryError: Direct buffer memory
如果你在直接或间接使用了ByteBuffer中的allocateDirect方法的时候,而不做clear的时候就会出现类似的问题,常规的引用程序IO输出存在一个内核态与用户态的转换过程,也就是对应直接内存与非直接内存,如果常规的应用程序你要将一个文件的内容输出到客户端需要通过OS的直接内存转换拷贝到程序的非直接内存(也就是heap中),然后再输出到直接内存由操作系统发送出去,而直接内存就是由OS和应用程序共同管理的,而非直接内存可以直接由应用程序自己控制的内存,jvm垃圾回收不会回收掉直接内存这部分的内存,所以要注意了哦。
如果经常有类似的操作,可以考虑设置参数:-XX:MaxDirectMemorySize
第四类内存溢出错误:
溢出关键字:
java.lang.StackOverflowError
这个参数直接说明一个内容,就是-Xss太小了,我们申请很多局部调用的栈针等内容是存放在用户当前所持有的线程中的,线程在jdk 1.4以前默认是256K,1.5以后是1M,如果报这个错,只能说明-Xss设置得太小,当然有些厂商的JVM不是这个参数,本文仅仅针对Hotspot VM而已;不过在有必要的情况下可以对系统做一些优化,使得-Xss的值是可用的。
第五类内存溢出错误:
溢出关键字:
java.lang.OutOfMemoryError: unable to create new native thread
上面第四种溢出错误,已经说明了线程的内存空间,其实线程基本只占用heap以外的内存区域,也就是这个错误说明除了heap以外的区域,无法为线程分配一块内存区域了,这个要么是内存本身就不够,要么heap的空间设置得太大了,导致了剩余的内存已经不多了,而由于线程本身要占用内存,所以就不够用了,说明了原因,如何去修改,不用我多说,你懂的。
第六类内存溢出:
溢出关键字
java.lang.OutOfMemoryError: request {} byte for {}out of swap
这类错误一般是由于地址空间不够而导致。

-XX:+CreateMinidumpOnCrash
-XX:MaxDirectMemorySize

["-Ddubbo.protocol.port=7776",
"-Dserver.port=8080",
"-DseataEnv=dev",
"-Dapollo.meta=http://it-apollo-dev.xyz/",
"-Denv=dev","-Dcsp.sentinel.dashboard.server=192.168.10.12:8088",
"-Dcsp.sentinel.api.port=8819",
"-Dcsp.sentinel.log.dir=/data01/logs/csp/",
"-Dproject.name=app-dev",
"-Xmx8000M",
"-Xms8000M",
"-XX:MaxMetaspaceSize=1024M",
"-XX:MetaspaceSize=1024M",
"-XX:+UseG1GC",
"-XX:MaxGCPauseMillis=100",
"-Dserver.tomcat.max-threads=500",
"-Dnacos.server=172.16.1.122:8848",
"-DserverAddr=it-seata-nacos.test.abc",
"-Dnamespace=2503333-7c4b-4af3-8fa1-c4915ac43532s",
"-DencryptionKey=23asdwee2343afwe",
"-Duser.home=/data01/app/logs",
"-javaagent:/home/webapps/ArmsAgent/arms-bootstrap-1.7.0-SNAPSHOT.jar",
"-Darms.licenseKey=2222@2222","-Darms.appName=app_dev-web"]

posted on 2021-02-09 13:02  泳之  阅读(285)  评论(0)    收藏  举报

我是谁? 回答错误