JVM堆内存


  • JVM内存划分为堆内存和非堆内存,堆内存分为年轻代(Young Generation)、老年代(Old Generation),非堆内存就一个永久代(Permanent Generation)。

  • 年轻代又分为Eden和Survivor区。Survivor区由FromSpace和ToSpace组成。Eden区占大容量,Survivor两个区占小容量,默认比例是8:1:1。

  • 堆内存用途:存放的是对象,垃圾收集器就是收集这些对象,然后根据GC算法回收。

  • 非堆内存用途:永久代,也称为方法区,存储程序运行时长期存活的对象,比如类的元数据、方法、常量、属性等。

补充:JDK1.8版本废弃了永久代,替代的是元空间(MetaSpace),元空间与永久代上类似,都是方法区的实现,他们最大区别是:元空间并不在JVM中,而是使用本地内存。

对象分代

  • 新生成的对象首先放到年轻代Eden区

  • 当Eden空间满了,触发Minor GC,存活下来的对象移动到Survivor0区

  • Survivor0区满后触发执行Minor GC,Survivor0区存活对象移动到Suvivor1区,这样保证了一段时间内总有一个survivor区为空

  • 经过多次Minor GC仍然存活的对象移动到老年代

  • 老年代存储长期存活的对象,占满时会触发Major GC(Full GC),GC期间会停止所有线程等待GC完成,所以对响应要求高的应用尽量减少发生Major GC,避免响应超时

Minor GC : 清理年轻代
Major GC(Full GC) : 清理老年代,清理整个堆空间,会停止应用所有线程。

Jstat

虚拟机或服务器查看当前jvm内存使用以及垃圾回收情况

1
2
3
4
5
6
7
8
9
jps 查看namenode的pid
7281 DataNode
7811 SecondaryNameNode
7219 ResourceManager
14554 Jps
7438 NodeManager
11647 JobHistoryServer

jstat -gc -t 7438 1s #显示pid是58563的垃圾回收堆的行为统计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#C即Capacity 总容量,U即Used 已使用的容量
S0C: 当前survivor0区容量(kB)
S1C: 当前survivor1区容量(kB)
S0U: survivor0区已使用的容量(KB)
S1U: survivor1区已使用的容量(KB)
EC: Eden区的总容量(KB)
EU: 当前Eden区已使用的容量(KB)
OC: Old空间容量(kB)。
OU: Old区已使用的容量(KB)
MC: Metaspace空间容量(KB)
MU: Metacspace使用量(KB)
CCSC: 压缩类空间容量(kB)
CCSU: 压缩类空间使用(kB)
YGC: 新生代垃圾回收次数
YGCT: 新生代垃圾回收时间
FGC: 老年代full GC垃圾回收次数
FGCT: 老年代垃圾回收时间
GCT: 垃圾回收总消耗时间

HDFS-GC详细日志输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#日志输出文件地址
export HADOOP_LOG_DIR=/hadoop/logs/

#JMX配置
export HADOOP_JMX_OPTS="-Dcom.sun.management.jmxremote.authenticate=false - Dcom.sun.management.jmxremote.ssl=false"

export HADOOP_NAMENODE_OPTS="-Dhadoop.security.logger=${HADOOP_SECURITY_LOGGER:- INFO,RFAS} -Dhdfs.audit.logger=${HDFS_AUDIT_LOGGER:-INFO,NullAppender} $HADOOP_NAMENODE_OPTS"

export HADOOP_DATANODE_OPTS="-Dhadoop.security.logger=ERROR,RFAS $HADOOP_DATANODE_OPTS"

export NAMENODE_OPTS="-verbose:gc -XX:+PrintGCDetails - Xloggc:${HADOOP_LOG_DIR}/logs/hadoop-gc.log \ -XX:+PrintGCDateStamps -XX:+PrintGCApplicationConcurrentTime - XX:+PrintGCApplicationStoppedTime \ -server -Xms150g -Xmx150g -Xmn20g -XX:SurvivorRatio=8 - XX:MaxTenuringThreshold=15 \ -XX:ParallelGCThreads=18 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC - XX:+UseCMSCompactAtFullCollection -XX:+DisableExplicitGC - XX:+CMSParallelRemarkEnabled \ -XX:+CMSClassUnloadingEnabled -XX:CMSInitiatingOccupancyFraction=70 - XX:+UseFastAccessorMethods -XX:+UseCMSInitiatingOccupancyOnly - XX:CMSMaxAbortablePrecleanTime=5000 \ -XX:+UseGCLogFileRotation -XX:GCLogFileSize=20m - XX:ErrorFile=${HADOOP_LOG_DIR}/logs/hs_err.log.%p - XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=${HADOOP_LOG_DIR}/logs/%p.hprof \"

export DATENODE_OPTS="-verbose:gc -XX:+PrintGCDetails - Xloggc:${HADOOP_LOG_DIR}/hadoop-gc.log \ -XX:+PrintGCDateStamps -XX:+PrintGCApplicationConcurrentTime - XX:+PrintGCApplicationStoppedTime \ -server -Xms15g -Xmx15g -Xmn4g -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=15 \-XX:ParallelGCThreads=18 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC - XX:+UseCMSCompactAtFullCollection -XX:+DisableExplicitGC - XX:+CMSParallelRemarkEnabled \ -XX:+CMSClassUnloadingEnabled -XX:CMSInitiatingOccupancyFraction=70 - XX:+UseFastAccessorMethods -XX:+UseCMSInitiatingOccupancyOnly - XX:CMSMaxAbortablePrecleanTime=5000 \ -XX:+UseGCLogFileRotation -XX:GCLogFileSize=20m - XX:ErrorFile=${HADOOP_LOG_DIR}/logs/hs_err.log.%p - XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=${HADOOP_LOG_DIR}/logs/%p.hprof \"

export HADOOP_NAMENODE_OPTS="$NAMENODE_OPTS $HADOOP_NAMENODE_OPTS"

export HADOOP_DATANODE_OPTS="$DATENODE_OPTS $HADOOP_DATANODE_OPTS"
1
2
3
4
5
6
7
8
-Xms150g -Xmx150g :堆内存大小最大和最小都是150g
-Xmn20g :新生代大小为20g,等于eden+2*survivor,意味着老年代为150-20=130g。
-XX:SurvivorRatio=8 :Eden和Survivor的大小比值为8,意味着两个Survivor区和一个Eden区的比值为2:8,一个Survivor占整个年轻代的1/10
-XX:ParallelGCThreads=10 :设置ParNew GC的线程并行数,默认为 8 + (Runtime.availableProcessors - 8) * 5/8 ,24核机器为18。
-XX:MaxTenuringThreshold=15 :设置对象在年轻代的最大年龄,超过这个年龄则会晋升到老年代
-XX:+UseParNewGC :设置新生代使用Parallel New GC
-XX:+UseConcMarkSweepGC :设置老年代使用CMS GC,当此项设置时候自动设置新生代为ParNew GC
-XX:CMSInitiatingOccupancyFraction=70 :老年代第一次占用达到该百分比时候,就会引发CMS的第一次垃圾回收周期。后继CMS GC由HotSpot自动优化计算得到。

GC日志解析

  • 查看GC日志输出文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
3.157: [GC (Allocation Failure) 3.157: [ParNew: 272640K->34048K(306688K), 0.0844702 secs] 272640K->69574K(2063104K), 0.0845560 secs] [Times: user=0.23 sys=0.03, real=0.09 secs]
4.092: [GC (Allocation Failure) 4.092: [ParNew: 306688K->34048K(306688K), 0.1013723 secs] 342214K->136584K(2063104K), 0.1014307 secs] [Times: user=0.25 sys=0.05, real=0.10 secs]
... cut for brevity ...
11.292: [GC (Allocation Failure) 11.292: [ParNew: 306686K->34048K(306688K), 0.0857219 secs] 971599K->779148K(2063104K), 0.0857875 secs] [Times: user=0.26 sys=0.04, real=0.09 secs]
12.140: [GC (Allocation Failure) 12.140: [ParNew: 306688K->34046K(306688K), 0.0821774 secs] 1051788K->856120K(2063104K), 0.0822400 secs] [Times: user=0.25 sys=0.03, real=0.08 secs]
12.989: [GC (Allocation Failure) 12.989: [ParNew: 306686K->34048K(306688K), 0.1086667 secs] 1128760K->931412K(2063104K), 0.1087416 secs] [Times: user=0.24 sys=0.04, real=0.11 secs]
13.098: [GC (CMS Initial Mark) [1 CMS-initial-mark: 897364K(1756416K)] 936667K(2063104K), 0.0041705 secs] [Times: user=0.02 sys=0.00, real=0.00 secs]
13.102: [CMS-concurrent-mark-start] 13.341: [CMS-concurrent-mark: 0.238/0.238 secs] [Times: user=0.36 sys=0.01, real=0.24 secs]
13.341: [CMS-concurrent-preclean-start] 13.350: [CMS-concurrent-preclean: 0.009/0.009 secs] [Times: user=0.03 sys=0.00, real=0.01 secs] 13.350: [CMS-concurrent-abortable-preclean-start]
13.878: [GC (Allocation Failure) [ParNew: 16844397K->85085K(18874368K), 0.0960456 secs]116885867K->100127390K(155189248K), 0.0961542 secs] [Times: user=0.14 sys=0.00, real=0.05 secs]
14.366: [CMS-concurrent-abortable-preclean: 0.917/1.016 secs] [Times: user=2.22 sys=0.07, real=1.01 secs]
14.366: [GC (CMS Final Remark) [YG occupancy: 182593 K (306688 K)]14.366: [Rescan (parallel) , 0.0291598 secs]14.395: [weak refs processing, 0.0000232 secs]14.395: [class unloading, 0.0117661 secs]14.407: [scrub symbol table, 0.0015323 secs]14.409: [scrub string table, 0.0003221 secs][1 CMS-remark: 976591K(1756416K)] 1159184K(2063104K), 0.0462010 secs] [Times: user=0.14 sys=0.00, real=0.05 secs]
14.412: [CMS-concurrent-sweep-start]
14.633: [CMS-concurrent-sweep: 0.221/0.221 secs] [Times: user=0.37 sys=0.00, real=0.22 secs]
14.633: [CMS-concurrent-reset-start] 14.636: [CMS-concurrent-reset: 0.002/0.002 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
  1. ParNew: 16844397K->85085K(18874368K), 0.0960456 secs;其中, 16844397K 表示GC前的新生代占用量, 85085K 表示GC后的新生代占用量,GC后Eden和一个Survivor为空,所以 85085K 也是另一个Survivor的占用量。括号中的 18874368K 是Eden+一个被占用Survivor的总和(18g)。

  2. 116885867K->100127390K(155189248K), 0.0961542 secs;其中,分别是Java堆在垃圾回收前后的大小,和Java堆大小。说明堆使用为116885867K=111.47g,回收大小为100127390K=95.49g,堆大小为155189248K=148g(去掉其中一个Survivor),回收了16g空间。

总结:在HDFS Namenode内存中的对象大都是文件,目录和blocks,这些数据只要不被程序或者数据的拥有者人为的删除,就会在Namenode的运 行生命期内一直存在,所以这些对象通常是存在在old区中,所以,如果整个hdfs文件和目录数多,blocks数也多,内存数据也会很大,

如何降低Full GC的影响?

计算NN所需的内存大小,合理配置JVM

使用低卡顿G1收集器

  • 为什么会有G1呢?

    因为并发、并行和CMS垃圾收集器都有2个共同的问题

    1. 老年代收集器大部分操作都必须扫描整个老年代空间(标记,清除和压缩)。这就导致了GC随着Java堆空间而线性增加或减少

    2. 年轻代和老年代是独立的连续内存块,所以要先决定年轻代和年老代放在虚拟地址空间的位置

  • G1的优点

G1垃圾收集器利用分而治之的思想将堆进行分区,划分为一个个的区域。

G1垃圾收集器将堆拆成一系列的分区,这样的话,大部分的垃圾收集操作就只在一个分区内执行,从而避免很多GC操作在整个Java堆或者整个年轻代进行。

1
export HADOOP_NAMENODE_OPTS="-server -Xmx220G -Xms200G -XX:+UseG1GC - XX:MaxGCPauseMillis=200 -XX:+UnlockExperimentalVMOptions - XX:+ParallelRefProcEnabled -XX:-ResizePLAB -XX:+PerfDisableSharedMem -XX:- OmitStackTraceInFastThrow -XX:G1NewSizePercent=2 -XX:ParallelGCThreads=23 - XX:InitiatingHeapOccupancyPercent=40 -XX:G1HeapRegionSize=32M - XX:G1HeapWastePercent=10 -XX:G1MixedGCCountTarget=16 -verbose:gc - XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps - XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=100M - Xloggc:/var/log/hbase/gc.log -Dhadoop.security.logger=${HADOOP_SECURITY_LOGGER:- INFO,RFAS} -Dhdfs.audit.logger=${HDFS_AUDIT_LOGGER:-INFO,NullAppender} $HADOOP_NAMENODE_OPTS"

注意:如果现在采用的垃圾收集器没有问题,就不要选择G1,如果追求低停顿,可以尝试使用G1