大致内容,如下所示:
》JVM在何时决定回收一个Java对象所占用的内存?
》JVM会不会漏掉回收某些Java对象,使之造成内存泄漏?
》JVM回收 lava对象所占用内存的实现细节。
》JVM能否对不同的Java对象占用的内存区分对待、回收?
》常见垃圾回收机制的实现细节是怎样的?
对象在内存中的状态
对于jvm的回收机制来说,是否回收一个对象的标准在于:是否还有引用变量引用该对象?只要有引用变量引用该对象,垃圾回收机制就不会回收它
基本上,可以把JVM内存中的对象引用理解为一种有向图,把引用变量,对象都当作有向图的顶点,将引用关系当作图的有向边,有向边总是从引用端指向被引用对象。因为Java的所有对象都是有一条条线程创建出来的,因此可以把线程对象当成有向图的顶点。
只要从有向图的起始顶点(也就是进程根)不可到达它们,垃圾回收机制就会回收它们。采用有向图来管理内存中的对象具有较高的精度,但缺点是效率较低。
当一个对象在堆内存中运行时,根据它在对应有向图中的状态,可以把它所处的状态分成如下三种:
》可达状态;:当一个对象被创建后,有一个以上的引用变量引用它。在有向图中可以从起始顶点导航到该对象,那么它就处于可达状态,程序可以通过引用变量来调用该对象的属性和方法。
》可恢复状态:如果程序中某个对象不再有任何引用变量引用它,它将先进入可恢复状态,系统调用fimalize方法可以重新让其变为可达状态
》不可达状态:当对象的所有关联都被切断,且系统调用所有对象的fimalize方法依然没有恢复可达状态
一个对象可以被一个方法的局部变量引用,也可以被其他类的类变量引用,或者被其他对象的实例变量引用。当某个对象被其他类的类变量引用时,只有该类被销毁后,该对象才会进入可恢复状态;当某个对象被其他对象的实例变量引用时,只有当引用该对象的对象被销毁或变成不可达状态后,该对象才会进入不可达状态。
引用分类的由来
对垃圾回收机制来说,判断一个对象是否可回收的标准就在于该对象是否被引用,因此引用也是JVM进行内存管理的一个重要概念。为了更好地管理对象的引用,从JDK1.2开始,Java在java.lang.ref包下提供了三个类;SoftReference、PhantomReference和WeakReference。它们分别代表了系统对对象的三种引用方式:软引用、虚引用和弱引用。归纳起来,Java语言对对象的引用有如下四种:
》强引用
这是Java程序中最常见的引用方式,程序创建一个对象,并把这个对象赋给一个引用变量,这个引用变量就是强引用。
由于JVM肯定不会回收被强引用所引用的Java对象,因此强引用是造成Java内存泄漏的主要原因之一。
》软引用
软引用通过SoftReference类来实现,当一个对象只具有软引用时,它有可能被垃圾回收机制回收。
当系统内存充足,则不会被回收,如果内存紧张,被软引用的对象则可以被回收,提高程序运行性能。
因此,SoftReference 引用对象非常适合实现内存敏感的缓存,例如加载图片的时候,bitmap缓存机制。
用法举例:
一,
String value = new String(“sy”);
SoftReference sfRefer = new SoftReference (value );
sfRefer .get();//可以获得引用对象值
二,
SoftReference<Person>[]people = new SoftReerence[10000];
for (int i=0;i<people.length;i++)
{
people[i]=new SoftReference<Person>(new Person("age","name"))
}
》弱引用
具有弱引用的对象,与软引用对比来说,前者的生命周期更短。当垃圾回收器扫描到弱引用的对象的时候,不管内存空间是否足够,都会直接被垃圾回收器回收。不过也不用特别担心,垃圾回收器是一个优先级比较低的现场,因此不一定很快可以发现弱引用的对象。
用法实例:
String value = new String(“sy”);
WeakReference weakRefer = new WeakReference(value );
System.gc();
weakRefer.get();//null
弱引用具有很大的不确定性,因为每次垃圾回收机制执行时都会回收弱引用所引用的对象,而垃圾回收机制的运行又不受程序员的控制,因此程序获取弱引用所引用的Java对象时必须小心空指针异常—通过弱引用所获取的Java对象可能是null。
》虚引用
小节拾遗:
在强引用中,如果不让该对象指向为空,垃圾回收器绝对不会回收它。除非当出现内存空间不足的时候。jvm抛出oom导致程序异常种植的时候,才会回收具有强引用的对象来解决内存空间不足问题。
Object obj =new Object(); // 强引用
obj = null;//这时候为垃圾回收器回收这个对象,至于什么时候回收,取决于垃圾回收器的算法
Java的内存泄漏
ArrayList的整体搬移
Stack的pop()方法
**ElementData[size]==null;
*************************************************************************************************************
垃圾回收机制
垃圾回收机制主要完成以下两种事情:
1. 跟踪并监控每一个Java对象,当某个对象处于不可达状态时,回收该对象所占用的内存。
2. 清理内存分配,回收过程中产生的内存碎片
垃圾回收的基本算法
对于一个垃圾回收器的设计算法来说,大致有如下可供选择的设计:
串行回收和并行回收:
并发执行和应用程序停止:
垃圾回收器回收已用内存空间的方式
压缩(标记压缩)
不压缩(标记清除)
复制
堆内存的分代回收
现行的垃圾回收器用分代的方式采用不同的回收算法
Young
Old
Permanent
分代回收的一个依据就是对象生存时间的长短,然后根据不同代采取不同的垃圾回收策略。
采用这种“分代回收”的策略基于如下两点事实。
1)绝大对数的对象不会被长时间引用,这些对象在其Young期间就会被回收。
2》很老的对象(生存时间很长)和很新的对象(生存时间很短)之间很少存在相互引用的情况。
Young
根据上面两点事实,对于Young代的对象而言,大部分对象都会很快就进入不可达状态,
只有少量的对象能熬到垃圾回收执行时,而垃圾回收器只需要保留Young代中处于可达状态的对象,如果采用复制算法只需要少量的复制成本,因此大部分垃圾回收器对Young代都采用复制算法。
OLD
如果Young代中的对象经过数次垃圾回收依然没有被回收掉,即这个对象经过足够长的时间还处于可达状态,垃圾回收机制就会将这个对象转移到Old代。
Old代回收的特点:
1.Old代垃圾回收的执行频率无须太高,因为很少有对象会死掉。
2.每次对Old代执行垃圾回收都需要更长的时间来完成。
Permanent代
Permanent代主要用于装载Class、方法等信息,默认为64MB,垃圾回收机制通常不会回收Permanent代中的对象。对于那些需要加载很多类的服务器程序,往往需要加大Permanent代的内存,否则可能会因为内存不足而导致程序终止。
总结:
当Young代的内存将要用完时,垃圾回收机制会对Young代进行垃圾回收,垃圾回收机制会采用较高的频率对Young代进行扫描和回收。因为这种回收的系统开销比较小,因此也被称为次要回收(minor collection)。
当Old代的内存将要用完时,垃圾回收机制会进行全回收,也就是对Young代和Old代都要进行回收,此时回收成本就大得多了,因此也称为主要回收(major collection)。
通常来说,Young代的内存会先被回收,而且会使用专门的回收算法(复制算法)来回收Young代的内存;对于Old代的回收频率则要低得多,因此也会采用专门的回收算法。
如果需要进行内存压缩,那么每个代都独立地进行压缩。
常见的垃圾回收器
1. 串行回收器
2. 并行回收器
3. 并行压缩回收器
4. 并发标识-清理回收器(不压缩)
********************************************************************************************
内存管理小技巧
1. 尽量使用直接量
当需要使用字符串,还有Byte、Short、Integer、Long、Float、Double、Boolean、Character包装类的实例时,程序不应该采用new的方式来创建对象,而应该直接采用直接量来创建它们。
2使用StringBuilder 和StringBufer进行字符串连接
String、StringBuilder、StringBuffer都可代表字符串,其中String代表字符序列不可变的字符串,而StringBuilder 和StringBuffer都代表字符序列可变的字符串。
如果程序使用多个String对象进行字符串连接运算,在运行时将生成大量的临时字符串,这些字符串会保存在内存中从而导致程序性能下降。
3.尽早释放无用对象的引用
分两种情况:
方法的局部变量所引用的对象会随着方法的结束而变成垃圾,此时无需用null将其显式释放
书上的例子:
对于上面程序所示的info0方法,如果在粗体字代码后还需要执行耗时、耗内存操作,或者还需要调用耗时、耗内存的方法,那么程序中的粗体字代码就是有必要的:
可以尽早释放对Objiect对象的引用。可能的情况是,程序在执行粗体字代码之后的耗时、耗内存操作时,obj之前所引用的Object对象可能被垃圾回收了。
4尽量少使用静态变量
5避免在经常调用的方法,循环中创建JAVA对象
6记得缓存经常使用的对象
两种方法:使用hashmap进行缓存
直接使用某些开源的缓存项目
7尽量不要使用finalize方法
8考虑使用弱引用特别是创建数量很大的数组时