内存泄露这意味着内存在程序中间动态分配,但在程序结束时没有释放这部分内存,导致这部分内存不可用。此外,当指向这个内存空间的指针不再存在时,这个内存将永远无法实现,内存空间将被侵蚀一点点。简单地说,它是一个不再被程序使用的对象或变量仍然占据内存中的存储空间。在 Java 在语言中引入了垃圾回收机制,垃圾回收器负责回收不再使用的对象。既然有垃圾回收器负责垃圾回收,还会有吗?java内存泄漏的问题呢?
让我们先了解一下java内存垃圾回收机制:无论哪种语言的内存分配方式,都需要返回分配内存的真实地址,即将指针返回到内存块的第一个地址。Java中的对象是通过new或反射创建的,这些对象都是堆积的(Heap)Java虚拟机通过垃圾回收机制完成了所有对象的回收。为了正确释放对象,GC将监控每个对象的运行状态,并监控其应用、引用、引用、赋值等状态。Java将使用图形方法来管理内存,并实时监控对象是否可以实现。如果没有,它将被回收利用。
事实上,在 Java 在语言中,有两个标准可以判断内存空间是否符合垃圾回收:一是给对象空值 null,以后再也没用过;第二,给对象新的价值,重新分配内存空间。一般来说,内存泄漏主要有两种情况:一种是堆中的应用空间没有释放;另一种是对象不再使用,但仍保留在内存中。垃圾回收机制的引入可以有效地解决第一种情况;在第二种情况下,垃圾回收机制不能保证不再使用的对象被释放。因此,Java 语言中的内存泄漏主要是指第二种情况。
但是,内存泄漏可以分为以下几类4类:
1. 常见的内存泄漏。内存泄漏的代码会多次执行,每次执行都会导致内存泄漏。
2. 偶尔的内存泄漏。内存泄漏的代码只发生在某些特定的环境或操作过程中。常见性和偶然性是相对的。对于特定的环境,偶然性可能会变得常见。因此,测试环境和测试方法对检测内存泄漏至关重要。
3. 隐藏内存泄漏。程序在运行过程中不断分配内存,但直到最后才释放内存。严格地说,这里没有内存泄漏,因为最终程序释放了所有应用程序的内存。但对于一个服务器程序来说,它需要运行几天、几周甚至几个月。不及时释放内存也可能导致系统的所有内存最终耗尽。因此,我们称这种内存泄漏为隐藏内存泄漏。
4.一次性内存泄漏。内存泄漏代码只执行一次,或由于算法缺陷,只有一个内存泄漏。例如,内存分布在类的结构函数中,但内存没有在分析函数中释放,因此内存泄漏只发生一次。
从用户使用程序的角度来看,内存泄漏本身不会造成任何伤害。作为普通用户,他们根本感觉不到内存泄漏的存在。真正有害的是内存泄漏的积累,这最终会消耗系统中的所有内存。从这个角度来看,一次性内存泄漏没有危害,因为它不会积累,而隐藏的内存泄漏非常有害,因为它比常见和偶尔的内存泄漏更难检测到。
然后问题随之而来:在什么情况下会发生?Java内存泄漏怎么样?长生命周期对象持有短生命周期对象的引用很可能会导致内存泄漏。虽然短生命周期对象不再需要,但由于长生命周期对象持有其引用,无法回收,这是Java内存泄漏的场景。主要有以下类别:
1、内存泄漏是由静态集合引起的:
像HashMap、Vector等的使用最容易发生内存泄漏。这些静态变量的生命周期与应用程序一致,它们引用的所有对象Object不能释放,因为它们将被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对象最简单的方法是将Vector对象设置为null。
2、当集合中的对象属性被修改后,再次调用remove()方法时,就不起作用了。
public static void main(String[] args)
{
Set set = 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()+" 个元素!"); //结果:总共有:33 个元素!
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、监听器
在java 在编程中,我们都需要处理监听器,通常在一个应用程序中使用大量的监听器,我们会调用addxxlistener()等控制器来添加监听器,但通常在释放对象时不记得删除监听器,从而增加了内存泄漏的机会。
4、各种连接
例如数据库连接(dataSourse.getConnection(),网络连接(socket)与io连接,除非其显式调用其close()方法关闭其连接,否则不会自动被GC连接 回收的。Resultsetset 以及Statemente 对象不能显式回收,但Conection 一定要显式回收,因为Conection Conection一旦回收,任何时候都不能自动回收,Resultset 以及Statemente 对象将立即成为NULL。但是,如果使用连接池,情况会有所不同。除了显式关闭连接外,Resultsetset还必须显式关闭 Statement 对象(关闭其中一个,另一个也会关闭),否则会导致大量的Statementententent。 对象无法释放,导致内存泄漏。在这种情况下,try中的连接通常会在finally中释放。
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采用singleton模式,持有A对象引用,A类对象不能回收。
综上所述,虽然我们有java避免内存垃圾回收机制java内存泄漏,但在java编程过程中,内存泄漏是不可避免的。因此,在编程过程中,应尽快释放无用对象的参考,尽量少使用静态变量,使用对象池技术提高系统性能,避免内存泄漏。