如果您的应用程序的对象创建率很高,因此,垃圾回收率也将非常高。高垃圾收集率也会增加GC暂停时间。因此,优化应用程序以创建较少数量的对象是减少长GC暂停的有效策略。这可能是比较耗时,但值得100%进行。为了优化应用程序中的对象创建速度,您可以考虑使用Java Profiler(如 JProfiler, YourKit,JVisualVM )。这些分析器将报告
小提示:如何计算对象创建率
当年轻代太小时,对象将被过早地提升为老代。从老年代收集垃圾要比从年轻代收集垃圾花费更多的时间。因此,增加年轻代的大小可以减少长时间的GC暂停。可以通过设置两个JVM参数中的任何一个来增加年轻代
GC算法的选择对GC暂停时间有很大影响。除非您是GC专家或者打算成为一个专家或者团队中的某人是GC专家,否则您可以调整GC设置以获得最佳的GC暂停时间。假设您不具备GC专业知识,那么我建议您使用G1 GC算法,因为它具有 自动调整功能。在G1 GC中,您可以使用系统属性“ -XX:MaxGCPauseMillis”设置GC暂停时间目标。例:
根据上面的示例,最大GC暂停时间设置为200毫秒。这是一个软目标,JVM将尽力实现这一目标。如果您已经在使用G1 GC算法,并且仍然继续经历高暂停时间,请参考本文。
有时由于内存不足(RAM), *** 作系统可能正在从内存中交换应用程序Swapping非常昂贵,因为它需要磁盘访问,这比物理内存访问要慢得多交换过程时,GC将花费很长时间才能完成。
下面是从StackOverflow获得的脚本(感谢作者)-执行该脚本 将显示所有正在交换的进程。请确保您的进程没有被交换
如果发现进程正在交换,请执行以下 *** 作之一:
a。向服务器分配更多RAM
b。减少服务器上运行的进程数,以便它可以释放内存(RAM)。
C。减小应用程序的堆大小(我不建议这样做,因为它可能导致其他副作用)
对于GC日志中报告的每个GC事件,将打印user,sys和real time。例:
(如果在GC事件中您始终注意到“real time”并不比“user”时间显着少,则可能表明GC线程不足。考虑增加GC线程数。假设“user”时间为25秒,并且您已将GC线程数配置为5,那么“real time”应接近5秒(因为25秒/ 5个线程= 5秒)。
警告:添加过多的GC线程将消耗大量CPU,并会占用应用程序的资源。因此,您需要在增加GC线程数之前进行彻底的测试
如果文件系统的I / O活动繁重(即发生大量读取和写入 *** 作),也会导致长时间的GC暂停。这种繁重的文件系统I / O活动可能不是由您的应用程序引起的。可能是由于同一服务器上正在运行的另一个进程引起的,仍然可能导致您的应用程序长时间处于GC暂停状态( >
最近所负责的服务略频繁地收到4xx告警
1、查业务日志,没发现相关错误的日志
2、查nginx access log,发现返回的状态码都是499,从request_uri查了一遍发现不是集中在某一个请求上,说明应该不是某个接口的问题了,有可能进程层面问题了。
通过对upstream_addr 分类,可以看到问题基本都是集中在 某一台这台机器上
3、网上资料了解到,499 是 nginx 扩展的 4xx 错误,代表客户端请求还未返回时,客户端主动断开连接。原因有几种,不过大部分原因都说到有可能服务器upstream处理过慢,导致用户提前关闭连接。那就先往这个方向排查,登录机器查看实际的accesslog
发现upstream response都是10s以上。这就证明了上游服务器处理10秒还没有响应,因此nginx提前关闭链接,返回499
4、为什么进程响应如此慢,10秒太不正常了。考虑到那段时间就只有一台机器有问题,而且是进程层面的问题,首先想到的是GC,于是再次登录到机器上查看gc log。发现有Full GC,时间点和告警的时间也吻合。 惊呆的是,这次FullGC耗时长达1907秒,由于我们的服务使用的是jdk8默认的ParallelGC,FullGC期间,整个应用Stop The World的。这是非常恐怖的一件事
由此看来,4xx告警的初步原因已经定位到,就是FullGC导致的。
那么究竟为什么会发生FullGC呢?需要深入分析一下。
借助服务治理平台的JVM监控观察了几天。期间不同机器不同时间也发生了几次FullGC。从监控发现,基本每台机器隔两天就会发生一次FullGC,每次FullGC后年老代回收的垃圾不算多,使用比例还是挺高的。
为什么年老代空间占用这么多?
继续分析上面那条full gc log,
1、发生full gc时,年老代内存已经占用了9998%了(1048397/1048576)。看起来因为年老代满了而触发的FullGC了。
2、full gc回收了年老代大约302M的垃圾,回收后年老代占用704%(738282/1048576)。这占用率还是比较高的。
1、首先jmap简单打印一下所有对象的信息。发现有ClassPathList和ClassClassPath两个类的对象数量高达1000多万,并且这两个数量是一样的。仿佛嗅到了内存泄漏的味道。
2、只依靠对象统计信息,不足以定位问题,需要使用完整HeapDump,通过MAT进一步分析
jmap把完整堆heapDump下来
隔一段时间后,继续jmap,这次只取存活对象的dump(实际效果是先执行一次FULL GC)
可以看到,经过Full GC后,ClassPathList对象没有被回收,数量反而继续增加。到这里,基本可以确定,ClassPathList是存在泄漏了。
那么,ClassPathList究竟被谁引用着,导致回收不掉呢?
通过MAT的OQL过滤出老生代的ClassPathList对象,从对象的关联关系上继续深入分析。
首先需要知道老生代的地址区间,可以使用vjtools
通过vjmap的address命令,快速打印各代地址。
可以得知,oldGen的下界是0x80000000,上界是0xc0000000(注意OQL中使用时要把数值前的那串0去掉)。
执行OQL只查询年老代中的ClassPathList对象:执行OQL只查询年老代中的ClassPathList对象:
抽取其中一个对象分析,可以发现这个ClassPathList对象被一连串不同ClassPathList对象的next属性引用着。看起来是个链表的结构
再看看GCRoot,发现是被AppClassLoader也就是我们的应用类加载器引用着。除非这个加载器卸载了,否则ClassPathList对象是不会被GC掉了。
分析到这里,似乎离真相越来越近了。到底这个ClassPathList在项目中哪里使用到了?
通过前面的分析知道了ClassPathList的整体引用关系链:
AppClassLoader -> ClassPool类的defaultPool字段 -> ClassPoolTail类的source字段 -> ClassPathList类的pathList
可以看到,ClassPathList有两个属性,一个是next,结合之前MAT的分析,ClassPathList的确就是一个链表的结构。随着时间的增长,ClassPathList不断新增,链表也随之变得越来越大,最后内存占用逐渐上升。
另一个path字段属于ClassPath类型,ClassPath是个接口,查看它的实现类,发现一个似曾相识的名称ClassClassPath,之前分析对象统计信息时,还有一个类的对象数量是和ClassPathList一样的,正是这个ClassClassPath。每新增一个ClassPathList,都会伴随着新增对应的ClassPath对象,这也解释了为什么两者数量是一致的了。
通过注释知道,这个ClassClassPath的作用大概就是,利用一个叫ClassPool的对象,可以调用其insertClassPath方法来新增一个ClassClassPath对象,insertClassPath方法内部通过头插法将ClassClassPath添加到ClassPathList链表,从而形成一个search-path,然后通过这个search-path能够获取到某一个Class类的信息。
于是尝试着搜了一下,看看项目中有没有调用到insertClassPath方法的地方。意外发现一个类,
这不就是我们项目用来打印方法入参、执行耗时、上报metrics的@AutoLog的实现类吗。
可以看到getParams方法中调用了insertClassPath,注解@AutoLog的printParams默认为true,也就是每次调用都需要打印方法入参,每次打印前都要调用getParams先获取参数名称。因此每次都会insertClassPath,从而导致ClassPathList链表越来越大。
至此,内存泄漏的元凶已经找到。解决方法也就简单了。
因为目标只是想得到方法的参数名称,通过JoinPoint其实能直接获取到,因此可以改成JoinPoint获取的方式。
为了进行对比,分别在修改前后各进行一次压测。压测JVM参数大致与线上一致,为了尽快看到效果,只是调小了heap的大小。-Xms200m -Xmx200m
ClassPathList数量不断增长
年老代每次能回收的垃圾越来越少,每次回收过后的剩余空间也越来越小。最终整个年老代被撑满
虽然还没触发OOM,但是CPU负载飙高,从基本都在处于频繁的FULLGC状态
ClassPathList已经被消灭掉了
FullGC也趋于规律化了。每次回收的垃圾大致都相同
第一种方式是在启动参数增加 -XX:+PrintHeapAtGC,每次GC都打印地址
第二种方式是使用vjmap的命令,在-old, -sur, -address 中,都会打印出该区间的地址
第三种方式,使用vjmap的address命令,快速打印各代地址,不会造成过长时间停顿。
附: 我们服务的JVM参数
楼主,很高兴为您解答问题您所谓的挂是什么情况呢
是指经常出现连接不上服务器的情况〉?
如果经常连接不上服务器的话,我觉得原因有多种
1检查下硬件,看看是不是线接触不良,或者硬件老化
2看下系统是否中毒
3看自己使用的软件,是否有漏洞
4更换台服务器试下,看看还有类似的情况没有
希望能帮助您dockergitlab服务器挂了准确来说应该是今天升级了阿里云的ECS内存之后重启实例,结果发现所有跟docker相关的东西都坏掉了。docker启动不了,所有镜像都查不到。我们的gitlab是用的docker,所以必须要把这个给弄好。
查看docker相关的文件和镜像容器都在,所以猜测数据可能没受到损坏。具体修复过程分为以下几个阶段:
1、这是由于重启了服务器造成的,所以有可能再重启一次情况会回复,但是重启后结果还是不行。
2、启动docker 的时候执行service docker start指令,显示数据如下图:
docker start/running,process 。这条指令并没有说明docker已经运行,因为我查询所有进程的时候根本没有docker,具体原因可以百度下。
3、找大神帮忙,加入了几个docker群,其中在docker分享群2中几位大神纷纷出来指点。
其中一位说service 只是相当于一个快捷方式,这样启动不了就去docker下直接手动启动。可是我找了半天没找到在哪启动。第二位朋友说dockerd指令,这个是手动启动docker的,可是执行后还是不行,(/dockerd也失败)
提示信息里说可能没有安装docker。可是我重启服务器之前运行了将近半年都是OK的,但是我不排除重启后docker完全损坏,不被识别的可能。
使用uname -a查看内核版本,看看是不是不支持docker。按照他的解释是,他之前遇到过,重启服务器之后内核更新了,导致不支持docker所以这也是一种可能。
查看docker版本:
我这里是162的客户端,
linux内核313
确认了我的服务器内核是支持docker的,所以把这个可能排除。
其中杭州的以为朋友注意到,我上边的错误提示里有一句缺少dockersock文件。所以建议我在相应的目录下简历dockersock。上边提示信息的完整路径是/var/run/dockersock。
按照上边说的建立后,再执行出现以下信息:
这时候注意后边那条提示,shutting down,看到这之后大神给出一条指令sudo apt-get install apparmor,说执行完之后就没问题了。
执行完之后果断docker可以起来了

在top中查询:

消失已久的docker终于出来了,而且docker下以前建立的容器都还在,手动起一下就好。
感谢各位大神的帮助,我的docker又复活了。总之不熟悉这个的朋友最好还是慎用,或者有人指点也好,省的不知道出问题之后该找谁。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)