Java的一个重要功能是通过垃圾回收器(GC)自动管理内存恢复,而无需程序员自己释放内存。从理论上讲,GC可以回收Java中不再使用的对象占用的所有内存,但是Java也存在内存泄漏,但是其性能与C ++不同。
JAVA中的内存管理
要了解Java中的内存泄漏,您必须首先知道如何在Java中管理内存。
在Java程序中,我们通常使用new来为对象分配内存,而这些内存空间都在堆上。
看下面的例子:
public class Simple {
public static void main(String args[]){
Object object1 = new Object();//obj1
Object object2 = new Object();//obj2
object2 = object1;
//...此时,obj2是可以被清理的
}
}Java使用有向图进行内存管理:
在有向图中,我们所谓的obj1是可访问的,而obj2是不可访问的。显然,无法解决的问题可以得到解决。
内存的释放(即清除无法访问的对象)由GC确定并执行,因此GC监视每个对象的状态,包括应用程序,引用,引用和分配。释放对象的基本原理是将不再使用该对象:
通常,通常认为在堆上分配对象的成本相对较高,但是GC优化了此操作:在C ++中,当在堆上分配内存块时,它将找到合适的内存并对其进行分配。如果对象被破坏,则该块存储器可以被重用;在Java中,这就像一条长皮带。每次分配新对象时,Java的“堆指针”都会向后移动到未分配的区域。因此,Java中的内存分配效率与C ++相当。
但是这种工作方式存在一个问题:如果您频繁请求内存,资源将被耗尽。这时,GC介入了。它回收了空间并使堆中的对象更加紧凑。这样,将始终有足够的内存空间可用于分配。
gc清理中的引用计数方式:当引用连接到新对象时,引用计数为+1;当引用超出范围或设置为null时,引用计数为-1,并且GC发现该计数为0,它将回收它所占用的内存。这种开销将在引用程序的整个生命周期中发生,并且无法处理循环引用。因此,该方法仅用于说明GC的工作方法,不会被任何Java虚拟机使用。
大多数GC使用自适应清洗方法(加上其他附加技术来提高速度)。主要基础是查找任何“活动”对象,然后使用“自适应,分代,停止复制,标记清除”垃圾收集器。我不会详细介绍。
JAVA中的内存泄漏
从广义上讲,Java的内存泄漏是:不再使用的对象的内存无法回收,这是内存泄漏。
Java中的内存泄漏与C ++中的内存泄漏不同。
在C ++中,已分配内存的所有对象在不再使用后必须由程序员手动释放。因此,每个类都将包含一个析构函数,其作用是完成清理工作。如果我们忘记释放某些对象,则会导致内存泄漏。
但是在Java中,我们不需要(也不能)自己释放内存,GC会自动清除无用的对象,从而大大简化了我们的编程工作。但是,有时某些不再使用的对象无法在GC的视图中释放,这将导致内存泄漏。

我们知道对象具有生命周期,有些较长,有些较短。如果生命周期较长的对象持有的生命周期较短,则可能会发生内存泄漏。让我们举一个简单的例子:
public class Simple {
Object object;
public void method1(){
object = new Object();
//...其他代码
}
}实际上,我们希望这里的对象实例仅在method1()方法中起作用,并且不会在其他地方使用,而是在执行method1()方法时,由对象分配的内存对象不会被释放,立即释放的对象仅在释放由Simple类创建的对象之后才释放。严格来说,这是内存泄漏。解决方案是在method1()方法中将object用作局部变量。当然,如果您必须这样写,则可以这样更改:
public class Simple {
Object object;
public void method1(){
object = new Object();
//...其他代码
object = null;
}
}通过这种方式,GC可以回收之前由“ new Object()”分配的内存。
这时,Java内存泄漏应该更清楚了。让我们在下面进一步解释:
一些容易出现内存泄漏的示例和解决方案
上面的例子很容易发生,这也是我们最容易忽略并导致内存泄漏的情况。解决方案的原理是最小化对象的范围(例如,在android studio中,上面的代码将发出警告。给出的建议是将类的成员变量重写为方法中的局部变量)设置空值。
关于范围,在编写代码时需要更多注意;对于null值的手动设置,我们可以查看Java容器的LinkedList的源代码(请参阅:解释Java的LinkedList的源代码(JDK 1. 8)))delete指定内部方法节点:
//删除指定节点并返回被删除的元素值
E unlink(Node x) {
//获取当前值和前后节点
final E element = x.item;
final Node next = x.next;
final Node prev = x.prev;
if (prev == null) {
first = next; //如果前一个节点为空(如当前节点为首节点),后一个节点成为新的首节点
} else {
prev.next = next;//如果前一个节点不为空,那么他先后指向当前的下一个节点
x.prev = null;
}
if (next == null) {
last = prev; //如果后一个节点为空(如当前节点为尾节点),当前节点前一个成为新的尾节点
} else {
next.prev = prev;//如果后一个节点不为空,后一个节点向前指向当前的前一个节点
x.next = null;
}
x.item = null;
size--;
modCount++;
return element;
} 除了修改节点之间的关系外,我们要做的就是将值分配为null。无论GC何时开始清理,我们都应将无用的对象标记为可以及时清理的对象。
我们知道Java容器ArrayList是由数组实现的(请参阅:Java ArrayList源代码的解释(JDK 1. 8))),如果我们要为其编写pop()方法,可能是像这样:
public E pop(){
if(size == 0)
return null;
else
return (E) elementData[--size];
}编写非常简洁,但是会在此处引起内存溢出:elementData [size-1]仍然保留对E型对象的引用,并且GC无法暂时将其回收。我们可以进行如下修改:
public E pop(){
if(size == 0)
return null;
else{
E e = (E) elementData[--size];
elementData[size] = null;
return e;
}
}为了追求简洁,我们不能盲目地编写代码,首先要确保其正确性。
使用容器时发生内存泄漏
在许多文章中,您可能会看到以下内存泄漏的示例:
Vector v = new Vector();
for (int i = 1; i<100; i++){
Object o = new Object();
v.add(o);
o = null;
}许起初可能不理解它,所以让我们完成上面的代码来理解它:

void method(){
Vector vector = new Vector();
for (int i = 1; i<100; i++){
Object object = new Object();
vector.add(object);
object = null;
}
//...对vector的操作
//...与vector无关的其他操作
}此处的内存泄漏是指在矢量操作完成之后执行以下与矢量无关的代码,如果发生GC操作,则该系列对象无法回收,此处的内存泄漏可能它是短暂的,因为在执行了整个method()方法之后,那些对象仍然可以被回收。这里的解决方案非常简单,只需手动将值分配为null:
void method(){
Vector vector = new Vector();
for (int i = 1; i<100; i++){
Object object = new Object();
vector.add(object);
object = null;
}
//...对v的操作
vector = null;
//...与v无关的其他操作
}上面的Vector已经过时了,但是它只是使用旧示例引入了内存泄漏。就像上面的示例一样,使用容器时我们容易发生内存泄漏,但是在上面的示例中,由容器方法中的局部变量引起的内存泄漏的影响可能不会太大(但我们也应避免),但是如果将此容器用作类的成员变量,甚至用作静态成员变量,则必须更加注意内存泄漏。
以下也是使用容器时可能发生的错误:
public class CollectionMemory {
public static void main(String s[]){
Set objects = new LinkedHashSet();
objects.add(new MyObject());
objects.add(new MyObject());
objects.add(new MyObject());
System.out.println(objects.size());
while(true){
objects.add(new MyObject());
}
}
}
class MyObject{
//设置默认数组长度为99999更快的发生OutOfMemoryError
List list = new ArrayList<>(99999);
} 运行上面的代码将迅速报告错误:
3
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.ArrayList.(ArrayList.java:152)
at com.anxpp.memory.MyObject.(CollectionMemory.java:21)
at com.anxpp.memory.CollectionMemory.main(CollectionMemory.java:16) 如果您对Java容器了解得足够多,则不太可能发生上述错误。我还推荐了有关Java容器的文章:...
容器集仅存储唯一元素,并且通过对象的equals()方法进行比较,但是Java中的所有类都直接或间接地从Object类继承,而Object类的equals()方法比较对象的地址对象,在上面的示例中,它将一直添加元素,直到内存溢出为止。
因此,以上示例严格来说是由于容器使用不当引起的内存溢出。
就Set而言,remove()方法还使用equals()方法删除匹配的元素。如果某个对象确实提供了正确的equals()方法,请记住不要使用remove(Object o),这也可能导致内存泄漏。
提供close()方法的各种对象
例如,连接(dataSourse.getConnection()),网络连接(socket)和io连接,以及在使用其他框架时,除非它显式调用其close()方法(或类似方法)以将其连接为Close,否则GC不会自动收集它。实际上,原因仍然是寿命长的对象持有对寿命短的对象的引用。
许可能已经使用过Hibernate。当我们操作时,我们通过SessionFactory获得一个会话:
Session session=sessionFactory.openSession();完成后,我们必须调用close()方法关闭:
session.close();SessionFactory是一个长期存在的对象,而session则是一个短暂存在的对象,但是该框架的设计是合理的:它不知道我们将使用会话多长时间,因此我们只能提供一种方法自己决定何时不再使用它。
由于在调用close()方法之前,可能会引发异常并且无法调用该方法。我们通常使用try语言,然后在finally语句中执行诸如close()之类的清理工作:

try{
session=sessionFactory.openSession();
//...其他操作
}finally{
session.close();
}单例模式引起的内存泄漏
Singleton模式,在许多情况下,我们可以将其生命周期视为与整个程序的生命周期相似,因此它是一个具有较长生命周期的对象。如果此对象包含对其他对象的引用,则也容易发生内存泄漏。
内部类和外部模块的引用
实际上,原理仍然是相同的,但是它的出现方式是不同的。
与清理有关的方法
本节主要讨论gc()和finalize()方法。
gc()
对于程序员而言,GC基本上是透明且不可见的。运行GC的函数是System.gc()。调用之后,垃圾收集器开始清理。
但是根据Java语言规范的定义,此函数不能保证将执行JVM的垃圾回收器。因为不同的JVM实现者可能使用不同的算法来管理GC。通常,GC线程的优先级较低。
JVM也有许多策略来调用GC。有些是仅在内存使用量达到一定水平时才使用GC,有些是定期执行,有些是温和执行GC,有些是中断执行GC。但总的来说,我们不需要关心这些。除非在某些特定情况下,否则GC的执行会影响应用程序的性能。例如,对于游戏等基于Web的实时系统,用户不希望GC突然中断应用程序的执行并执行垃圾回收。然后,我们需要调整GC参数,以允许GC以柔和的方式释放内存,例如将垃圾回收分解为一系列要执行的小步骤。 Sun提供的HotSpot JVM支持此功能。
finalize()
finalize()是Object类中的方法。
每个了解C ++的人都知道有一个析构函数,但是请注意,finalize()绝不等于C ++中的析构函数。
这在Java编程思想中得到了解释:GC准备释放对象占用的存储空间后,将首先调用其finalize()方法,并在下一个对象被占用时实际恢复该对象占用的对象。发生GC回收操作。内存,因此需要进行一些清理工作,我们可以将其放入finalize()中。
此方法的重要用途是:当在Java中调用非Java代码(例如c和c ++)时,在这些非java代码中可以使用相应的内存应用程序操作(例如c的malloc()) )函数),并且在这些非Java代码无法有效释放内存的情况下,可以使用finalize()方法,并在内部调用本地方法的free()函数。
因此finalize()不适合常规清理工作。
但有时,此方法也很有用:
如果存在一系列对象,则其中一个对象的状态为false。如果我们已经处理了该对象,则状态将变为true。为了避免丢失尚未处理的对象,我们可以使用finalize()方法:
class MyObject{
boolean state = false;
public void deal(){
//...一些处理操作
state = true;
}
@Override
protected void finalize(){
if(!state){
System.out.println("ERROR:" + "对象未处理!");
}
}
//...
}但是从很多角度来看,建议不要使用这种方法,并且认为该方法是多余的。
本文来自电脑杂谈,转载请注明本文网址:
http://www.pc-fly.com/a/shoujiruanjian/article-362157-1.html
要看左右派
假如他国侵犯我国领海必须击之
深寒魅影的意思是围攻定镇二舰的日本舰队发射的大量炮弹均未能击穿定远镇远的主装甲