根据
java中的内存是由jvm管理的,而垃圾收集是由gc负责的,所以一般情况下不会出现内存泄漏的情况,所以很容易被大家忽略。 内存泄漏是指无用的对象(不再使用的对象)继续占用内存或者无用的对象的内存不能及时释放,造成内存空间的浪费称为内存泄漏。 内存泄漏有时并不严重且难以检测,从而导致开发人员不知道存在内存泄漏,需要独立观察。 更严重的时候,没有内存可以分配,直接oomed。 主要和溢出是有区别的。
内存泄漏
烫发/披露
堆泄漏
更常见的内存泄漏
静态集合类导致监听器内存泄漏:但在释放对象时往往不记得删除这些监听器,从而增加了内存泄漏的机会。 各种连接、数据库、网络、IO、外部模块等内部类的引用:内部类的引用比较容易忘记,而且一旦不释放,后续的一系列类对象也可能不会被释放。 -静态内部类会隐式强引用其外围对象,因此当内部类不释放时,外围对象也不会被释放,从而导致内存泄漏。 单例模式:单例模式使用不当会导致内存泄漏一个常见问题,单例对象在初始化后(以静态变量的形式)会存在于JVM的整个生命周期中,如果单例对象持有一个引用外部对象,那么外部对象将不会被jvm正常回收,其他第三方类会导致内存泄漏
这个例子(线程泄漏)
本案现象
内存使用率达到80%+左右,并且持续上升,最高达到94%
更常见的是,当内存比较高的时候,伴随着
线程数量比较多,最高达到2w+(这个比较重要,可惜后面会关注这个)
日志中伴随大量异常,主要是三类错误
调用翻译接口识别语言服务错误
对接算法提供的二方包请求错误
一开始就走错了弯路
一开始我发现机器内存占用比较大,超过80%+。 这时候我在思考内存相关的逻辑,并没有观察此时的线程数。 根据现象1、2、4,这个过程中我并没有发现现象3,排查无果。 最后,经过重新定位问题,我们发现了现象3。由于现象4的错误日志较多,内存占用较高,所以我们想到了如下思路(因为本例中很多服务都是通过mq消费来启动的)
解决弯路中的疑惑
回到正常的处理逻辑
登录相关机器
top,观察内存使用率(这里的图是重启一段时间后),但是cpu使用率比较高,很快就会下降。 这里有一个延迟,top -Hp pid,确认线程使用率很高,读取一下对应的线程在做什么?
确认螺纹是否有规定尺寸,如果没有找到规定,则使用默认值
查看堆、gc状态
查看线程的状态,但是可以发现线程很多,而且可以定位,但是为了方便,我转储了一条数据来详细观察堆栈
cat /proc/{pid}/ (有这么多线程)
由于线程数量较多,仍然可以创建。 要查看普通Linux用户允许的进程数,使用命令:cat /etc//.d/90-nproc.conf,对比该值,远远超过了当前的数量。
线程信息
线程状态
找到问题线程
==》==》如图,无法直接定位代码块,所以maven定位引用该jar的服务==>具体的二方包。 如果不是每次都终止新线程,则gc中的线程就是根节点。 如果线程没有终止,则不会被回收。 因此,如果创建大量正在运行的线程,内存占用就会增加,但是在线可以创建多少个线程呢? 呢绒?
问题代码块
方法启动(每次初始化一个新的客户端,使用底层封装,使用NIO模型,初始化包括一个boss,10个工作线程)
方法结束(最后调用所有方法)
根据现象和对应的线程堆栈信息,可以判断线程此处溢出。 客户端关闭线程池的方法无效。 导致初始线程处于NIO模式,并没有被终止,所以线程的积压一直在增加,可以修改为单实例。 模式,限制系统使用线程池。