【什么是垃圾】
垃圾是指在运行程序中没有任何指针指向的对象。
提供finalization机制来允许开发人员自定义对象销毁前的处理逻辑。 Object中定义了finalize方法,可以被覆写。
永远不要主动调用对象的finalize方法,这个应该交由垃圾收集器调用。 理由如下:
(1)在finalize时可能会导致对象复活。
(2)finalize的执行时刻是没有保障的,完全由GC线程决定。 极端情况下,如果不发生GC,那么finalize方法永远不会执行。
(3)一个糟糕的finalize方法严重影响GC的性能。
虚拟机中的对象存在三种状态:
① 可触及的:从根节点开始,可以到达这个对象
② 可复活的:对象的所有引用都被释放,但是对象有可能在finalize中复活。
③ 不可触及的:对象的finalize被调用,并且没有复活,那么就会进入不可触及状态。 不可触及的对象不可能被复活,因为finalize只会被调用一次。
判定一个对象objA是否可以回收,至少要经历两次标记过程:
/**
* 注释掉了finalize方法的结果
* first gc
* obj is dead
* second gc
* obj is dead
*
* 没有注释掉finalize方法的执行结果:
* first gc
* Call override finalize method.
* obj is still alive
* second gc
* obj is still alive
*/
public class CanReliveObj {
public static CanReliveObj obj;
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("Call override finalize method.");
obj = this;
}
public static void main(String[] args) throws InterruptedException {
obj = new CanReliveObj();
obj = null;
System.gc();
System.out.println("first gc");
// finalizer线程优先级很低,主线程sleep两秒,等待finalizer执行。
Thread.sleep(2000);
if (obj == null) {
System.out.println("obj is dead");
} else {
System.out.println("obj is still alive");
}
System.out.println("second gc");
// finalizer线程优先级很低,主线程sleep两秒,等待finalizer执行。
Thread.sleep(2000);
if (obj == null) {
System.out.println("obj is dead");
} else {
System.out.println("obj is still alive");
}
}
}
显示触发full gc,同时对新生代和老年代进行回收。 但是System.gc无法保证对垃圾收集器的调用(无法确保开始执行的时间)。
public class SystemGcTest {
public static void main(String[] args) {
new SystemGcTest();
System.gc();
System.runFinalization();
}
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("SystemGcTest has override finalize() method");
}
}
The java.lang.Runtime.runFinalization() method runs the finalization methods of any objects pending finalization. Calling this method suggests that the Java virtual machine expend effort toward running the finalize methods of objects that have been found to be discarded but whose finalize methods have not yet been run. When control returns from the method call, the virtual machine has made a best effort to complete all outstanding finalizations.
The virtual machine performs the finalization process automatically as needed, in a separate thread, if the runFinalization method is not invoked explicitly. The method System.runFinalization() is the conventional and convenient means of invoking this method.
【内存溢出】
OOM:没有空闲的内存,并且垃圾收集器也无法提供更多的内存。
【内存泄漏】
内存泄漏:只有对象不会再被程序用到了,但是GC又不能回收他们的情况,称为内存泄漏。
实际情况中,可能会存在一些不好的实现,会导致对象的生命周期变得很长,甚至导致了OOM,这种就叫做广义上的“内存泄漏”。
举例:
单例模式
如果单例对象持有外部对象的引用的话,这个外部对象是不能被回收的,会导致内存泄漏产生。
一些提供close的资源未关闭导致内存泄漏
数据库连接、网络连接和IO连接必须手动close,否则是不会被回收的。
用户线程只有在特定的位置才能停顿下来GC,这些位置称为“安全点(Safe Point)”。如果安全点太少会导致GC等待的时间太长。如果太多会导致运行时性能问题。通常会选择一些执行时间较长的指令作为safe point,如方法调用、循环跳转和异常跳转。
【如何保证在GC的时候,检查所有线程都跑到最近的安全点上停顿下来】
线程处于Sleep状态或者Blocked状态,这时候无法响应JVM的中断请求,无法走到安全点。此种情况下,需要靠安全区域来解决这个问题。
安全区是指在一段代码片段中,对象的引用关系不会发生变化,在这个区域中任何位置开始GC都是安全的。
【安全区域的运作机制】
强引用:不回收
软引用:内存不足即回收
弱引用:发现即回收
虚引用:对象回收跟踪
【终结器引用】
终结器引用用来实现对象的finalize方法。
无需手动编码,其内部配合引用队列使用。
在GC时,终结器引用入队。由Finalizer线程通过终结器引用找到被引用对象并调用它的finalize方法,第二次gc的时候才能回收被引用对象。
【怎样判断垃圾——引用记数法】
优点:实现简单,垃圾对象便于识别;判定效率高,回收没有延迟性(只要引用计数是0,随时都可以回收)。
Python使用的是引用计数法。Python解决循环引用的方法:(1)手动接触引用(2)弱引用。
【可以作为GC Roots的对象】
【Stop The World】
可达性分析必须要在一个能保障一致性的快照中进行。 如果不满足的话,分析结果的准确性无法保证。 这就是GC进行时必须“Stop the World”的一个重要原因。 即使几乎不发生停顿的CMS收集器中,枚举根节点时也是必须要停顿的。
① 待查看的代码
package org.example.gcroot;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Scanner;
public class GcRootTest {
public static void main(String[] args) {
List<Object> numList = new ArrayList<>();
Date birth = new Date();
for (int i = 0; i < 100; i++) {
numList.add(String.valueOf(i));
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("数据添加完毕,请操作");
new Scanner(System.in).next();
numList = null;
birth = null;
System.out.println("numList, birth已经置空,请操作");
new Scanner(System.in).next();
System.out.println("程序结束");
}
}
② 在运行过程中使用visualvm导出堆dump,在第一次程序阻塞和第二次程序阻塞的时候都dump一次
③ 使用MAT查看两次导出的dump文件,查看gc roots
① 启用在OOM的时候导出HeapDump
② 使用JProfiler查看Biggest Object
③ 查看Thread Dump,查看哪个线程的哪一行有问题。
Copying
【优点】
实现简单,运行高效。
可以保证空间连续性,不会出现“碎片”问题。
【缺点】
需要两倍的内存空间。空间浪费。
G1将内存拆分成很多的region,在复制过程中,GC需要维护region之间对象的引用关系。内存占用和时间开销都是不小的。
如果系统中存活的对象很多,复制算法就不理想。这样会复制很多存活的对象,过犹不及。
标记:Collector从引用根节点开始遍历,标记所有被引用的对象。一般是在对象的Header中记录为可达对象。 注意:标记清除算法的标记标记的是可达对象,不是垃圾对象。
清除:Collector对堆内存进行线性遍历,如果发现某个对象的Header中没有可达标记,则将其回收。
缺点:
效率不算高。 两次扫描
在GC的时候需要停止整个应用程序。
存在内存碎片,需要维护空闲列表。
【何为清除?】
清除并不是真的置空,而是把要清除的对象地址保存在空闲的地址列表里面。下次有新对象需要加载时,判断垃圾的位置空间是否够,如果足够就存放。
Mark阶段的开销与存活对象的数量成正比。
Sweep阶段的开销与所管辖的区域的大小成正比。
Compact阶段的开销与存活对象的数据成正比。
以HotSpot中的CMS回收器为例,CMS是基于MARK-SWEEP实现的,对于对象的回收效率很高。对于碎片问题,CMS使用基于标记压缩算法的Serial Old收集器进行补偿。 当内存回收不佳的时候,将采用Serial Old执行Full GC以达到对老年代内存的整理。
优点:
缺点:系统吞吐量的下降。
将大的内存区域分割成多个小块,根据目标的停顿时间,每次合理地回收若干个小区间,而非整个堆空间。 从而减少一次GC所产生的停顿。
【吞吐量】
运行用户代码时间占总运行时间的比例。
总运行时间 = 程序的运行时间 + 内存回收的时间
【垃圾收集开销】
吞吐量的补数,垃圾收集所用时间占总运行时间的比例。
【暂停时间】
进行垃圾收集时,程序的工作线程被暂停的时间
【收集频率】
收集操作发生的频率
【内存占用】
Java堆区所占内存的大小。
【回收速率】
一个对象从诞生到被回收所经历的时间。
串行回收器:Serial、Serial Old
并行回收器:ParNew、Parallel Scavenge、Parallel Old
并发回收器:CMS、G1
新生代收集器:Serial、ParNew、 Parallel Scavenge
老年代收集器:Serial Old、Parallel Old、CMS
整堆收集器:G1
【查看当前系统使用Garbage Collector】