结合代码和内存变化图一步步弄懂JVM的FullGC
1.年轻代存活的对象太多,老年代了放不下
01.示例代码
public class DemoTest1 {
public static void main(String[] args) {
byte[] array1 = new byte[4 * 1024 * 1024];
array1 = null;
byte[] array2 = new byte[2 * 1024 * 1024];
byte[] array3 = new byte[2 * 1024 * 1024];
byte[] array4 = new byte[2 * 1024 * 1024];
byte[] array5 = new byte[128 * 1024];
byte[] array6 = new byte[2 * 1024 * 1024];
}
02.启动JVM参数
-XX:NewSize=10485760 -XX:MaxNewSize=10485760 -XX:InitialHeapSize=20971520 -XX:MaxHeapSize=20971520 -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=15 -XX:PretenureSizeThreshold=3145728 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:gc.log
其中,参数-XX:PretenureSizeThreshold
,参数要设置大对象阈值为3MB,也就是超过3MB,就直接进入老年代。
大对象大小是3MB。一旦对象大小超过3MB,不会进入新生代,直接进入老年代。
启动命令:
java -jar -XX:NewSize=10485760 -XX:MaxNewSize=10485760 -XX:InitialHeapSize=20971520 -XX:MaxHeapSize=20971520 -XX:SurvivorRatio=8 -XX:MaxTenuringThre
shold=15 -XX:PretenureSizeThreshold=3145728 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:gc.log jvm-demo.jar
03.GC日志
启动之后就得到如下GC日志:
Java HotSpot(TM) 64-Bit Server VM (25.151-b12) for windows-amd64 JRE (1.8.0_151-b12), built on Sep 5 2017 19:33:46 by "java_re" with MS VC++ 10.0 (VS2010)
Memory: 4k page, physical 16703268k(7458748k free), swap 23781156k(9784196k free)
CommandLine flags: -XX:InitialHeapSize=20971520 -XX:MaxHeapSize=20971520 -XX:MaxNewSize=10485760 -XX:MaxTenuringThreshold=15 -XX:NewSize=10485760 -XX:OldPLABSize=16 -XX:PretenureSizeThreshold=3145728 -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:SurvivorRatio=8 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseConcMarkSweepGC -XX:-UseLargePagesIndividualAllocation -XX:+UseParNewGC
0.174: [GC (Allocation Failure) 0.174: [ParNew (promotion failed): 7457K->8328K(9216K), 0.0046949 secs]
0.179: [CMS: 8194K->6962K(10240K), 0.0033396 secs] 11553K->6962K(19456K), [Metaspace: 2970K->2970K(1056768K)], 0.0089224 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
Heap
par new generation total 9216K, used 2130K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
eden space 8192K, 26% used [0x00000000fec00000, 0x00000000fee14930, 0x00000000ff400000)
from space 1024K, 0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
to space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
concurrent mark-sweep generation total 10240K, used 6962K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
Metaspace used 2976K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 330K, capacity 386K, committed 512K, reserved 1048576K
04.分析GC日志
先看如下代码:
byte[] array1 = new byte[4 * 1024 * 1024];
array1 = null;
这行代码直接分配了一个4MB的大对象,此时这个对象会直接进入老年代,接着array1不再引用这个对象。
此时内存分配如下:
紧接着就是如下代码
byte[] array2 = new byte[2 * 1024 * 1024];
byte[] array3 = new byte[2 * 1024 * 1024];
byte[] array4 = new byte[2 * 1024 * 1024];
byte[] array5 = new byte[128 * 1024];
连续分配了4个数组,其中3个是2MB的数组,1个是128KB的数组,如下图所示,全部会进入Eden区域中。
接着会执行如下代码:byte[] array6 = new byte[2 * 1024 * 1024];
。此时还能放得下2MB的对象吗?
不可能了,因为Eden区已经放不下了。因此此时会直接触发一次Young GC。
我们看下面的GC日志:
0.174: [GC (Allocation Failure) 0.174: [ParNew (promotion failed): 7457K->8328K(9216K), 0.0046949 secs]
这行日志显示了,Eden区原来是有7000多KB的对象,但是回收之后发现一个都回收不掉,因为上述几个数组都被变量引用了。
所以此时,一定会直接把这些对象放入到老年代里去,但是此时老年代里已经有一个4MB的数组了,还能放的下3个2MB的数组和1个128KB的数组吗?
明显是不行的,此时一定会超过老年代的10MB大小。
所以此时看gc日志:
0.179: [CMS: 8194K->6962K(10240K), 0.0033396 secs] 11553K->6962K(19456K), [Metaspace: 2970K->2970K(1056768K)], 0.0089224 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
此时执行了CMS垃圾回收器的Full GC,Full GC其实就是会对老年代进行Old GC,同时一般会跟一次Young GC关联,还会触发一次元数据区(永久代)的GC。
在CMS Full GC之前,就已经触发过Young GC了,此时可以看到此时Young GC就已经有了,接着就是执行针对老年代的Old GC,也就是如下日志:
CMS: 8194K->6962K(10240K), 0.0033396 secs
这里看到老年代从8MB左右的对象占用,变成了6MB左右的对象占用,这是怎么个过程呢?
很简单,一定是在Young GC之后,先把2个2MB的数组放入了老年代,如下图。
此时要继续放1个2MB的数组和1个128KB的数组到老年代,一定会放不下,所以此时就会触发CMS的Full GC。
然后此时就会回收掉其中的一个4MB的数组,因为他已经没人引用了,如下图所示。
所以再看CMS的垃圾回收日志:CMS: 8194K->6962K(10240K), 0.0033396 secs
,他是从回收前的8MB变成了6MB,就是上图所示。
最后在CMS Full GC执行完毕之后,其实年轻代的对象都进入了老年代,此时最后一行代码要在年轻代分配2MB的数组就可以成功了,如下图。
05.总结
这是一个触发老年代GC的案例,就是年轻代存活的对象太多放不下老年代了,此时就会触发CMS的Full GC。
2.老年代可用空间小于了历次Young GC后升入老年代的对象的平均大小
01.示例代码
public class DemoTest1 {
public static void main(String[] args) {
byte[] array1 = new byte[1 * 1024 * 1024];
array1 = null;
byte[] array2 = new byte[1 * 1024 * 1024];
array2 = null;
byte[] array3 = new byte[1 * 1024 * 1024];
array3 = null;
byte[] array4 = new byte[1 * 1024 * 1024];//触发YGC 1MB 1
array1 = new byte[1 * 1024 * 1024];
array1 = null;
array2 = new byte[1 * 1024 * 1024];
array2 = null;
array3 = new byte[1 * 1024 * 1024];//触发YGC Y 1MB O 1MB 2
array3 = null;
byte[] array5 = new byte[1 * 1024 * 1024];// Y 2MB O 1MB
array1 = new byte[1 * 1024 * 1024];// Y 3MB
array1 = null;
array2 = new byte[1 * 1024 * 1024];// Y 1MB O 2MB YGC 3
array2 = null;
array3 = new byte[1 * 1024 * 1024];//Y 2MB O 2MB
array3 = null;
byte[] array6 = new byte[1 * 1024 * 1024];//Y 3MB O 2MB
array1 = new byte[1 * 1024 * 1024];//Y 1MB O 3MB YGC 4
array1 = null;
array2 = new byte[1 * 1024 * 1024];//Y 2MB
array2 = null;
array3 = new byte[1 * 1024 * 1024];//Y 3MB
array3 = null;
byte[] array7 = new byte[1 * 1024 * 1024];//YGC 5
}
}
02.启动JVM参数
-XX:NewSize=5M -XX:MaxNewSize=5M -XX:InitialHeapSize=10M -XX:MaxHeapSize=10M -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=15 -XX:PretenureSizeThreshold=2M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:gc.log
其中,参数-XX:PretenureSizeThreshold
,参数要设置大对象阈值为2MB,也就是超过2MB,就直接进入老年代。
大对象大小是3MB。一旦对象大小超过3MB,不会进入新生代,直接进入老年代。
启动命令:
java -jar -XX:NewSize=5M -XX:MaxNewSize=5M -XX:InitialHeapSize=10M -XX:MaxHeapSize=10M -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=15 -XX:PretenureSizeThreshold=2M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:gc.log jvm-demo.jar
03.GC日志
启动之后就得到如下GC日志:
老年代
年轻代
Java HotSpot(TM) 64-Bit Server VM (25.151-b12) for windows-amd64 JRE (1.8.0_151-b12), built on Sep 5 2017 19:33:46 by "java_re" with MS VC++ 10.0 (VS2010)
Memory: 4k page, physical 16703268k(7221016k free), swap 23781156k(8613656k free)
CommandLine flags: -XX:InitialHeapSize=10485760 -XX:MaxHeapSize=10485760 -XX:MaxNewSize=5242880 -XX:MaxTenuringThreshold=15 -XX:NewSize=5242880 -XX:OldPLABSize=16 -XX:PretenureSizeThreshold=2097152 -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:SurvivorRatio=8 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseConcMarkSweepGC -XX:-UseLargePagesIndividualAllocation -XX:+UseParNewGC
0.121: [GC (Allocation Failure) 0.121: [ParNew: 3155K->512K(4608K), 0.0041165 secs] 3155K->766K(9728K), 0.0042644 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.125: [GC (Allocation Failure) 0.125: [ParNew: 3663K->0K(4608K), 0.0016667 secs] 3917K->1732K(9728K), 0.0017448 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.127: [GC (Allocation Failure) 0.127: [ParNew: 3142K->0K(4608K), 0.0013221 secs] 4875K->2756K(9728K), 0.0013592 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.129: [GC (CMS Initial Mark) [1 CMS-initial-mark: 2756K(5120K)] 4878K(9728K), 0.0004498 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.129: [CMS-concurrent-mark-start]
0.130: [GC (Allocation Failure) 0.130: [ParNew: 3146K->0K(4608K), 0.0005869 secs] 5902K->2756K(9728K), 0.0006362 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.131: [GC (Allocation Failure) 0.131: [ParNew: 3148K->0K(4608K), 0.0007974 secs] 5904K->3780K(9728K), 0.0008262 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
par new generation total 4608K, used 2207K [0x00000000ff600000, 0x00000000ffb00000, 0x00000000ffb00000)
eden space 4096K, 53% used [0x00000000ff600000, 0x00000000ff827f38, 0x00000000ffa00000)
from space 512K, 0% used [0x00000000ffa80000, 0x00000000ffa80000, 0x00000000ffb00000)
to space 512K, 0% used [0x00000000ffa00000, 0x00000000ffa00000, 0x00000000ffa80000)
concurrent mark-sweep generation total 5120K, used 3780K [0x00000000ffb00000, 0x0000000100000000, 0x0000000100000000)
Metaspace used 2976K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 330K, capacity 386K, committed 512K, reserved 1048576K
04.分析GC日志
(1).代码块1
先看如下代码:
byte[] array1 = new byte[1 * 1024 * 1024];
array1 = null;
byte[] array2 = new byte[1 * 1024 * 1024];
array2 = null;
byte[] array3 = new byte[1 * 1024 * 1024];
array3 = null;
byte[] array4 = new byte[1 * 1024 * 1024];
这段代码直接分配了4个1MB的数组,并且在第4个数组的时候,会因为新生代内存不足触发YGC。
此时内存分配如下:
对应如下GC日志:
0.121: [GC (Allocation Failure) 0.121: [ParNew: 3155K->512K(4608K), 0.0041165 secs] 3155K->766K(9728K), 0.0042644 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
此时,可以看到新生代就只剩512K的对象,这个奇怪的512KB的对象进入Survivor From区。
那么大小为1MB的数组对象去哪里呢?肯定不是这个奇怪的512KB的对象。
这1MB的数组首先肯定是准备进入Survivor From区,可是,在我们设置的JVM参数下,只有0.5MB,明显是不够分配的。根据JVM YoungGC的规则,Survivor区放不下GC之后存活的对象,直接进入老年代。
所以,1MB的数组对象是直接进入到老年代了。
此时,内存分配如下:
(2).代码块2
紧接这就是这块代码:
array1 = new byte[1 * 1024 * 1024];
array1 = null;
array2 = new byte[1 * 1024 * 1024];
array2 = null;
array3 = new byte[1 * 1024 * 1024];
这里再次创建了3个1MB的数组对象,并且会触发一次YoungGC;
对应 GC日志如下:
0.125: [GC (Allocation Failure) 0.125: [ParNew: 3663K->0K(4608K), 0.0016667 secs] 3917K->1732K(9728K), 0.0017448 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
此时,Young GC之后,新生代变成0KB,那么存活的大小为1MB的数组对象去哪里呢?
这1MB的数组首先肯定是准备进入Survivor From区,可是,在我们设置的JVM参数下,只有0.5MB,明显是不够分配的。根据JVM YoungGC的规则,Survivor区放不下GC之后存活的对象,直接进入老年代。
所以,1MB的数组对象是直接进入到老年代了。
之前看到的未知的对象512KB也进入到老年代,此时内存分配如下:
(3).代码块3
array3 = null;
byte[] array5 = new byte[1 * 1024 * 1024];
array1 = new byte[1 * 1024 * 1024];
array1 = null;
array2 = new byte[1 * 1024 * 1024];
这里再次创建了3个1MB的数组对象,并且会触发一次YoungGC;
对应的GC日志如下:
0.127: [GC (Allocation Failure) 0.127: [ParNew: 3142K->0K(4608K), 0.0013221 secs] 4875K->2756K(9728K), 0.0013592 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
此时内存分配如下:
(4).代码块4
array2 = null;
array3 = new byte[1 * 1024 * 1024];//Y 2MB O 2MB
array3 = null;
byte[] array6 = new byte[1 * 1024 * 1024];
array1 = new byte[1 * 1024 * 1024];
这里再次创建了3个1MB的数组对象,并且会触发一次YoungGC;并且在这儿,触发Young GC之前触发了一次CMS的Old GC,触发的条件就是老年代可用空间小于了历次Young GC后升入老年代的对象的平均大小。此时新生代大小变成0KB
对应的GC日志如下:
0.129: [GC (CMS Initial Mark) [1 CMS-initial-mark: 2756K(5120K)] 4878K(9728K), 0.0004498 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.129: [CMS-concurrent-mark-start]
0.130: [GC (Allocation Failure) 0.130: [ParNew: 3146K->0K(4608K), 0.0005869 secs] 5902K->2756K(9728K), 0.0006362 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
此时内存分配如下:
(5).代码块5
array1 = null;
array2 = new byte[1 * 1024 * 1024];//Y 2MB
array2 = null;
array3 = new byte[1 * 1024 * 1024];//Y 3MB
array3 = null;
byte[] array7 = new byte[1 * 1024 * 1024];
此时,再创建3个1MB的数组对象,再次触发一次Young GC,执行完YoungGC,此时新生代大小变成0KB;
对应的GC日志如下:
0.131: [GC (Allocation Failure) 0.131: [ParNew: 3148K->0K(4608K), 0.0007974 secs] 5904K->3780K(9728K), 0.0008262 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
此时内存分配如下:
(6).总结
如下GC堆内存日志我们也可以去验证下上面的推测:
此时新生代使用了53%的大小,我们还有一个1MB的数组,可能还存在一些未知对象。
在老年代中使用了大约3MB的空间,应该就是上图中的对象。
Heap
par new generation total 4608K, used 2207K [0x00000000ff600000, 0x00000000ffb00000, 0x00000000ffb00000)
eden space 4096K, 53% used [0x00000000ff600000, 0x00000000ff827f38, 0x00000000ffa00000)
from space 512K, 0% used [0x00000000ffa80000, 0x00000000ffa80000, 0x00000000ffb00000)
to space 512K, 0% used [0x00000000ffa00000, 0x00000000ffa00000, 0x00000000ffa80000)
concurrent mark-sweep generation total 5120K, used 3780K [0x00000000ffb00000, 0x0000000100000000, 0x0000000100000000)
Metaspace used 2976K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 330K, capacity 386K, committed 512K, reserved 1048576K
3.几个触发Full GC的条件
第一:是老年代可用内存小于新生代全部对象的大小,如果没开启空间担保参数,会直接触发Full GC,所以一般空间担保参数都会打开;注:jDK1.8之后已经取消了
-XX:-HandlePromotionFailure
机制第二:是老年代可用内存小于历次新生代GC后进入老年代的平均对象大小,此时会提前Full GC;
第三:是新生代Minor GC后的存活对象大于Survivor,那么就会进入老年代,此时老年代内存不足。
上述情况都会导致老年代Full GC。
第四:就是“-XX:CMSInitiatingOccupancyFaction”参数,
如果老年代可用内存大于历次新生代GC后进入老年代的对象平均大小,但是老年代已经使用的内存空间超过了这个参数指定的比例,也会自动触发Full GC。默认92%
结合代码和内存变化图一步步弄懂JVM的FullGC的更多相关文章
- 一文带你弄懂 JVM 三色标记算法!
大家好,我是树哥. 最近和一个朋友聊天,他问了我 JVM 的三色标记算法.我脑袋一愣发现竟然完全不知道!于是我带着疑问去网上看了几天的资料,终于搞清楚啥事三色标记算法,它是用来干嘛的,以及它和 CMS ...
- 一步步弄懂HTTPS
为什么要使用HTTPS? HTTP采用明文传输,存在被监听.内容被篡改.身份被冒充的可能.为了保证安全性,需要对数据进行加密,这便有了HTTPS. 一步步分析HTTPS 1. 采用哪种加密方式加密数据 ...
- 不是吧!做了两年java还没弄懂JVM堆?进来看看你就明白了
堆的核心概述 一个JVM实例只存在一个堆内存,堆也是java内存管理的核心区域Java堆区在jvm启动的时候被创建,其空间大小也就确定了.是jvm管理的最大一块内存空间.(堆内存的大小可以调节)< ...
- 阿里P8Java大牛仅用46张图让你弄懂JVM的体系结构与GC调优。
本PPT从JVM体系结构概述.GC算法.Hotspot内存管理.Hotspot垃圾回收器.调优和监控工具六大方面进行讲述.图文并茂不生枯燥. 此PPT长达46页,全部展示篇幅过长,本文优先分享前十六页 ...
- 46张PPT弄懂JVM、GC算法和性能调优!
来源:cnblogs.com/cyfonly/p/5807121.html 本PPT从JVM体系结构概述.GC算法.Hotspot内存管理.Hotspot垃圾回收器.调优和监控工具六大方面进行讲述. ...
- Golang, 以 9 个简短代码片段,弄懂 defer 的使用特点
作者:林冠宏 / 指尖下的幽灵 掘金:https://juejin.im/user/587f0dfe128fe100570ce2d8 博客:http://www.cnblogs.com/linguan ...
- Pythontutor:可视化代码在内存的执行过程
http://www.pythontutor.com/visualize.html今天去问开发一个Python浅拷贝的问题,开发给了一个神器,可以可视化代码在内存的执行过程,一看即懂,太NB了!~真是 ...
- 解密native代码的内存使用
前言 无论是从资源使用的角度,还是从发现内存泄漏问题的角度来看,在性能测试或者系统的稳定性测试中,内存的使用情况是一个很重要的监控点.为保证项目的质量前移,输入法内核测试小组的同学分配到了一个新的任务 ...
- c++ 汇编代码看内存分配
汇编代码看内存分配 (1). 程序运行时分为存储区域分为 存储区域 存储内容 extra 代码区 存放代码指令,包括除字符串常量的字面值 静态存储区 存放静态变量和全局变量 执行main之前就分配好了 ...
- 必须弄懂的495个C语言问题
1.1 我如何决定使用那种整数类型? 如果需要大数 值(大于32, 767 或小于¡32, 767), 使用long 型.否则, 如果空间很重要(如有大数组或很多结构), 使用short 型.除此之外 ...
随机推荐
- MongoDB数据库与Python的交互
一.缘由 这是之前学习的时候写下的基础代码,包含着MongDB数据库和Python交互的基本操作. 二.代码实现 import pymongo #连接数据库 client=pymongo.MongoC ...
- tcp/udp 协议特性和三次握手
一.TCP/UDP协议特性1)TCP特性:工作在传输层.建立连接.可靠的.错误检查 2)UDP特性:工作在传输层.不需要连接.不可靠的.有限的错误检查.传输性能高 2.控制位及确认号解释 控制位:由6 ...
- 为什么总是应该考虑给定 List 的初始大小
在 .Net 技术中,使用 List<> 来存储数据是很常见的.List<> 是一个可以动态增长的泛型集合类型,可以存储任何类型的数据. 但是,在实际使用中,很多人并不注意给定 ...
- Redis——02 学习
Redis--02 前面了解了 Redis 以及在 Linux 的安装,下面了解一些 Redis 常用的命令. Redis 常用命令: Redis 是 Key-Value 形式,Key 为字符串类型, ...
- django.db.migrations.exceptions.NodeNotFoundError: Migration apitest.0001_initial dependencies reference nonexistent parent node ('product', '0001_initial')
执行python manage.py makemigrations时出现以下错误 D:\autotestplat>python manage.py makemigrations Tracebac ...
- week_5
Andrew Ng机器学习笔记---by Orangestar Week_5 重点:反向传播算法,backpropagation 1. Cost Function神经元的代价函数 回顾定义:(上节回顾 ...
- 第一章 --------------------WPF基础概述
1.在使用WPF之前我一直在思考为什么要使用WPF? 主要原因在于我已经受够了MFC和Winform 和QT的界面设计.尤其是MFC的界面设计,使用一个界面库十分的复杂,并且我的绝大多数时间都是用在这 ...
- 浅谈promise对象
背景: 最近项目在做小程序的开发,涉及设计一个统一的登录公共方法,当实现时涉及到多个异步请求,那么问题来了,如何让多个异步请求先后同步进行呢?很多人会想到使用多层嵌套套来实现,就像这样: functi ...
- 如何用 30s 给面试官讲清楚什么是 Token
引言 前文介绍了 Session-Cookie 的认证过程,简单回顾下基本步骤: 客户端(浏览器)向服务器发送用户名和密码 服务器验证通过后,创建 Session 对象,在 Session 中保存该用 ...
- FFmpeg 解码内存泄漏汇总,sws_getContext函数无法释放问题
使用FFmpeg库做的项目,调试项目的时候发现,连续解视频进行播放,会有明显内存增加问题.连续工作10个小时后就会 被linux 内核kill掉. 通过逐步注掉代码和网上查阅资料.最后发现内存泄漏有一 ...