Java内存回收机制

来源:互联网 发布:神机妙算软件打不开 编辑:程序博客网 时间:2024/05/29 17:37

JVM的垃圾回收采用有向图方式来管理内存中对象,因此可以很方便地解决循环引用的问题,只要从有向图的起始顶点不可到达它们,垃圾回收机制就会回收它们。采用有向图来管理内存具有较高的精度,但缺点是效率较低。

当一个对象在堆内存中运行时,根据有向图中的状态,分为三种情况:

1、可达状态:对象被创建,有一个以上的引用变量引用它,在有向图中可从起始顶点导航到该对象,它就处于可达状态。

2、可恢复状态:程序中某个对象不再有任何引用变量引用它,它将进入可恢复状态,此时从有向图的顶点不能导航到该对象,此时系统会调用可恢复状态的对象finalize方法进行资源清理,如果系统在调用finalize方法重新让一个以上引用变量引用该对象,则这个对象会再次变为可达状态,否则该对象将进入不可达状态。

3、不可达状态:当对象的所有关联都被切断,且系统调用所有对象的finalize方法依然没有使该对象恢复可达状态,则这个对象永久性失去引用,变为不可达。


Java在java.lang.ref包下提供了3个类:SoftReference/PhantomReference/WeakReference,分别代表系统对象三种引用方式:软引用、虚引用、弱引用

java的引用主要如下:

1、强引用

2、软引用

3、虚引用

4、弱引用


1、强引用:

java程序中最常见的引用方式,程序创建一个对象,并把这个对象赋给一个引用变量,这个引用变量就是强引用。

由于JVM肯定不会回收强引用java对象,则强引用是造成java内存泄漏的主要原因。


2、软引用:

软引用需要通过SoftReference类来实现,对于只有软引用的对象而言,当系统内存空间足够时,它不会被系统回收,程序也可使用该对象,当系统内存不足时,系统将会回收它。可以看出来当系统内存足够时,软引用和强引用没有什么区别。所以软引用是强引用很大的替代。

可以调用System.gc()与System.runFinalization()来通知系统进行垃圾回收。


3、弱引用:

弱引用所引用的对象的生存期更短,弱引用通过WeakReference类实现,弱引用虽然和软引用很想,但是弱引用的引用级别更低,对于只有弱引用的对象而言,当系统垃圾回收机制运行时,不管内存是否足够,总会回收该对象所占用的内存。并不是说有一个弱引用就会回收,而是等到系统垃圾回收机制运行时才会被回收。

弱引用具有很大的不确定性,因为垃圾回收机制不受程序员的控制,因此程序获取弱引用的Java对象时必须小心空指针异常。

与WeakReference功能类似的还有WeakHashMap,当程序有大量的Java对象需要使用弱引用,可以考虑使用WeakHashMap来保存它们。


4、虚引用:

软引用和弱引用可以单独使用,但虚引用不能单独使用,单独使用虚引用没有太大的意义。虚引用的主要作用就是跟踪对象被垃圾回收的状态,程序通过检查与虚引用关联的引用队列中是否已经包含指定的虚引用,从而了解虚引用所引用的对象是否即将被回收。

引用队列为java.lang.ref.ReferenceQueue类表示。它用于保存被回收后对象的引用。

虚引用通过PhantomReference类实现,它完全类似于没有引用,它只用于跟踪对象被垃圾回收的状态,虚引用不能单独使用,必须和引用队列联合使用。

使用这些引用类可以避免在程序执行期间将对象留在内存,如果以软引用、弱引用和虚引用的方式引用对象,垃圾回收器就能够随意地释放对象。


一、Java内存泄漏:

一些Java对象,它们处于可达状态,但程序以后都不会再访问到它们,那么它们所占用的内存空间也不会被回收,它们所占用的内存空间就会产生内存泄漏。

内存泄漏通常会出现在ArrayList中的remove()方法中,而在源码中,remove()中删除一个元素后,就会将这个元素置null,如果我们不做这步操作的话,很容易会产生内存泄漏。


二、Java垃圾回收机制:

java的垃圾回收机制主要做两件事情:

1、跟踪并监控每个Java对象,当某个对象处于不可达状态时,回收改对象所占用的内存。

2、清理内存分配、回收过程中产生的内存碎片

高效的垃圾回收算法是java程序运行效率的重要体现,避免内存的分配和回收称为应用程序的性能瓶颈。


三、垃圾回收的基本算法:

注意:JVM垃圾回收机制判断某个对象是否可以回收的唯一标准是:是否还有其他的引用指向该对象?如果有,垃圾回收机制就不会回收该对象,否则垃圾回收机制就会尝试回收。

对于一个垃圾回收期的设计算法,主要有如下设计:

1、串行回收(Serial)和并行回收(Parallel):串行回收只用一个CPU进行垃圾回收,并行回收用多个CPU分担进行垃圾回收。显然并行回收的效率更加高,但是其复杂度也很高,而且还容易产生内存碎片。

2、并发执行(Concurrent)和应用程序停止(Stop-the-world):应用程序停止(Stop-the-world)在执行垃圾回收的时候会导致应用程序的暂停,并发执行的垃圾回收不会导致应用程序的暂停,但是并发执行的系统开销更大,而且需要的内存也较大。

3、压缩(Compacting)和不压缩(Non-Compacting)和复制(Copying):压缩的垃圾回收会把所有的活对象搬到一起,然后释放内存;不压缩的垃圾回收知识回收内存,这样会导致更多的内存碎片;显然压缩的垃圾回收更快,不压缩的垃圾回收回收快,但是分配内存慢,且容易产生内存碎片。。复制的垃圾回收是回收过程不易产生内存碎片,但是复制过程需要额外的内存。


四、堆内存的分代回收:

先行的垃圾回收器用分代的方式来采用不同的回收设计,分代是根据对象生成时间的长短,将堆内存分为3代:

堆内存分代回收的基本思路:

(1)绝大多数内存对象不会被长时间引用,这些对象在其Young时期就会被回收

(2)很老的对象和很新的对象之间很少存在相互引用的情况


1、Young(年轻代):只需遍历那些处于可达状态的对象,而且这些对象的数量较少,可复制成本不大,采用复制算法较好。

2、Old(老年代):使用串行标记压缩算法,这种算法可以避免复制Old代的大量对象,回收过程中不会产生大量的内存碎片。

3、Permanent(永久代):对于那些需要加载很多类的服务器程序,需要加大Permanent代内存,否则可能因为内存不足而导致程序终止。


五、常见的垃圾回收器:

1、串行回收器(Serial Collector):对Young代采用串行复制算法,对Old代采用串行标记压缩算法

2、并行回收器:只有多CPU并行的机器才能发挥其优势,主要用于对Old代进行垃圾回收

3、并行压缩回收器(Parallel Compacting Collector)

4、并发标志-清理回收器(Mark-Sweep CMS)



Java为了更好的避免内存泄漏,根据上两篇介绍的内存回收机制,下面给出Java内存回收的几点建议:

1、使用直接量:

当需要使用字符串时,还有Byte/Short/Integer/Long/Float/Double/Boolean/Character包装类的实例时,程序不应该使用new的方式来创建,而应该采用直接量来创建它们

String str=“hello”,创建了一个“hello”字符串,而且JVM的字符串缓存池还会缓存这个字符串

String str=new String(“hello”),此程序创建了一个缓存在字符串池中的“hello”字符串,除此之外str所引用的String对象底层还包含一个char[]数组,这个char[]数组里依次存放着h、e、l、l、o等字符。


2、使用StringBuilder和StringBuffer进行字符串连接:

大家如果不知道String、StringBuilder、StringBuffer之间区别的,可以看我之前的一篇博文,上面已经进行一些比较详细的分析,

总之一句话,如果使用多个String对象进行字符串连接运算,在运行的时候会产生大量的临时字符串,这些字符串会保存在内存中从而导致程序性能下降。


3、尽早释放无用对象的引用:

大部分情况下,方法的局部引用变量所引用的对象会随着方法的结束而变成垃圾,因为局部变量的生存周期很短,所以一般情况下不需要直接将局部引用变量直接赋值为null,但是如果一个方法很大,里面有很多耗费内存的调用,那么把巨变引用变量赋为null就是十分必要的了,而且应该这么做。


4、尽量少使用静态static变量:

因为垃圾回收机制判断一个对象是否是垃圾的唯一标准就是该对象是否有引用变量引用它,当某个对象被static变量所引用,那么垃圾回收机制通常不会回收这个对象所占用的内存,如下:

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. package com.crazyit;  
  2.   
  3. public class Student {  
  4.     static Student obj = new Student();  
  5. }  
obj变量是Student类的静态变量,因此它的生命周期与Student同步,在Student类不被卸载的情况下,Student类对应的Class对象会常驻内存,知道程序运行结束,因此obj所引用的Student对象一旦被创建,也会常驻内存,知道程序运行结束。同时obj应用变量在Permannent代里。


5、避免在经常调用的方法、循环中创建Java对象:

经常调用的方法和循环有一个共同的特征,这段代码会被重复调用,如果在循环体内创建新的对象,则要不断地为对象分配内存和执行初始化操作,而且还要不断地回收内存空间,这样重复的操作会让程序的性能性能很慢。


6、缓存经常使用的对象:

弱国一些对象需要被经常使用,可以考虑把这些对象用缓存池保存起来,这样当下次需要的时候就可以直接拿出这些对象来使用,典型的缓存就是数据库连接池,数据连接池里缓存了大量的数据库连接,每次程序需要访问数据库时都可以直接取出数据库连接池。

实现缓存有两种方法:

(1)使用HashMap缓存

(2)直接使用某些开源缓存项目


7、尽量不要使用finalize方法:

垃圾回收机制的工作量十分大,尤其在回收Young代内存的时候,大都会引起应用程序的暂停,这样会使用户难以忍受。


8、考虑使用SoftReference:

这里只需要记住软引用会产生的一些意外即可。

0 0
原创粉丝点击