今天线上的hadoop集群崩溃了,现象是namenode一直在GC,长时间无法正常服务。最后运维大神各种倒腾内存,GC稳定后,服务正常。虽说全程在打酱油,但是也跟着学习不少的东西。
有过JVM经验的开发者都应该知道,GC是在内存不够时,JVM自动进行的自我救赎(删除不用的数据,释放内存空间)。那么NameNode在什么情况下会进行GC呢?在解释这个问题之前,需要明白GC的几种级别,以及触发的条件:
Full GC的触发条件一般是:
参考:https://blog.csdn.net/chenleixing/article/details/46706039
可以通过下面的配置输出GC日志,并记录FullGC前的线程情况:
-Xmx2g -XX:+HeapDumpBeforeFullGC
-XX:HeapDumpPath=. -Xloggc:gc.log
-XX:+PrintGC -XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-XX:+UseGCLogFileRotation
-XX:NumberOfGCLogFiles=10
-XX:GCLogFileSize=100m
-XX:HeapDumpOnOutOfMemoryError
再来说说,namenode什么情况下会触发GC:
由于namenode中需要维护namespace(目录结构)和blockmanager(block状态),因此内存消耗很严重。如果小文件很多,势必会增加两部分的内存消耗。
参考:https://blog.csdn.net/qq_25418883/article/details/79926153
JVM提供了很多JVM内存分析的工具,如jstat,使用的方法就是:
# 先执行top查找指定的进程pid
top
# 然后使用jstat分析内存情况
jstat -gc 12345 5000
# 12345是java的进程id,5000是gc的间隔时间,单位是ms
我这边有一段gc的记录:
[hdfs@hnode1 ~]$ jstat -gc 1842 5000
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
127744.0 127744.0 0.0 116918.1 1022464.0 755982.7 11304960.0 1199292.3 30752.0 30014.9 3672.0 3477.0 25 3.058 2 0.302 3.360
127744.0 127744.0 0.0 127744.0 1022464.0 0.0 11304960.0 1643454.1 30752.0 30150.5 3672.0 3492.1 35 4.111 2 0.302 4.413
127744.0 127744.0 127744.0 0.0 1022464.0 613512.1 11304960.0 2042124.0 30752.0 30161.0 3672.0 3493.6 44 5.336 2 0.302 5.638
127744.0 127744.0 127744.0 122761.9 1022464.0 1022464.0 11304960.0 2517047.0 30752.0 30161.0 3672.0 3493.6 55 6.121 2 0.302 6.422
127744.0 127744.0 0.0 127744.0 1022464.0 304352.9 11304960.0 2976155.9 31008.0 30286.0 3672.0 3501.9 65 6.950 2 0.302 7.252
127744.0 127744.0 0.0 127744.0 1022464.0 211192.9 11304960.0 3267759.0 31008.0 30498.1 3672.0 3525.3 71 7.498 2 0.302 7.800
127744.0 127744.0 127744.0 0.0 1022464.0 467990.3 11304960.0 3413694.4 31008.0 30498.1 3672.0 3525.3 72 7.834 2 0.302 8.136
127744.0 127744.0 0.0 127744.0 1022464.0 693620.3 11304960.0 3598033.7 31008.0 30502.2 3672.0 3525.3 73 8.333 2 0.302 8.635
127744.0 127744.0 0.0 67244.8 1022464.0 245206.5 11304960.0 3953531.0 31264.0 30745.0 3672.0 3547.3 77 9.108 2 0.302 9.410
127744.0 127744.0 111122.2 0.0 1022464.0 736237.5 11304960.0 4239210.9 31264.0 30746.5 3672.0 3547.3 88 9.792 2 0.302 10.094
127744.0 127744.0 119642.4 0.0 1022464.0 470375.6 11304960.0 4575876.0 31264.0 30746.5 3672.0 3547.3 100 10.584 2 0.302 10.886
127744.0 127744.0 0.0 127672.5 1022464.0 878275.8 11304960.0 4887903.6 31264.0 30767.6 3672.0 3547.3 111 11.314 2 0.302 11.616
在GC日志中能看到什么?
由于JVM设计者认为,大部分的对象都是新创建的,生命周期都不长。因此新建的对象会直接放在新生代中,并采用复制回收机制。即保证to区总是空的,每次触发GC的时候,就会把Eden和from survivor中的还存活的对象拷贝到to区中。然后to变成了from,from变成to。这样反复几次,还存活的对象,就会拷贝到old老年代当中。
在配置JVM的时候就有几个比较重要的参数:
参考:http://ifeve.com/useful-jvm-flags-part-5-young-generation-garbage-collection/
通过前面的讲述,应该了解到:
表示新创建的对象过多、survivor剩余空间太小;如果老年代内存利用率不高,那么可以考虑调整老年代的阈值。
就需要认真排查了,比如:
看看是不是内存发生泄漏,内存泄漏的情况:
在老版本的JVM中,类的方法数据、字节码、变量大小、常量池等等被认为是静态信息,不由程序创建而控制,存储在永久带Perm当中。
在1.8中,这部分数据放入了一个叫做元空间的地方。
带来的好处:
参考:https://www.cnblogs.com/paddix/p/5309550.html
1 合并小文件 2 优化目录树:https://www.cnblogs.com/yangjiandan/p/3535125.html