Posted on 2015-06-08 19:13
Justfly Shi 阅读(3967)
评论(4) 编辑 收藏 所属分类:
随便写写
感冒了,在家里休息,打开电脑随便看看,想起前两天有人问的一个事情:“内存溢出怎么分析和处理?”方案有很多了,基本上的思路就是获取系统状态,内存变化方向,内存对象等之类的,profile,debug,jmx,dump等等。
我更想说的是,为什么会内存溢出呢?
在我看来,干活有两种方式:
- 没想清楚了,贸贸然开干,然后各处救火各种解决问题
- 想清楚了再开干,无惊无险,安然做完
一般来说,我都是后者,所以真的很少碰到各种莫名其妙的问题,比如自己实现排序算法、在内存中处理有100000个值的列表、不用第三个变量来交换两个变量的值等等。
怎么避免内存溢出
吐槽完毕,来说说对于内存溢出这种事情是要怎么避免,我所谓的“想清楚了再开干”到底是怎么想清楚的。
首先内存溢出的本质是什么?“内存使用超出了预期”。
那么要怎么避免呢?“预期内存怎么使用,将其控制在内存使用范围呢”。
如何预期内存的使用
Java程序中,内存是被怎么占用的?被数据和对象占用的,数据和对象怎么来的?
- 应用的输入和输出
- 第三方系统的输入和输出
- 应用本身产生的数据
如何控制内存的使用
应用输入和输出怎么控制
- 控制允许输入的线程数,比如允许同时多少个线程提供服务
- 控制输入的请求对象的大小和内容,比如输入时的所允许的缓冲区大小
- 输出的线程数是和输入的线程对应的,如果是主动输出的,那么就控制一下
- 输出的服务对象的大小和内容,比如你是文件服务器,那么设置一个输出缓存,每10K就Flush一下。
第三方系统的输入和输出怎么控制
思路和应用输入和输出怎么控制所说的是一样的,控制线程数,使用缓存控制。
应用本身产生的数据怎么控制
思路也是一样的,线程数,缓存,再加上生存时间,对象池等等。
还有?
使用上述技巧和思路你就能控制好你应用中的内存了,但是所有的设计都是在风险和满足需求之间的平衡,如果再退一步,那么你需要考虑一下Java虚拟机中内存各个区的使用了。你需要区分好哪些是常驻对象,哪些是临时对象,新生代,旧生代,怎么安排你的虚拟机中各个内存区的大小。希望你不需要用到,如果需要的话,可以看看《深入理解Java虚拟机》这本书。
来个例子
有这么一个应用,它获取客户端的请求,验证请求的合法性,然后对于合法的请求,从第三方系统获取文件内容,并把文件内容写回给客户端。
下面是一大片的空白,再滚动下去之前看我的设计之前,读者可以就这个需求想想你会怎么处理。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
应用本身的的输入和输出的控制
- 同时服务数设置为可配置的属性,来控制并行的服务数
- 请求只允许Get请求,请求内容只有Header和URL,设置一个输入Buffer
- 输出,设置输出缓存区的大小,可配置的,默认情况下,每20K Flush一下。
第三方系统的输入和输出
第三方系统有两个,一个是合法性验证的,一个是文件内容的。
合法性验证的请求和输出的内容都比较小。但是考虑到其服务器的性能,把合法性验证的交互过程放到一个线程池中控制。这样能避免合法性验证服务不过来的情况。
文件内容第三方系统的输入和输出的控制
- 和合法性验证一样,文件内容第三方系统的输入和输出能力有限,将其访问用线程池控制起来。
- 文件内容第三方系统的请求访问对象较小,不予控制
- 整个系统的最重点来了!文件内容第三方系统的返回文件大小不可控制。所以使用缓存机制,每次读入20K(可配置),然后写给系统的客户端,然后再读20K,然后再写,让文件内容从这个系统中像水一样流过去。流不动了(第三方系统连接或者客户端连接失败)就让本次服务失败。这个流过去就是重点了,不管这个文件多大,每次请求只占用20K。
应用本身产生的数据
最原始的需求中还有一个把文件内容缓存在本地,然后可以多次写给客户端的这么一个想法,减少网络等待。这个想法被我否决了,原因如下:
- 首先可以通过HTTP 304状态码来减少同一客户端对同样内容的多次读取。
- 如果不对这个文件内容进行管理,将导致硬盘空间溢出。如果对其管理,会添加很多的开发、运维和设计的工作,得不偿失。
总结
想清楚了再开干就是这个意思,花点时间系统的想想,想清楚了,前面多写点代码,多写点单元测试,后面少点麻烦,不用救火,也不用加班。
吃饭去了。
再多啰嗦一句,《JUnit收费课程》的第二节的材料已经准备好了,昨晚担心自己的塞着鼻子,大家听着难受,没有录。今天好多了,晚上录一下,明天上午编辑审核,明天下午或者晚上大家应该就能看到了。