java内存泄露 内存溢出的区别,java内存泄漏和溢出的区别
如何解决写爬虫IP受阻的问题?立即使用。
本教程运行环境:windows7系统,java8版本8,DELL G3电脑。
1、内存泄漏memory leak :
表示程序申请内存后,无法释放申请的内存空间。一次内存泄漏看起来影响不大,但是内存泄漏累积后的后果就是内存溢出。
2、内存溢出 out of memory :
表示程序申请内存时,没有足够的内存供申请人使用。换句话说,如果给你一个存储空间来存储int类型的数据,但你存储的是long类型的数据,那么结果就是没有足够的内存。这时候就会报错OOM,这叫内存溢出。
3、二者的关系:
内存泄漏的累积最终会导致内存溢出。
内存溢出是指你想要的内存空间超过了系统实际分配给你的空间。此时系统相当于无法满足你的需求,会报错内存溢出。
内存泄漏是指你向系统申请分配内存使用(new),但使用后不归还(delete)。这样一来,你就无法访问你自己申请的内存(可能你把它的地址弄丢了),系统也无法再把它分配给需要的程序。相当于租一个柜子配一把钥匙。你存完东西锁柜子后,钥匙丢了或者不还。结果这个柜子谁都不能用,收垃圾的也不能回收,因为你找不到他的任何信息。
内存溢出:一个盘子经过各种方法只能装4个水果。你放5,它掉在地上,不能吃。这是溢出。比如当栈满时,将其推入栈中,必然会导致空间溢出,称为溢出。当堆栈为空时,将其推出也会导致空间溢出,称为下溢。即分配的内存不足以放下数据项序列,称为内存溢出。说白了,我承受不了这么多,就报个错。
4、内存泄漏的分类(按发生方式来分类)
经常性内存泄漏。有内存泄漏的代码会被执行几次,每次执行都会造成内存泄漏。
偶尔内存泄漏。内存泄漏的代码只会在某些特定的环境或操作过程中发生。复发和散发是相对的。对于特定的环境,偶尔可能会变成经常。因此,测试环境和测试方法对于检测内存泄漏非常重要。
一次性内存泄漏。有内存泄漏的代码只会执行一次,或者由于算法的缺陷,总会有一个且只有一个内存泄漏。比如内存是在类的构造函数中分配的,但是在析构函数中没有释放,所以内存泄漏只会发生一次。
隐式内存泄漏。程序在运行过程中不断分配内存,但直到最后才释放内存。严格地说,这里没有内存泄漏,因为最终的程序释放了所有请求的内存。然而,对于一个服务器程序来说,它需要运行几天、几周甚至几个月。不能及时释放内存也可能导致系统中所有内存的最终耗尽。因此,我们将这种内存泄漏称为隐式内存泄漏。
5、内存溢出的原因及解决方法:
(1) 内存溢出原因:
内存中加载的数据量太大,比如一次从数据库中取出的数据太多;
集合中有对对象的引用,使用后不清空,JVM无法回收;
代码中存在无限循环,或者循环生成的重复对象实体太多;
BUG在使用的第三方软件中;
启动参数记忆值设置太小。
(2)内存溢出的解决方案:
第一步是修改JVM启动参数,直接增加内存。(-Xms,-Xmx参数必须添加。)
其次,检查错误日志,查看在“OutOfMemory”错误之前是否有其他异常或错误。
第三步是遍历并分析代码,找出可能发生内存溢出的地方。
重点关注以下几点:
检查是否存在获取数据库查询中所有数据的查询。一般来说,如果一次取100,000条记录到内存中,可能会导致内存溢出。这个问题是隐藏的。在上线之前,数据库里的数据比较少,不容易出问题。上线后数据库中数据较多,一次查询就可能造成内存溢出。因此,数据库查询应该尽可能分页。
检查代码中是否有无限循环或递归调用。
检查是否存在重复生成新对象实体的大循环。
检查是否存在获取数据库查询中所有数据的查询。一般来说,如果一次取100,000条记录到内存中,可能会导致内存溢出。这个问题是隐藏的。在上线之前,数据库里的数据比较少,不容易出问题。上线后数据库中数据较多,一次查询就可能造成内存溢出。因此,数据库查询应该尽可能分页。
检查列表、地图等集合对象使用后是否未清除。像List和MAP这样的集合对象总是有对对象的引用,所以这些对象不能被GC回收。
步骤4:使用内存查看工具动态查看内存使用情况。
JVM8 内存模型
内存溢出的十个场景
在运行时,JVM首先需要类加载器来加载所需类的字节码文件。加载后交给执行引擎,执行过程中需要一个空间来存储数据(像CPU和主存)。分配和释放这个内存空间的过程正是我们需要关心的运行时数据区。加载类加载器时会发生内存溢出。内存溢出可以分为两类:OutOfMemoryError和StackOverflowError。下面列举10种内存溢出的情况,用示例代码解释内存溢出是如何发生的。
1.java堆内存溢出
当Java . lang . out memory error:Java heap space异常发生时,堆内存溢出。
1)、问题描述
当的jvm内存设置得太小、对象所需的内存太大以及创建对象时分配了空间时,将引发此异常。
流量/数据峰值,对应用本身的处理是有一定限制的,比如一定的用户数或者一定的数据量。但当用户数或数据量突然增加并超过预期阈值时,峰值停止前的正常操作会停止并触发Java . lang . out memory error:Java heap space error。
2)示例代码
编译以下代码,执行时jvm参数设置为-xms20 m-xm20m。
在上面的例子中,如果在一个请求中只分配了一次5m的内存,那么在少量请求的情况下,垃圾收集是正常的,不会有错误,但是一旦并发出现,就会超过最大内存值,并且会抛出内存溢出。
3.解决办法
首先,如果代码没有问题,可以适当调整-Xms和-Xmx jvm参数,使用压力测试将这两个参数调整到最优值。
其次,尽量避免大对象的应用,比如上传文件,从数据库中大量获取,这些都是需要避免的。尽量分块或者批量处理,有利于系统的正常稳定执行。
最后,尽量加快请求的执行速度,垃圾收集越早越好。否则,当大量并发请求到来时,新的请求将无法分配内存,容易导致系统的雪崩。
2、java堆内存泄漏
1)、问题描述
Java中的内存泄漏是应用程序不再使用某些对象,但垃圾收集无法识别它们的情况。因此,这些未使用的对象仍然无限期地存在于Java堆空间中。不断的积累最终会触发java。lang.OutOfMemoryError
2)示例代码
当执行上述代码时,可以预期它将永远运行,不会出现任何问题。假设简单的缓存解决方案只将底层映射扩展到10,000个元素,而不是HashMap中已经存在的所有键。然而,事实上,元素将继续被添加,因为key类没有覆盖它的equals()方法。
久而久之,随着泄露的代码不断被使用,“缓存”的结果最终会消耗大量的Java堆空间。当泄漏的内存填满堆区所有可用内存时,垃圾回收无法清理,java。lang.OutOfMemoryError
3)解决方案
相应的解决方案相对简单:重写equals方法:
3.垃圾回收超时内存溢出
1)、问题描述。当应用程序用尽所有可用内存,GC开销限制超过错误,GC多次清除失败,那么就会引发java.lang.OutOfMemoryError。当jvm花费大量时间执行GC却收效甚微时,一旦GC的整个进程超出限制,就会触发错误(默认JVM花费98%以上的时间配置GC,收集不到2%的堆内存)。
2)示例代码
3)解决方案
为了缩短对象的生命周期,应该尽可能快地进行垃圾收集。
4.Metaspace内存溢出
1)、问题描述
元空间溢出,系统会抛出Java . lang . out of memory error:metaspace。出现这种异常问题的原因是系统中的代码太多或者引用的第三方包或者动态代码生成、类加载等方法太多,导致对元空间的内存占用较大。
2)示例代码
3)解决方案
默认情况下,元空间的大小仅受本地内存的限制。但是为了整机的性能,这一项要尽量设置,以免造成整机的服务关闭。
优化参数配置,避免影响其他JVM进程。
-XX:MetaspaceSize,初始空间大小。当达到该值时,将为类型卸载触发垃圾回收。同时,GC会对这个值进行调整:如果释放了大量的空间,会适当减小这个值;如果释放了少量空间,请在不超过MaxMetaspaceSize时适当增加该值。
-XX:MaxMetaspaceSize,最大空间,默认情况下无限制。
除了上面两个指定大小的选项,还有两个GC相关的属性:-XX:MinMetaspaceFreeRatio,是GC后Metaspace最小剩余空间容量的百分比,减少空间分配导致的垃圾收集。-XX:MaxMetaspaceFreeRatio,GC后最大元空间剩余空间容量的百分比,这减少了因释放空间而导致的垃圾收集。
谨慎引用第三方包。
对于第三方套餐,一定要慎重选择,去掉不必要的套餐。这不仅有助于加快编译和打包,还有助于加快远程部署。
关注动态生成类的框架
对于使用大量动态生成类的框架,要做压力测试,验证动态生成的类是否超过内存需求,会抛出异常。
5、直接内存内存溢出
1)、问题描述
在ByteBuffer中使用allocateDirect()时会用到它。很多javaNIO(像netty)框架都是封装成其他方法的,出现这种问题时会抛出Java . lang . out memory error:direct buffer内存异常。
如果直接或间接使用ByteBuffer中的allocateDirect方法,但不做clear,也会出现类似的问题。
2)示例代码
3)解决方案
如果经常有类似操作,可以考虑设置参数:-XX:MaxDirectMemorySize,及时清空内存。
6、栈内存溢出
1)、问题描述
当一个线程执行一个Java方法时,JVM将创建一个新的堆栈框架,并将它推到堆栈的顶部。此时,新的堆栈帧成为当前堆栈帧。当执行该方法时,堆栈帧用于存储参数、局部变量、中间指令和其他数据。
当一个方法递归调用自身时,新方法生成的数据(也可以理解为新的栈帧)会被推到栈顶。每次方法调用自己时,都会复制当前方法的数据,并推送到堆栈中。因此,每一层递归调用都需要创建一个新的堆栈帧。结果,堆栈中越来越多的内存将被递归调用所消耗。如果你递归调用自己一百万次,就会产生一百万个堆栈帧。这将导致堆栈的内存溢出。
2)示例代码
3)解决方案
如果程序中有递归调用,当堆栈溢出时,可以增加-Xss的大小,这样可以解决堆栈内存溢出的问题。递归调用可以防止无限循环的形成,否则会发生堆栈内存溢出。
7、创建本地线程内存溢出
1)、问题描述
基本上线程只占用堆之外的内存区域,也就是这个错误说明除了堆之外不可能给线程分配内存区域。要么是内存本身不够用,要么是堆的空间设置太大,导致剩余内存不多,不够用是因为线程本身要占用内存。
2)示例代码
3)解决方案
首先检查操作系统是否有线程数量限制,不能用shell创建线程。如果这是问题所在,您需要调整系统可以支持的最大文件数量。
在日常开发中尽量保证可以控制线程的最大数量,不要随意使用线程池。不能无限增长。
8、超出交换区内存溢出
1)、问题描述
在Java应用程序启动期间,指定的所需内存可以由-Xmx和其他类似的启动参数来限制。但是,当JVM请求的总内存大于可用的物理内存时,操作系统开始将内容从内存转换到硬盘。
一般来说,JVM抛出交换空间不足错误,是指当应用程序给JVM原生堆分配内存失败,原生堆即将用完时,错误消息包含分配失败的大小(以字节为单位)和请求失败的原因。
2)解决方案
增加系统交换区的大小。个人认为如果使用交换区,性能会大打折扣。不建议采用这种方法。生产环境应该尽量避免最大内存超过系统的物理内存。其次,去掉了系统交换区,只使用系统的内存来保证应用的性能。
9、数组超限内存溢出
1),问题的描述有时会遇到这种内存溢出描述请求的阵列大小超过虚拟机限制。一般来说,java对一个应用程序可以分配的数组的最大大小有限制,但这个限制因平台而异,但通常在100到21亿个元素之间。当请求的数组大小超过虚拟机限制时,会出现错误,这意味着应用程序正在尝试分配一个大于Java虚拟机所支持的数组。在JVM为数组分配内存之前,它将执行特定于平台的检查:所分配的数据结构在这个平台上是否是可寻址的。
2)示例代码
下面的代码是数组超过了最大限制。
3)解决方案
因此,数组长度应该在平台允许的长度范围内。不过这种错误一般比较少见,主要是因为Java数组的索引是int类型。Java中最大的正整数是2 ^ 31-1=2,147,483,647。特定于平台的限制可能非常接近这个数字。比如我的环境(64位macOS,运行Jdk1.8),初始化数组的长度可以高达2147483645(integer . max _ value-2)。如果数组的长度增加1到整数。MAX_VALUE-1,将出现OutOfMemoryError错误。
10、系统杀死进程内存溢出
1)问题概述。在描述问题之前,先熟悉一下操作系统的知识:操作系统基于进程的概念,在内核中运行,有一个非常特殊的进程叫做“内存不足杀手”。当内核检测到系统内存不足时,OOM killer被激活,检查当前谁占用的内存最多,然后杀死进程。
一般内存不足:当可用的虚拟内存(包括交换空间)被消耗,从而使整个操作系统处于危险之中时,将触发kill process或sacricechild错误。在这种情况下,OOM黑仔会选择“流氓进程”并杀死它。
2)示例代码
3)解决方案
虽然增加交换空间可以缓解java堆空间异常,但是建议最好的解决方法是升级系统内存,让Java应用有足够的内存可用,不会出现这类问题。
通过总结以上10种内存溢出情况,大家在实际遇到的时候就知道怎么解决了,在实际编码中记住:
应该谨慎引入第三方jar包,坚决去掉无用的jar包,提高编译速度和系统的内存占用。
对于大型对象或者大量内存的应用,应该进行优化,将大型对象分片处理,提高处理性能,减少对象的生命周期。
尽量固定线程数量,确保线程占用的内存可以控制。同时,当需要大量线程时,应该优化操作系统中可以打开的最大连接数。
对于递归调用,递归的层次也要控制,不能太高,超过栈的深度。
分配给堆栈的内存并不是越大越好,因为堆栈内存越大,线程越多,留给堆栈的空间越少,越容易抛出OOM。JVM的默认参数一般没有问题(包括递归)。
相关视频教程推荐:java视频教程以上是Java中什么是内存泄漏和内存溢出的详细介绍。请多关注我们的其他相关文章!