12

如何防止内存泄露

 3 years ago
source link: http://javakk.com/1010.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

在本文中,我们将全面概述Java世界中的内存泄漏,以及防止它们的主要方法。

与许多人的想法相反,用Java编写的应用程序确实会出现内存泄漏问题。不幸的是,大量java程序员认为内存泄漏是C++的一部分,java垃圾收集器完全解决了这个问题。在本文中,我打算说明虽然垃圾收集器工作得很好,但它不能发挥神奇的作用。

内存泄漏的意思正是它的名字所说的:内存泄漏。它可以有两种类型:

  1. 内存块:已分配并可供应用程序使用,但不可访问,因为应用程序没有指向此内存区域的指针*。因此,这些存储器块既不能用于应用程序,也不能用于其他处理。
  2. 内存块:具有可以释放的数据,因为它们不可访问,并且(因为它们被遗忘了)即使没有被使用,仍然在代码中被引用,并且不能被释放。

在C/C++中,情况1是很常见的。例如,使用 malloc* 函数分配一个内存量,在这个序列中,程序员使指向该区域的指针pass memory指向另一个位置,从而丢失初始引用。在Java中,这种类型的内存泄漏不会发生,因为垃圾收集器能够检测已分配和未引用的内存块,并释放它们以供以后使用。

问题出在情况2。但是,在解释这个问题是如何发生以及如何防止它之前,我们必须对Java垃圾收集器的工作原理有一点了解。

*1指针是指向内存地址的变量。它是C/C++中非常常见的术语,认为java没有指针是错误的。使用new创建对象时,接收该对象的变量实际上是指向包含该对象的内存地址的指针。

*2 malloc() 函数是C API的一部分。它的功能是分配所需的内存量(作为参数提供)。此函数的返回是指向新创建的内存区域的指针。

垃圾收集器的角色

Java编程语言在较低级别上的一大优点是存在垃圾收集器。它的功能是清理已分配块后面的内存,这些块不再需要被应用程序引用。当应用程序再次使用垃圾回收器时,它将面临这种情况。

看看为什么Java中垃圾收集器的工作方式非常简单。注意清单1中的代码。

清单1。主方法示例。

public static void main(String[] args) throws Exception { 
	int[] array = null; 
	while(true) { 
		array = new int[1000]; 
		System.out.println("Free Bytes : " + Runtime.getRuntime().freeMemory()); 
		Thread.sleep(200); 
	} 
}

这段代码正在无休止的循环中运行。循环的每一次迭代,都会创建一个包含1000个整数位置的数组。每次执行循环时,都会显示空闲字节的数量。一开始您可能会想:这个应用程序最终会得到JVM的内存吗?事实上,情况并非如此。当您在任何给定的时间运行该程序时,您将看到清单2中所示的以下输出。

清单2。程序输出。

Free Bytes: 1530520 
Free Bytes: 1530520 
Free Bytes: 1530520 
Free Bytes: 1526504 
Free Bytes: 1522488 
Free Bytes: 1518472 
Free Bytes: 1514456 
Free Bytes: 1510440 
Free Bytes: 1912792 
Free Bytes: 1912792 
Free Bytes: 1908776 
Free Bytes: 1904760 
Free Bytes: 1900744 
Free Bytes: 1896728 
Free Bytes: 1892712

内存在运行时突然增加了。对此的解释很简单。执行循环时,将创建数组。在循环的下一次迭代中,上一次迭代中创建的数组不会被其他任何人引用(请注意,数组变量停止指向旧数组,并继续指向新数组,从而使旧数组无法访问)。在某一点上,JVM会看到可用内存的减少,并让垃圾收集器运行。然后,垃圾回收器发现在循环中分配的内存不可访问并释放它。在这一点上,我们可以看到可用内存的增加。

在看到这段代码运行之后,您一定在想:我怎么知道垃圾回收器什么时候会运行?你不知道。垃圾回收器的执行控制来自JVM。JVM决定何时运行。请注意,不建议一直运行垃圾收集器,因为它会占用计算机资源。

关于垃圾收集器的另一个重要点是,由于它由JVM控制,所以不可能以编程方式强制执行它。最能做的就是调用 System.gc() 方法(或 Runtime.getRuntime().gc() )。此方法通知JVM应用程序希望执行垃圾回收器,但并不保证它将在所需的时间实际执行。

finalize方法

Java对象类有一个名为 finalize() 的方法,它可以被继承自Object的类(即任何类)重写。当垃圾回收器决定某个特定的对象由于没有被更多引用而被销毁时,它会在销毁该对象之前对该对象调用 finalize() 方法。

尽管 finalize() 方法是程序员在销毁对象时释放与该对象相关联的资源的机会,但完全不建议重写 finalize() 。原因很简单:由于不能保证对象被销毁,也不能保证 finalize() 将运行。Oracle本身不建议重写 finalize() 方法。资源的释放可以通过其他方式实现,例如使用块 try/catch/finally 和/或为此目的建立特定的方法(例如 close() 方法)。

导致Java内存泄漏

在理解了什么是内存泄漏以及Java垃圾收集器的工作原理之后,我们现在将了解如何在Java中引起内存泄漏以及如何避免它。首先,我想强调内存泄漏很难发现(有时需要借助外部工具),并且总是由编程错误引起的。通常程序员不太关心它们,直到他们开始消耗过多的内存,甚至颠覆JVM(当内存用完时,JVM抛出一个 java.lang.OutOfMemoryError 和结束)。

正如我们看到的,垃圾收集器能够检测到非引用对象并销毁它们,从而释放内存。要创建内存泄漏,只需在代码中保留对一个或多个对象的引用,即使以后不使用它。因此,垃圾回收器永远无法销毁对象,它们将继续存在于内存中,即使不具有更高的可访问性。请看清单3中堆栈的这个简单示例实现。

清单3。堆栈的示例。

public class Stack { 
	private List stack = new ArrayList(); 
	int count = 0; 
	public void push(Object obj) { 
		pilha.add(count++, obj); 
	} 
	public Object pop() { 
		if(count == 0) { 
			return null; 
		} 
		return pilha.get(--count); 
	} 
}

每次在堆栈中放置或移除元素时,计数器都会控制最后一个元素的位置。这是一个明显的记忆泄露。堆栈已注册对其中所有对象的引用。但是,由于对象都从堆栈中移除,堆栈将继续引用移除的对象,这使得Gargabe收集器无法恢复此内存。

要解决这种情况,只需在从堆栈中移除对象时删除对该对象的引用。

清单4。解决问题。

public class Stack { 
	private List stack = new ArrayList(); 
	int count = 0; 
	public void push(Object obj) { 
		pilha.add(count++, obj); 
	} 
	public Object pop() { 
		if(count == 0) { 
			return null; 
		} 
		Object obj = stack.get(--count); 
		stack.set(count, null); 
		return obj; 
	} 
}

将对象引用赋值为null将导致 battery 不再引用该对象,从而允许垃圾回收器销毁该对象。

这个例子旨在说明Java中内存泄漏是如何可能的。任何人都很难以这种方式实现堆栈(甚至更多的Java已经为此目的提供了一个stack类)。无论如何,想象一个类似的情况,成千上万甚至数百万的对象是不可访问的,但却被引用了。例如,在应用程序服务器中不停地运行的应用程序中,这可能导致JVM在内存累积泄漏几天后终止。在这种情况下,找出错误可能是一项极其复杂和费力的任务。

避免内存泄漏的建议

遵循一些避免内存泄漏的建议比在代码完成后检测要容易得多。由于垃圾回收器极大地方便了程序员的工作,只需注意一些常见的导致内存泄漏的关键点:

  • 注意对象集合(数组、列表、映射、向量等)。有时,它们可以保存对不再需要的对象的引用;
  • 另外,对于对象集合,如果它们是静态的或在整个应用程序生命周期中持续存在,则应小心;
  • 在编程需要将事件侦听器注册到对象的情况下,如果不再需要这些对象的记录,请注意删除这些对象的记录。底线:删除对不必要对象的所有引用。通过这样做,垃圾回收器将能够彻底地完成它的工作,并且您将不会在应用程序中出现内存泄漏。

本文试图证明Java中存在内存泄漏,这与许多人的想法相反。已经演示了如何创建内存泄漏情况,以及如何注意避免这种情况,以免损害应用程序。

naqAZ3.jpg!mobile


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK