前言
对于JVM(Java虚拟机)的GC(垃圾回收机制)来说,是否回收一个对象的标准是:是否还有引用变量指向该对象。只有有引用变量指向该对象,那么JVM就不会考虑去回收它。
而在学习Java的时候,一般都是会说:Java有一套完整的垃圾回收机制,程序员可以不需要考虑内存。但是在实际应用中,还是会出现“内存泄漏”的情况。
对象在内存中的状态
基本上可以将JVM中的对象引用理解为离散中学到的有向图。
将对象当做有向图的顶点,将引用关系当做有向图的有向边,有向边总是从引用端指向被引用的变量。在JVM中,Java的各种对象都是由各个线程创建的,所以可以将Java的线程对象作为有向图的起点。
如果按照上述的方式,对于某一个对象来说,始终有大于等于一条路径能够从起点指到它,那么它就不会被GC。当然,如果找不到一条路径能够指向它,那么它就可能会被回收(注意这里说的是可能,因为GC的回收机制是由一套相对复杂的算法来决定的,所有的对象并不是失去引用马上就会被回收)。
那么,是不是所有的对象,只要有引用指向它,它就不会被回收呢?但是是否定的,原因请继续往下看。
而一般来说,对于Java中的对象引用有4种方式:强引用、弱引用、软引用和虚引用。
关于这几种引用的概念,简单的说明如如下:
强引用
对于强引用来说,可能我们平时在写代码的时候,都是用的强引用,被强引用的Java对象时不会被垃圾回收机制给回收的。即使系统资源非常紧张,即使有些变量以后都不会用到,JVM也不会强制回收被强类型引用的变量,如果系统内存实在不够,程序会直接抛出OutOfMemory异常。
而我们在平常的开发中,使用的最多的也是这种,比如
1Person person =new Person();
软引用
软引用需要通过SoftReference来实现,当一个对象只具有SoftReference时,它可能会被垃圾回收机制给回收,但是对于SoftReference的对象来说,当系统资源足够时,它是不会被系统回收,程序可以使用该对象,但是当系统资源不够的时候,GC会回收它。
使用的方法一般如下:
SoftReference<Person>[] persons=new SoftReference[1000000]
弱引用
弱引用和软引用有些相似,区别在于 弱引用的生命周期更短,弱引用需要通过WeakReference来实现,对于只有弱引用对象,当GC运行时,无论系统资源是否足够,该对象都会被回收。(这里还是需要提醒一下,GC的运行不是实时的,不是说当该对象不再被引用变量引用的时候,GC会立即运行来回收它)
当然了,这样的设计是有一定的道理的,这样是为了更加好的解决系统资源。由于GC的不定时,所以可能会遇到空指针异常,所以在进行业务逻辑处理的时候,建议在开始的时候,判断下该对象是不是为空。
虚引用
虚引用通过PhantomReference类实现,它完全类似于没有引用。虚引用主要用于跟踪对象被垃圾回收的状态,虚引用不能单独被使用,虚引用必须要和引用队列(ReferenceQueue)一起使用。
对象在堆中的几种状态
一个对象在堆中运行时,根据它在有向图中的状态可以分为3种:
可达状态
当一个对象被创建后,有一个以上的引用变量引用它,在有向图中可以从起始顶点导航到该对象,那么就处于可达状态。
可恢复状态
如果程序中某个对象不在有任何引用变量引用它,它将先进入可恢复状态,此时从有向图的顶点不能导航到该对象,在这个状态下,系统的垃圾回收机制准备回收该对象所占用的内存,在回收该对象之前,会调用可恢复状态的对象的finalize方法进行资源清理,如果在系统调用finalize方法重新让一个以上的引用引用该对象,那么这个对象就可以变成可达状态。否则,该对象将进入到不可达状态。
不可达状态
当对象的所有关联都被切断,且系统调用所有对象finalize依然没有使该对象变为可达状态,那这个对象将永久性的失去引用,当一个对象处于不可达状态时,系统才会真正的回收该对象所占用的资源。
Java的内存泄漏
定义:程序运行过程中会不断的分配内存空间,那些不再使用的内存空间应该立即回收它,从而保证系统再次使用这些内存,如果存在无用的内存被回收回来,那么就叫内存泄漏。
内存管理的技巧
1、尽量使用直接量,比如String s=:”hello”;而不是String s=new String (“hello”),这两种方法都会在再字符串缓冲池里面有缓存,区别是使用new的方式底层会创建一个char[]数组。
2、使用StringBuilder和StringBuffer进行字符串连接。如果使用String对象进行字符串连接,会生成大量的临时字符串会导致性能下降。
3、尽早释放无用对象的引用。
4、尽量少使用静态变量,因为静态变量的生命周期和类同步,只要class没有被卸载,那么就会一直常驻内存。
5、避免在经常调用的方法,循环中创建Java对象。这样会导致系统不断的为变量分配释放空间。
6、缓存经常使用的对象:如果有些对象需要被经常使用,那么可以考虑将这些对象用缓冲池保存起来,典型的缓存就是数据连接池。
PS:缓存的设计本身就是一种牺牲系统空间来换取运行时间的方式。如何控制缓存容器占用的内存空间不至于太大,同时又能保存大部分需要使用到的对象,这是缓存的设计的关键。
7、尽量不要使用finalize方法
8、考虑使用softReference,但是要注意软引用的不确定性。
总结
引用对象有4个级别,其由高到低一次为:强引用、软引用、弱引用和虚引用。在需要严格考虑系统资源的情况下,还是需要考虑到这个系统的消耗的,这个时候就不能一味的使用强类型引用了。