美文网首页
内存溢出与内存泄露

内存溢出与内存泄露

作者: 水煮鱼又失败了 | 来源:发表于2020-05-22 13:05 被阅读0次

目录

[TOC]

1 内存泄露与内存溢出的区别

1.1 内存泄露

内存泄露(Memory Leak),指的是堆内存中被分配的对象无用处了,仍然GC ROOT可达,无法回收

简单来说,就是程序执行时时,临时对象已无用处了,按理对象需要被取消引用,但是对象仍然存在强引用,导致此部分内存属于无用内存,程序的内存中存在一部分无法被使用的内存

可以这么理解,泄露的意思就是丢了、没有了,即程序原有的内存缺少了一部分

1.2 内存溢出

内存溢出(out of memory),指的是程序执行过程中,无法申请到足够的内存,导致的错误。

即程序当前可用的空闲内存不够了,无法满足程序执行所需要的内存大小。如程序中需要缓存1000个对象到List中,需要占用10m内存,而当前可用内存只有5m,就会内存溢出。

溢出的意思,就是当前内存容量,无法满足程序执行需要的内存大小,超过程序的内存上限了

内存泄露,会损失掉程序的一部分内存,是内存溢出的诱因之一。

2 出现的场景

2.1 内存泄露出现的场景

2.2.1 更改对象哈希值运算相关的参数

将对象存在HashSet中时,一般会手动重写对象的equals方法和hashCode方法。

HashSet判断对象是否已存在时,会先。如果hashCode的计算结果,如果存在,再判断equals方法的执行结果。

如果hashCode的计算结果依赖了对象中的某个属性,人为地更改了HashSet中的此属性(比如判断用户是否存在时,通过userNunber计算了HashCode,此时更改了HashSet中的某个已有对象的userName),会导致此对象的前后Hash值不一样。如果通过contains来判断对象是否存在,只通过原来的对象contains来获取HashSet中的对象时,会导致被变更的对象,有可能永远无法被使用到(说明:如果通过对HashSet遍历的方式操作数据,不会造成内存泄露情况)。

个人觉得,此部分泄露情况,一般情况下可以忽略,如果更改了hashCode计算方式相关的属性,此对象在定义上就属于不同的对象了。通过对应的不同的对象来判断contains的时候,就可以得到数据了。
但是,如果在判断对象是否存在时,在前面缓存了已经存在的对象信息后,更改了影响hash计算的属性,再根据前面缓存的对象对hashSet中的对象进行操作,则会出现内存泄露情况。暂时能想到的此种情况只此一例,且不常见。

2.2.2 ThreadLocal使用不当导致内存泄露

​ TheadLocal指的是java中的线程变量,主要用于多线程操作一个变量时,每个线程存储一份变量的副本,各操作各的,最常见的是spring的声明式事务和mybatis的分页插件PageHelper

​ 以PageHelper为例,当手动调用分页插件代码,后面的mapper查询时,会自动分页。内部原理为,手动调用代码时会写入一份待消费的状态到当前线程中(通过ThreadLocal实现),如果后面执行mybatis的查询操作,则会自动实现分页,消费掉写入到TheadLocal中的待分页状态。
但是,如果在生成待消费数据和消费之前,执行了别的代码且代码执行出错了。导致当前操作失败,线程被回收到线程池中(如tomcat的线程池),然后此线程中仍然有未被消费的ThreadLocal的内容。
此时会有两个很坏的情况:

​ (1)回归线程池线程的TheadLocalMap中存在未被消费掉的分页属性,存在内存泄露

​ (2)如线程未被线程池销毁,当线程被再次使用时。正好遇到一个mybatis的非分页查询,线程中的分页插件信息将被消费掉,导致了不想分页查询,但是分页查询的情况,此种情况排查起来,非常难。因此建议在使用mybatis分页插件的时候,需要在调用分页插件后,紧接着执行查询代码,并将执行代码进行异常捕获,在finally块中,手动调用下分页插件的clear待消费数据的方法。

以上内存泄露情况,属于研发人员代码不规范导致的泄露情况,使用ThreadLocal还有一种特殊情况,会在正常操作的情况下,一定概率地情况下发生内存泄露。

以下情况,是线程会被线程池回收的情况下

在线程中有个theadLocals的类似Map的结构存储相关信息,key存储的是ThreadLocal变量本身,value存储的是ThreadLocal对应的在本线程中存的值。

其中,keye是弱引用(执行垃圾回收的时候会被回收),key只有在线程中还存在一份强引用的情况下,key才不会在垃圾回收的时候回收掉。如果key在线程中的强引用没有了,而且没有手动执行remove操作,key会在执行gc的时候被回收掉,但是,只有key被回收掉了,key对应的value还在。而此种情况,只有在对应的线程再次执行其他ThreadLocal的set/get/remove时,才会检查此线程对应的threadLocals中有没有key为null的,如果有,则会删除value的内容。

如果对应的线程一直不执行TheadLocals中的上述三个操作,对应的value会一直存在内存中,存在内存泄露问题。

此种情况,也可以避免,那就是使用ThreadLocal时,如果此变量无用了,需调用remove方法,移除无用的value。

2.2 内存溢出出现的场景

2.2.1 堆内存溢出

java的堆中主要是用来存放数组和对象的相关的jvm属性配置为:-Xms -Xmx

堆内存溢出(outOfMemoryError:java heap space)主要指的是Java堆中没有足够的空间去容纳创建的对象所需要的内存大小。

比如当前堆的总内存为8G,可用内存为5G,程序中创建了一个100万对象List,需要10G,5G小于10G,内存空间不够,则内存溢出。

堆内存溢出的情况,主要如下:

  • 查询数据库时数据较多的表时,查询条件中能筛选绝大部分数据的条件为空,且未采用游标等查询优化方式,将数据全部读取放到内存中。因此需要很大的堆内存,然后溢出。
  • 程序中一些操作,需要占用不至于溢出,但也不算少的内存时(比如一个申请,将需要500M的内存,但是系统中一共5G内存),当有12个请求一起发送到系统时,将会占用6G左右的内存,5G<6G,内存溢出。
  • 程序死循环,无限创建变量
2.2.2 方法区内存溢出

java中的方法区主要用来存储类信息、常量、静态变量等。相关的jvm属性配置为:-XX:PermSize、-XX:MaxPermSize

方法区内存溢出(outOfMemoryError:permgem space)一般情况下不会出现,除非是加载类太多,或者java动态代理、CGLIB使用不当导致

2.2.3 线程栈溢出

线程栈溢出(java.lang.StackOverflowError)主要指的是方法调用层级太多导致的溢出。

生产上很大一部分原因是maven配置依赖时,同一个gva被多个关联依赖,且每个关联依赖的版本不一致,在程序自动选择版本的时候,有一定几率出现此问题,且诡异的是,不同的系统环境,有的环境出现此问题,有的环境不出现此问题,甚至上线很长时间后,突然出现此问题。

相关文章

  • 内存溢出与内存泄露

    目录 [TOC] 1 内存泄露与内存溢出的区别 1.1 内存泄露 内存泄露(Memory Leak),指的是堆内存...

  • 内存优化

    内存优化主要是分析内存泄露和内存溢出。将从内存是怎么分配,内存怎么出现泄露和溢出,用工具判断什么情况出现泄露,找出...

  • Android性能优化 内存泄漏和内存溢出

    内存泄漏 内存溢出 常见的内存泄露场景 常见的内存溢出场景

  • JAVA内存区域

    首先解释下内存溢出和内存泄露之间的区别,为后面的学习做些铺垫: 1、内存溢出和内存泄露的区别和联系 内存溢出 ou...

  • JAVA内存区域

    首先解释下内存溢出和内存泄露之间的区别,为后面的学习做些铺垫: 1、内存溢出和内存泄露的区别和联系 内存溢出 ou...

  • Android面试 内存泄漏连环炮

    面试问题 什么是内存泄露,什么是内存溢出 什么情况下会造成堆溢出、栈溢出 常见造成内存泄露的情况 常见造成内存溢出...

  • JVM之内存模型

    Java内存内存区域图 内存泄露和内存溢出的区别 内存泄露是指分配出去的内存没有被回收回来 内存溢出是指程序所需要...

  • 内存溢出与内存泄露

    内存溢出一种程序运行出现的错误当程序运行需要的内存超过了剩余的内存时,就抛出内存溢出的错误 内存泄露占用的内存没有...

  • jvm2:Java内存溢出

    内存泄露和内存溢出的区别 内存溢出通俗理解就是内存不够了,不能分配足够大的内存。内存泄露就是程序中已动态分配的堆内...

  • Android基础知识(一)

    1、内存溢出和内存泄露有什么区别 内存溢出:应用的内存已经已经达到系统设置的最大值,进而导致崩溃 内存泄露:应用使...

网友评论

      本文标题:内存溢出与内存泄露

      本文链接:https://www.haomeiwen.com/subject/nsfbohtx.html