内存不足意味着当程序正在申请内存时,没有足够的内存空间供其使用,并且出现内存不足的情况;
内存泄漏内存泄漏表示程序在申请内存后无法释放分配的内存空间。内存泄漏的危害可以忽略,但是内存泄漏累积的后果非常严重。无论有多少内存,它迟早都会用完。
内存泄漏最终将导致内存不足!
按发生的方式分类,内存泄漏可以分为4类:
1.频繁的内存泄漏。具有内存泄漏的代码将被多次执行,并且每次执行都会导致内存泄漏。
2.零星的内存泄漏。具有内存泄漏的代码只能在某些情况或操作下发生。频繁和零星是相对的。对于特定的环境,偶尔可能会变得很频繁。因此,测试环境和测试方法对于检测内存泄漏至关重要。
3.一次内存泄漏。具有内存泄漏的代码将仅执行一次,或者由于算法中的缺陷,将始终存在一个和一个内存泄漏。例如,内存是在类的构造函数中分配的,但内存并未在析构函数中释放,因此内存泄漏只会发生一次。
4.隐式内存泄漏。程序在运行时会不断分配内存,但是直到结束时才释放内存。严格来说,没有内存泄漏,因为最终程序会释放所有请求的内存。但是对于服务器程序,它需要运行几天,几周甚至几个月。无法及时释放内存可能还会导致系统的所有内存最终耗尽。因此,我们将这种类型的内存泄漏称为隐式内存泄漏。

从使用该程序的用户的角度来看,内存泄漏本身不会造成任何危害。作为一般用户,根本没有内存泄漏。真正有害的是内存泄漏的累积,最终将耗尽系统的所有内存。从这个角度来看,一次内存泄漏不会造成危害,因为它不会累积,而隐式内存泄漏则非常有害,因为它比频繁和偶尔的内存泄漏更难检测。
一、 Java内存恢复机制
不管任何语言的内存分配方法如何,都必须返回分配的内存的实际地址,即返回指向该内存块的第一个地址的指针。 Java中的对象是使用新方法或反射方法创建的。这些对象的创建在堆中分配,Java虚拟机通过垃圾回收机制完成所有对象的回收。为了能够正确释放对象,GC将监视每个对象的运行状态,并监视它们的应用程序,引用,引用,分配等。Java将使用有向图方法来管理内存,并监视对象是否可以被释放。实时到达。 ,如果无法访问,则会将其回收,
二、 Java内存泄漏的原因
内存泄漏意味着无用的对象(不再使用的对象)继续占用内存,或者无法及时释放无用的对象的内存。由此造成的内存空间浪费称为内存泄漏。有时内存泄漏并不严重,也不容易检测,因此开发人员不知道内存泄漏,但有时可能很严重,并且系统会提示您内存不足。
那么,Java内存泄漏的根本原因是什么?持有对短期对象的引用的长期对象很可能导致内存泄漏。尽管不再需要短期生存的对象,但是由于长期生存的对象保留了其引用,因此无法将其回收。这是java场景中发生了内存泄漏。具体的主要类别如下:
1、静态集合类导致内存泄漏:

使用HashMap,Vector等最容易发生内存泄漏。这些静态变量的生命周期与应用程序一致。它们引用的所有对象都无法释放,因为它们将始终被Vector等引用。
Static Vector v = new Vector(10); for (int i = 1; i<100; i++) { Object o = new Object(); v.add(o); o = null; }//
在此示例中,循环申请Object对象,然后将所申请的对象放入Vector中。如果仅释放引用本身(o = null),则Vector仍将引用该对象,因此该对象是用于GC的,不可回收。因此,如果将对象添加到Vector,则必须将其从Vector中删除。最简单的方法是将Vector对象设置为null。
2、修改了集合中对象的属性后,remove()方法不起作用。
public static void main(String[] args) { Setset = new HashSet (); Person p1 = new Person("唐僧","pwd1",25); Person p2 = new Person("孙悟空","pwd2",26); Person p3 = new Person("猪八戒","pwd3",27); set.add(p1); set.add(p2); set.add(p3); System.out.println("总共有:"+set.size()+" 个元素!"); //结果:总共有:3 个元素! p3.setAge(2); //修改p3的年龄,此时p3元素对应的hashcode值发生改变 set.remove(p3); //此时remove不掉,造成内存泄漏 set.add(p3); //重新添加,居然添加成功 System.out.println("总共有:"+set.size()+" 个元素!"); //结果:总共有:4 个元素! for (Person person : set) { System.out.println(person); } }
3、 Listener
在Java编程中,我们都需要处理侦听器。通常,一个应用程序将使用许多侦听器。我们将调用诸如addXXXListener()之类的控制方法来添加侦听器,但是我们经常释放对象。那时,我不记得删除这些侦听器,这增加了内存泄漏的机会。

4、各种联系
例如,连接(dataSourse.getConnection()),网络连接(socket)和io连接,除非它显式调用其close()方法来关闭其连接,否则它将不会被GC自动回收。无法显式回收Resultsset和Statement对象,但是必须显式回收Connection,因为Connection不能在任何时间自动回收,并且一旦Connection被回收,Resultset和Statement对象将立即为NULL。但是,如果您使用连接池,情况将有所不同。除了显式关闭连接之外,还必须显式关闭Resultset语句对象(关闭其中一个,另一个也将关闭),否则将导致大量的Statement对象无法发布,从而导致内存泄漏。在这种情况下,通常是尝试建立连接,然后最终释放连接。
6、单人模式
如果单例对象持有对外部对象的引用,则JVM将通常不会回收该外部对象,从而导致内存泄漏。
如果单例对象拥有对外部对象的引用,那么JVM将不会正常回收该外部对象,从而导致内存泄漏
单例模式使用不当是导致内存泄漏的常见问题。初始化单例对象之后,它将在JVM的整个生命周期中存在(以静态变量的形式)。如果单例对象包含一个外部对象,那么JVM将不会正常回收该外部对象,从而导致内存泄漏。考虑以下示例:
class A{ public A(){ B.getInstance().setA(this); } .... } //B类采用单例模式 class B{ private A a; private static B instance=new B(); public B(){} public static B getInstance(){ return instance; } public void setA(A a){ this.a=a; } //getter... }
很明显,B采用单例模式,该模式持有对A对象的引用,并且该类型A的对象将不会被回收。想象一下,如果A是更复杂的对象或集合类型会发生什么
本文来自电脑杂谈,转载请注明本文网址:
http://www.pc-fly.com/a/shoujiruanjian/article-362163-1.html