Contents
  1. 1. 实例化的过程
  2. 2. java内存泄露
    1. 2.1. java如何管理内存
    2. 2.2. 什么是java的内存泄露
    3. 2.3. 几种典型的内存泄露以及解决办法
      1. 2.3.1. 全局集合
      2. 2.3.2. 缓存
      3. 2.3.3. 类装载器
    4. 2.4. 如何检测内存泄露
      1. 2.4.1. 检测内存泄露的方法
    5. 2.5. 如何处理内存泄露
    6. 2.6. 内存泄露发生的情况
      1. 2.6.1. 静态集合类
      2. 2.6.2. 集合里的对象属性值被改变
      3. 2.6.3. 监听器
      4. 2.6.4. 各种连接
      5. 2.6.5. 外部模块的引用
      6. 2.6.6. 单例模式

实例化的过程

对象的创建过程:

  1. 遇到new指令,检查类是否完成了加载、验证、准备、解析、初始化(解析过程就是符号引用解析成直接引用,比如方法名就是一个符号引用,可以在初始化完成后使用这个符号引用时候进行,真实为了支持动态绑定),没有完成先进行的这些过程;
  2. 分配内存,采用空闲列表或者指针碰撞的方法,并将新分配的内存“置零”,因此所有的实例变量在此环节都进行了一次默认初始化为0(引用为null)的过程。
  3. 执行方法,包括检查调用父类的方法(构造器),实例变量定义出的赋值动作,实例化器顺序执行,最后调用构造器中的动作。

java内存泄露

java如何管理内存

为了判断Java中是否有内存泄露,我们首先必须了解Java是如何管理内存的。Java的内存管理就是对象的分配和释放问题。在Java中,内存的分配是由程序完成的,而内存的释放是由垃圾收集器(Garbage Collection,GC)完成的,程序员不需要通过调用函数来释放内存,但它只能回收无用并且不再被其它对象引用的那些对象所占用的空间。

Java的内存垃圾回收机制是从程序的主要运行对象开始检查引用链,当遍历一遍后发现没有被引用的孤立对象就作为垃圾回收。GC为了能够正确释放对象,必须监控每一个对象的运行状态,包括对象的申请、引用、被引用、赋值等,GC都需要进行监控。监视对象状态是为了更加准确地、及时地释放对象,而释放对象的根本原则就是该对象不再被引用。

在Java中,这些无用的对象都由GC负责回收,因此程序员不需要考虑这部分的内存泄露。虽然,我们有几个函数可以访问GC,例如运行GC的函数System.gc(),但是根据Java语言规范定义,该函数不保证JVM的垃圾收集器一定会执行。因为不同的JVM实现者可能使用不同的算法管理GC。通常GC的线程的优先级别较低。JVM调用GC的策略也有很多种,有的是内存使用到达一定程度时,GC才开始工作,也有定时执行的,有的是平缓执行GC,有的是中断式执行GC。但通常来说,我们不需要关心这些。

什么是java的内存泄露

在Java中,内存泄漏就是存在一些被分配的对象,这些对象有下面两个特点。

  1. 首先,这些对象是有被引用的,即在有向树形图中,存在树枝通路可以与其相连;
  2. 其次,这些对象是无用的,即程序以后不会再使用这些对象。
    如果对象满足这两个条件,这些对象就可以判定为Java中的内存泄漏,这些对象不会被GC所回收,然而它却占用内存。

几种典型的内存泄露以及解决办法

全局集合

在大型应用程序中存在各种各样的全局数据仓库是很普遍的,比如一个JNDI-tree或者一个session table。在这些情况下,必须注意管理储存库的大小。必须有某种机制从储存库中移除不再需要的数据。

解决办法:

  1. 通常有很多不同的解决形式,其中最常用的是一种周期运行的清除作业。这个作业会验证仓库中的数据然后清除一切不需要的数据。
  2. 另一种管理储存库的方法是使用反向链接(referrer)计数。然后集合负责统计集合中每个入口的反向链接的数目。这要求反向链接告诉集合何时会退出入口。当反向链接数目为零时,该元素就可以从集合中移除了。

缓存

缓存一种用来快速查找已经执行过的操作结果的数据结构。因此,如果一个操作执行需要比较多的资源并会多次被使用,通常做法是把常用的输入数据的操作结果进行缓存,以便在下次调用该操作时使用缓存的数据。缓存通常都是以动态方式实现的,如果缓存设置不正确而大量使用缓存的话则会出现内存溢出的后果,因此需要将所使用的内存容量与检索数据的速度加以平衡。

解决办法:
常用的解决途径是使用java.lang.ref.SoftReference类坚持将对象放入缓存。这个方法可以保证当虚拟机用完内存或者需要更多堆的时候,可以释放这些对象的引用。

类装载器

Java类装载器的使用为内存泄漏提供了许多可乘之机。一般来说类装载器都具有复杂结构,因为类装载器不仅仅是只与”常规”对象引用有关,同时也和对象内部的引用有关。比如数据变量,方法和各种类。这意味着只要存在对数据变量,方法,各种类和对象的类装载器,那么类装载器将驻留在JVM中。既然类装载器可以同很多的类关联,同时也可以和静态数据变量关联,那么相当多的内存就可能发生泄漏。

如何检测内存泄露

如何查找引起内存泄漏的原因一般有两个步骤:第一是安排有经验的编程人员对代码进行走查和分析,找出内存泄漏发生的位置;第二是使用专门的内存泄漏测试工具进行测试。

  1. 第一个步骤在代码走查的工作中,可以安排对系统业务和开发语言工具比较熟悉的开发人员对应用的代码进行了交叉走查,尽量找出代码中存在的数据库连接声明和结果集未关闭、代码冗余等故障代码。
  2. 第二个步骤就是检测Java的内存泄漏。在这里我们通常使用一些工具来检查Java程序的内存泄漏问题。市场上已有几种专业检查Java内存泄漏的工具,它们的基本工作原理大同小异,都是通过监测Java程序运行时,所有对象的申请、释放等动作,将内存管理的所有信息进行统计、分析、可视化。开发人员将根据这些信息判断程序是否有内存泄漏问题。这些工具包括Optimizeit Profiler,JProbe Profiler,JinSight , Rational 公司的Purify等。

检测内存泄露的方法

  1. 我们持续地观察系统运行时使用的内存的大小和各实例的个数,如果内存的大小持续地增长,则说明系统存在内存泄漏,如果特定类的实例对象个数随时间而增长(就是所谓的“增长率”),则说明这个类的实例可能存在泄漏情况。
  2. 在应用程序中出现了OutOfMemoryError。
  3. 不间断地监控GC的活动,确定内存使用量是否随着时间增加

如何处理内存泄露

  1. 首先,Profiler会进行趋势分析,找出是哪个类的对象在泄漏。
  2. 接下来,看看有哪些其他的类与泄漏的类的对象相关联。
  3. 最后,进一步研究单个对象,看看它们是如何互相关联的。

内存泄露发生的情况

虽然Java自带垃圾回收机制(GC),程序员不需要手动进行内存管理,但是仍然会出现内存泄漏的情况。尽管如此,Java的自动内存管理,比起C/C++,内存泄漏的情况大大减少了。下面总结下什么情况下会发生Java内存泄漏。

静态集合类

在使用Set、Vector、HashMap等集合类的时候需要特别注意,有可能会发生内存泄漏。当这些集合被定义成静态的时候,由于它们的生命周期跟应用程序一样长,这时候,就有可能会发生内存泄漏,看下面代码:

‘’’java
class StaticTest
{
private static Vector v = new Vector(10);

public void init()
{
    for (int i = 1; i < 100; i++)
    {
        Object object = new Object();
        v.add(object);
        object = null;
    }
}

}
‘’’
在上面的代码中,循环申请了Object对象,并添加到Vector中,然后将对象设置为null,可是这些对象因为被Vector引用着,因此并不能被GC回收,因此造成了内存泄漏。因此,要释放这些对象,还需要被它们从Vector删除,最简单的方法就是将Vector设置为null。

集合里的对象属性值被改变

‘’’java
public static void main(String[] args)
{
Set set = new HashSet();
Student s1 = new Student(“Jack”);
Student s2 = new Student(“Mary”);
Student s3 = new Student(“Eason”);

set.add(s1);
set.add(s2);
set.add(s3);

System.out.println(set.size());//3
s2.setName("Jackson"); //修改属性,此时s2元素对应的hashcode值发生改变
set.remove(s2);        // remove不掉,造成内存泄漏
set.add(s2);           // 添加成功

System.out.println(set.size());//4

}
‘’’
在这个例子中,由于对象s2的属性值被改变了,因此不能从set中删除,所以set中会一直保持着s2的引用,不能被回收,造成了内存泄漏。

监听器

在Java中,我们经常会使用到监听器,如对某个控件添加单击监听器addOnClickListener(),但往往释放对象的时候会忘记删除监听器,这就有可能造成内存泄漏。好的方法就是,在释放对象的时候,应该记住释放所有监听器,这就能避免了因为监听器而导致的内存泄漏。

各种连接

Java中的连接包括数据库连接、网络连接和io连接,如果没有显式调用其close()方法,是不会自动关闭的,这些连接就不能被GC回收而导致内存泄漏。一般情况下,在try代码块里创建连接,在finally里释放连接,就能够避免此类内存泄漏。

外部模块的引用

调用外部模块的时候,也应该注意防止内存泄漏。如模块A调用了外部模块B的一个方法,如:
public void register(Object o)
这个方法有可能就使得A模块持有传入对象的引用,这时候需要查看B模块是否提供了去除引用的方法,如unregister()。这种情况容易忽略,而且发生了内存泄漏的话,比较难察觉,应该在编写代码过程中就应该注意此类问题。

单例模式

使用单例模式的时候也有可能导致内存泄漏。因为单例对象初始化后将在JVM的整个生命周期内存在,如果它持有一个外部对象(生命周期比较短)的引用,那么这个外部对象就不能被回收,而导致内存泄漏。如果这个外部对象还持有其它对象的引用,那么内存泄漏会更严重,因此需要特别注意此类情况。这种情况就需要考虑下单例模式的设计会不会有问题,应该怎样保证不会产生内存泄漏问题。

Contents
  1. 1. 实例化的过程
  2. 2. java内存泄露
    1. 2.1. java如何管理内存
    2. 2.2. 什么是java的内存泄露
    3. 2.3. 几种典型的内存泄露以及解决办法
      1. 2.3.1. 全局集合
      2. 2.3.2. 缓存
      3. 2.3.3. 类装载器
    4. 2.4. 如何检测内存泄露
      1. 2.4.1. 检测内存泄露的方法
    5. 2.5. 如何处理内存泄露
    6. 2.6. 内存泄露发生的情况
      1. 2.6.1. 静态集合类
      2. 2.6.2. 集合里的对象属性值被改变
      3. 2.6.3. 监听器
      4. 2.6.4. 各种连接
      5. 2.6.5. 外部模块的引用
      6. 2.6.6. 单例模式