`
nicegege
  • 浏览: 589337 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

JAVA内存调小

 
阅读更多

1.背景

当前,后台架构方案流行微服务架构。把一个大系统拆成多个服务,服务之间通过rpc或http restful方式相互通信。微服务拆分方式有业务模块或功能。我司采用业务模块。大概公共服务,微服务,api,注册发现配置等。

预发布环境模拟商用,搭建所有的微服务。预发布用于测试或压测等。预发布的硬件条件没有商用好或配置低。预发布在大部分情况下,空闲或使用量小。所以配置低,尽量不多花钱。

2.目的

为了减少阿里云资源费用,提高服务器利用率,在一台2核心8G内存的ECS服务器上,同时运行6个微服务应用。

3.环境

jdk8、centos7、spring clound微服务架构、单一部署。

/bin/java -server -Xms1G -Xmx1G -Xss512k -XX:MetaspaceSize=60m -XX:MaxMetaspaceSize=128m 
-classpath /opt/app/micro-acs/conf:/opt/app/micro-acs/boot/*:/opt/app/micro-acs/lib/*: 
-Dbasedir=/opt/app/micro-acs -Dfile.encoding=UTF-8 -Djava.awt.headless=true 
-Dsun.net.client.defaultConnectTimeout=60000 -Dsun.net.client.defaultReadTimeout=60000 
-Djmagick.systemclassloader=no -Dnetworkaddress.cache.ttl=300 -Dsun.net.inetaddr.ttl=300 
-XX:+UseG1GC -XX:+DisableExplicitGC -XX:+HeapDumpOnOutOfMemoryError 
-XX:-OmitStackTraceInFastThrow -XX:HeapDumpPath=/data/logs/ 
-XX:ErrorFile=/data/logs/micro-acs/javadump_error_%p.log -Dons.client.logRoot=/data/ons 
-Dons.client.logLevel=INFO -Dons.client.logFileMaxIndex=10 com.zhht.BootstrapApplication

 

命令:jstat -gccapacity 14114

 

命令结果:
NGCMN    NGCMX     NGC         S0C  S1C     EC      OGCMN OGCMX     OGC      OC       MCMN  MCMX       MC        CCSMN  CCSMX      CCSC      YGC    FGC 
0.0    1048576.0   660480.0    0.0 10240.0 650240.0  0.0  1048576.0 388096.0 388096.0 0.0   1138688.0  102016.0  0.0    1048576.0  12416.0   3286     0

 

NGCMN 新生代最小值(KB)
NGCMX 新生代最大值(KB)
NGC   当前新生代大小(KB)

S0C  Survivor0(幸存区0)大小(KB)
S1C  Survivor1(幸存区1)1大小(KB)
EC   Eden(伊甸区)大小(KB)

OGCMN  老年代最小值(KB)
OGCMX  老年代最大值(KB)
OGC    当前老年代大小(KB)
OC     老年代大小(KB)
OU     老年代已使用大小(KB)

MCMN   最小元数据容量(KB)
MCMX   最大元数据容量(KB)
MC     当前元数据空间大小(KB)

CCSMN  最小压缩类空间大小(KB)
CCSMX  最大压缩类空间大小(KB)
CCSC   当前压缩类空间大小(KB)

YGC  新生代GC个数
FGC  Full GC次数	

 

当前使用空间: CCSC + MC + OC + EC+ +S1C =  12416 + 102016 + 388096 + 650240 + 10240 = 1135.7500M

 

总结:
CCSC和MC分别代表当前压缩类空间大小、当前元数据空间大小 加在一起125M左右,对应-XX:MaxMetaspaceSize=128m
这个使用合理,没有多余空间。OC、EC、S1C代表年老代、伊甸、S1区。其中伊甸、S1区加一起等于年轻代。
年老代、年轻代分别是388M、660M左右。堆空间设置-Xmx1G。
再看YGC、FGC参数,分别代表年轻代回收和年老代回收次数。我们发现年老代回收次数为0。

我认为年老代回收频率低,压缩内存的方式提升回收。或者年轻代的回收频率提升。
root      9053     1  1 May22 ?        02:54:53 /bin/java -server -Xms400m -Xmx400m -Xss512k -XX:MetaspaceSize=60m -XX:MaxMetaspaceSize=128m -classpath /opt/app/micro-acs/conf:/opt/app/micro-acs/boot/*:/opt/app/micro-acs/lib/*: -Dbasedir=/opt/app/micro-acs -Dfile.encoding=UTF-8 -Djava.awt.headless=true -Dsun.net.client.defaultConnectTimeout=60000 -Dsun.net.client.defaultReadTimeout=60000 -Djmagick.systemclassloader=no -Dnetworkaddress.cache.ttl=300 -Dsun.net.inetaddr.ttl=300 -XX:+UseG1GC -XX:+DisableExplicitGC -XX:+HeapDumpOnOutOfMemoryError -XX:-OmitStackTraceInFastThrow -XX:HeapDumpPath=/data/logs/ -XX:ErrorFile=/data/logs/micro-acs/javadump_error_%p.log -Dons.client.logRoot=/data/ons -Dons.client.logLevel=INFO -Dons.client.logFileMaxIndex=10 com.zhht.BootstrapApplication
root     11671 11645  0 18:23 pts/0    00:00:00 grep --color=auto java
 
总结:内存设置400后,单台服务器上运行了6个应用,没有内存溢出等情况。关键是使用率低。
 
[root@notice_ips_shedulejob_busiconf01 ~]# jstat -gccapacity 9053
 NGCMN    NGCMX     NGC     S0C   S1C       EC      OGCMN      OGCMX       OGC         OC       MCMN     MCMX      MC     CCSMN    CCSMX     CCSC    YGC    FGC 
     0.0 409600.0 229376.0    0.0 7168.0 222208.0        0.0   409600.0   180224.0   180224.0      0.0 1136640.0  98252.0      0.0 1048576.0  11980.0   2756     0

 

 

 

 

G1 GC技术解析
介绍
    G1 GC,全称Garbage-First Garbage Collector,通过-XX:+UseG1GC参数来启用。G1收集器是工作在堆内不同分区上的收集器,分区既可以是年轻代也可以是老年代,同一个代的分区不需要连续。并且每个代分区的数量是可以动态调整的。为老年代设置分区的目的是老年代里有的分区垃圾多,有的分区垃圾少,这样在回收的时候可以专注于收集垃圾多的分区,这也是G1名称的由来。不过这个算法并不适合新生代垃圾收集,因为新生代的垃圾收集算法是复制算法,但是新生代也使用了分区机制主要是因为便于代大小的调整。
    G1 GC是设计用来取代CMS的,同CMS相比G1有以下优势:
1、可预测的停顿模型
2、避免了CMS的垃圾碎片
3、超大堆的表现更出色

G1关键概念
Region
    G1里面的Region的概念不同于传统的垃圾回收算法中的分区的概念。G1默认把堆内存分为1024个分区,后续垃圾收集的单位都是以Region为单位的。Region是实现G1算法的基础,每个Region的大小相等,通过-XX:G1HeapRegionSize参数可以设置Region的大小。如下图所示:

图中的E代表是Eden区,S代表Survivor,O代表Old区,H代表humongous表示巨型对象(大小大小Region空间一半的对象)。从图中可以看出各个区域逻辑上并不是连续的。并且一个Region在某一个时刻是Eden,在另一个时刻就可能属于老年代。G1在进行垃圾清理的时候就是将一个Region的对象拷贝到另外一个Region中。

SATB
SATB的全称是Snapchat-At-The_Beginning。SATB是维持并发GC的一种手段。G1并发的基础就是SATB。SATB可以理解成在GC开始之前对堆内存里的对象做一次快照,此时活的对象就认为是活的,从而形成一个对象图。在GC收集的时候,新生代的对象也认为是活的对象,除此之外其他不可达的对象都认为是垃圾对象。
如何找到在GC的过程中分配的对象呢?每个region记录着两个top-at-mark-start(TAMS)指针,分别为prevTAMS和nextTAMS。在TAMS以上的对象就是新分配的,因而被视为隐式marked。通过这种方式我们就找到了在GC过程中新分配的对象,并把这些对象认为是活的对象。
解决了对象在GC过程中分配的问题,那么在GC过程中引用发生变化的问题怎么解决呢, G1给出的解决办法是通过Write Barrier。Write Barrier就是对引用字段进行赋值做了环切。通过Write Barrier就可以了解到哪些引用对象发生了什么样的变化。

RSet
RSet全称是Remember Set,每个Region中都有一个RSet,记录的是其他Region中的对象引用本Region对象的关系(谁引用了我的对象)。G1里面还有另外一种数据结构就Collection Set(CSet),CSet记录的是GC要收集的Region的集合,CSet里的Region可以是任意代的。在GC的时候,对于old->young和old->old的跨代对象引用,只要扫描对应的CSet中的RSet即可。

停顿预测模型
G1收集器突出表现出来的一点是通过一个停顿预测模型来根据用户配置的停顿时间来选择CSet的大小,从而达到用户期待的应用程序暂停时间。通过-XX:MaxGCPauseMillis参数来设置。这一点有点类似于ParallelScavenge收集器。关于停顿时间的设置并不是越短越好。设置的时间越短意味着每次收集的CSet越小,导致垃圾逐步积累变多,最终不得不退化成Serial GC;停顿时间设置的过长,那么会导致每次都会产生长时间的停顿,影响了程序对外的响应时间。

#G1回收的过程
G1垃圾回收分为两个阶段:
1、全局并发标记阶段(Global Concurrent marking)
2、拷贝存活对象阶段(evacuation)

全局并发标记阶段
    全局并发标记阶段是基于SATB的,与CMS有些类似,但是也有不同的地方,主要的几个阶段如下:
初始标记:该阶段会STW。扫描根集合,将所有通过根集合直达的对象压入扫描栈,等待后续的处理。在G1中初始标记阶段是借助Young GC的暂停进行的,不需要额外的暂停。虽然加长了Young GC的暂停时间,但是从总体上来说还是提高的GC的效率。
并发标记:该阶段不需要STW。这个阶段不断的从扫描栈中取出对象进行扫描,将扫描到的对象的字段再压入扫描栈中,依次递归,直到扫描栈为空,也就是说trace了所有GCRoot直达的对象。同时这个阶段还会扫描SATB write barrier所记录下的引用。
最终标记:也叫Remark,这个阶段也是STW的。这个阶段会处理在并发标记阶段write barrier记录下的引用,同时进行弱引用的处理。这个阶段与CMS的最大的区别是CMS在这个阶段会扫描整个根集合,Eden也会作为根集合的一部分被扫描,因此耗时可能会很长。
清理: 该阶段会STW。清点和重置标记状态。这个阶段有点像mark-sweep中的sweep阶段,这个阶段并不会实际上去做垃圾的收集,只是去根据停顿模型来预测出CSet,等待evacuation阶段来回收。

拷贝存活对象阶段
    Evacuation阶段是全暂停的。该阶段把一部分Region里的活对象拷贝到另一部分Region中,从而实现垃圾的回收清理。Evacuation阶段从第一阶段选出来的Region中筛选出任意多个Region作为垃圾收集的目标,这些要收集的Region叫CSet,通过RSet实现。
筛选出CSet之后,G1将并行的将这些Region里的存活对象拷贝到其他Region中,这点类似于ParalledScavenge的拷贝过程,整个过程是完全暂停的。关于停顿时间的控制,就是通过选择CSet的数量来达到控制时间长短的目标。

G1的收集模式:
YoungGC:收集年轻代里的Region
MixGC:年轻代的所有Region+全局并发标记阶段选出的收益高的Region
无论是YoungGC还是MixGC都只是并发拷贝的阶段。

分代G1模式下选择CSet有两种子模式,分别对应YoungGC和mixedGC:
YoungGC:CSet就是所有年轻代里面的Region
MixedGC:CSet是所有年轻代里的Region加上在全局并发标记阶段标记出来的收益高的Region

    G1的运行过程是这样的,会在Young GC和Mix GC之间不断的切换运行,同时定期的做全局并发标记,在实在赶不上回收速度的情况下使用Full GC(Serial GC)。初始标记是搭在YoungGC上执行的,在进行全局并发标记的时候不会做Mix GC,在做Mix GC的时候也不会启动初始标记阶段。当MixGC赶不上对象产生的速度的时候就退化成Full GC,这一点是需要重点调优的地方。

G1最佳实践
    在使用G1垃圾收集器的时候遵循以下实践可以少走不少弯路:

不断调优暂停时间指标
    通过XX:MaxGCPauseMillis=x可以设置启动应用程序暂停的时间,G1在运行的时候会根据这个参数选择CSet来满足响应时间的设置。一般情况下这个值设置到100ms或者200ms都是可以的(不同情况下会不一样),但如果设置成50ms就不太合理。暂停时间设置的太短,就会导致出现G1跟不上垃圾产生的速度。最终退化成Full GC。所以对这个参数的调优是一个持续的过程,逐步调整到最佳状态。

不要设置新生代和老年代的大小
    G1收集器在运行的时候会调整新生代和老年代的大小。通过改变代的大小来调整对象晋升的速度以及晋升年龄,从而达到我们为收集器设置的暂停时间目标。设置了新生代大小相当于放弃了G1为我们做的自动调优。我们需要做的只是设置整个堆内存的大小,剩下的交给G1自己去分配各个代的大小。

关注Evacuation Failure
Evacuation Failure类似于CMS里面的晋升失败,堆空间的垃圾太多导致无法完成Region之间的拷贝,于是不得不退化成Full GC来做一次全局范围内的垃圾收集。

G1常用参数
参数/默认值 含义

-XX:+UseG1GC	使用 G1 垃圾收集器
-XX:MaxGCPauseMillis=200	设置期望达到的最大GC停顿时间指标(JVM会尽力实现,但不保证达到)
-XX:InitiatingHeapOccupancyPercent=45	启动并发GC周期时的堆内存占用百分比. G1之类的垃圾收集器用它来触发并发GC周期,基于整个堆的使用率,而不只是某一代内存的使用比. 值为 0 则表示”一直执行GC循环”. 默认值为 45.
-XX:NewRatio=n	新生代与老生代(new/old generation)的大小比例(Ratio). 默认值为 2.
-XX:SurvivorRatio=n	eden/survivor 空间大小的比例(Ratio). 默认值为 8.
-XX:MaxTenuringThreshold=n	提升年老代的最大临界值(tenuring threshold). 默认值为 15.
-XX:ParallelGCThreads=n	设置垃圾收集器在并行阶段使用的线程数,默认值随JVM运行的平台不同而不同.
-XX:ConcGCThreads=n	并发垃圾收集器使用的线程数量. 默认值随JVM运行的平台不同而不同.
-XX:G1ReservePercent=n	设置堆内存保留为假天花板的总量,以降低提升失败的可能性. 默认值是 10.
-XX:G1HeapRegionSize=n	使用G1时Java堆会被分为大小统一的的区(region)。此参数可以指定每个heap区的大小. 默认值将根据 heap size 算出最优解. 最小值为 1Mb, 最大值为 32Mb.
G1日志分析
复制代码
//新生代GC
2018-05-03T10:21:43.209-0800: [GC pause (G1 Humongous Allocation) (young) (initial-mark), 0.0035356 secs]  //初始标记,耗时0.0035秒
   [Parallel Time: 2.4 ms, GC Workers: 8]  //并行8个线程,耗时2.4ms
      [GC Worker Start (ms): Min: 813.1, Avg: 813.7, Max: 813.9, Diff: 0.7]
      [Ext Root Scanning (ms): Min: 0.0, Avg: 1.1, Max: 1.5, Diff: 1.5, Sum: 9.1]   //每个扫描root的线程耗时
      [Update RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]   //更新RS的耗时,G1中每块区域都有一个RS与之对应,RS记录了该区域被其他区域引用的对象。回收时,就把RS作为根集的一部分,从而加快回收
         [Processed Buffers: Min: 0, Avg: 0.0, Max: 0, Diff: 0, Sum: 0]  //Processed Buffers就是记录引用变化的缓存空间
      [Scan RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]   //扫描RS
      [Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]  //根扫描耗时
      [Object Copy (ms): Min: 0.0, Avg: 0.5, Max: 1.3, Diff: 1.3, Sum: 3.6] //对象拷贝
      [Termination (ms): Min: 0.0, Avg: 0.2, Max: 0.2, Diff: 0.2, Sum: 1.2]   
         [Termination Attempts: Min: 1, Avg: 1.8, Max: 4, Diff: 3, Sum: 14]
      [GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.1]
      [GC Worker Total (ms): Min: 1.6, Avg: 1.8, Max: 2.3, Diff: 0.8, Sum: 14.1]   //GC线程耗时
      [GC Worker End (ms): Min: 815.4, Avg: 815.4, Max: 815.4, Diff: 0.0]
   [Code Root Fixup: 0.0 ms]
   [Code Root Purge: 0.0 ms]
   [Clear CT: 0.1 ms]   //清空CardTable耗时,RS是依赖CardTable记录区域存活对象的
   [Other: 1.1 ms]
      [Choose CSet: 0.0 ms]   //选取CSet
      [Ref Proc: 0.9 ms]  //弱引用、软引用的处理耗时
      [Ref Enq: 0.0 ms]   //弱引用、软引用的入队耗时
      [Redirty Cards: 0.1 ms]
      [Humongous Register: 0.0 ms]
      [Humongous Reclaim: 0.0 ms]
      [Free CSet: 0.0 ms]   //释放被回收区域的耗时(包含他们的RS)
   [Eden: 5120.0K(24.0M)->0.0B(12.0M) Survivors: 0.0B->2048.0K Heap: 16.0M(50.0M)->12.4M(50.0M)]
 [Times: user=0.01 sys=0.00, real=0.01 secs] 
 //根区域扫描
2018-05-03T10:21:43.213-0800: [GC concurrent-root-region-scan-start]
2018-05-03T10:21:43.214-0800: [GC concurrent-root-region-scan-end, 0.0012422 secs]
// 并发标记
2018-05-03T10:21:43.214-0800: [GC concurrent-mark-start]
2018-05-03T10:21:43.214-0800: [GC concurrent-mark-end, 0.0004063 secs]
//重新标记又叫最终标记
2018-05-03T10:21:43.214-0800: [GC remark 2018-05-03T10:21:43.215-0800: [Finalize Marking, 0.0003736 secs] 2018-05-03T10:21:43.215-0800: [GC ref-proc, 0.0000533 secs] 2018-05-03T10:21:43.215-0800: [Unloading, 0.0007439 secs], 0.0013442 secs]
 [Times: user=0.00 sys=0.00, real=0.00 secs] 
 //独占清理
2018-05-03T10:21:43.216-0800: [GC cleanup 13M->13M(50M), 0.0004002 secs]
 [Times: user=0.01 sys=0.00, real=0.00 secs]

 

分享到:
评论

相关推荐

    java管理windows系统内存_java释放内存缓存_java获得CPU使用率_系统内存_硬盘_进程源代码

    Windows实现按需调页的虚拟内存机制,使得应用程序可以使用超过物理内存容量的虚拟内存。此外,Windows还使用了页面文件来扩展物理内存的容量。 在Java中,我们可以使用Java Native Interface(JNI)来调用Windows ...

    Java 内存调整技巧

    Java 内存调整是优化Java应用程序性能的关键环节,特别是对于处理大量对象和复杂数据结构的企业级应用。Java的内存管理主要依赖于垃圾回收机制,它自动处理对象的创建和销毁,但在某些情况下,不适当的内存配置和...

    Java加载dll,导致Java进程内存泄露

    标题“Java加载dll,导致Java进程内存泄露”涉及到的是Java平台与本地库(DLL)交互时可能出现的问题。在Java中,通过Java Native Interface (JNI) 可以调用C/C++编写的动态链接库(DLL),实现Java代码与本地代码的...

    Java内存模型详解

    ### Java内存模型详解 #### 1. JMM简介 ##### i. 内存模型概述 Java内存模型(Java Memory Model, JMM)是Java虚拟机(JVM)的一部分,用于规定程序中的各种变量(包括实例字段、静态字段和数组元素等)在多个...

    java内存模型.pdf

    Java内存模型(JMM)是Java虚拟机(JVM)的一部分,它定义了程序中不同变量如何交互,特别是在多线程环境下。JMM确保了在各种硬件和操作系统平台上,Java程序的行为具有一致性和可预测性。Java内存模型的主要目标是...

    java内存模型

    Java内存模型(Java Memory Model,JMM)是Java虚拟机(JVM)规范中的一个重要组成部分,它定义了程序中各个变量(包括实例域、静态域和数组元素)的访问规则,以及在实际计算机系统中如何将这些变量存储在内存和从...

    java内存管理问题及解决办法

    Java内存管理是Java编程中至关重要的一环,它涉及到程序的性能和稳定性。本文将深入探讨Java内存管理机制,包括垃圾回收、内存分配与释放、内存泄漏及其预防措施,以及四种引用类型的特点和应用场景。 首先,Java...

    java内存讲解

    ### Java内存管理与垃圾回收机制详解 #### 一、引言 Java作为一种广泛使用的编程语言,其内存管理和垃圾回收机制是开发人员必须了解的核心概念之一。本文将详细探讨JVM的垃圾回收机制及其调优方法,深入分析Java...

    java避免内存泄露

    ### Java避免内存泄露的关键知识点 #### 一、内存泄露的概念及原因 内存泄露是指程序在申请内存后未能释放,导致这部分内存无法再次被利用。在Java中,由于具备垃圾回收机制(GC),理论上开发者不必担心内存泄露...

    Java内存模型,全面了解Java内存模型,减少代码bug

    ### Java内存模型详解 #### 一、JMM简介 ##### 1. 内存模型概述 Java内存模型(Java Memory Model, JMM)是Java虚拟机(JVM)的一部分,它规定了程序中各种变量(包括实例字段、静态字段和数组元素)的访问规则,...

    C回调java几种方法

    使用JNI时,还需要注意内存管理、异常处理以及线程安全等问题,确保Java和C代码之间的交互稳定可靠。在开发过程中,利用JNI的调试工具,如`javah`生成头文件,以及`jdb`进行调试,能够提高开发效率和代码质量。

    关于java内存泄露问题解决

    ### 关于Java内存泄露问题解决 #### 引言:Java内存管理的核心——垃圾回收机制(GC) Java作为当今产业界和学术界最炙手可热的编程语言之一,以其独特的安全性和可移植性赢得了广泛的应用,尤其是在服务器端编程...

    Java系统中内存泄漏测试方法的研究

    二、Java内存模型 在Java中,内存分为堆(Heap)和栈(Stack)两部分。堆是存储对象实例的地方,而栈则存储方法调用时的局部变量。Java虚拟机(JVM)有自动垃圾回收机制(Garbage Collection, GC),用于回收不再...

    jdk 内存设置jdk内存设置

    jdk 内存设置是 Java 开发中非常重要的一部分。它直接影响着 Java 应用程序的性能和稳定性。jdk 内存设置主要包括堆大小设置、垃圾收集器的选择和配置等几个方面。 堆大小设置是 jdk 内存设置的核心内容。堆大小的...

    JAVA 线程实现数据库的主从同步更新

    线程是程序执行的最小单元,一个进程可以包含多个线程,每个线程都有自己的程序计数器、寄存器和栈空间,但共享同一块内存空间。在Java中,我们可以通过实现Runnable接口或者继承Thread类来创建线程。 在主从同步的...

Global site tag (gtag.js) - Google Analytics