Java垃圾回收的对象都储存在堆内存中。
堆内存分为:新生代和老生代(永久代已经在1.8中删除),新生代又分为:Eden,Survivor0 和 Survivor1。
可以调用System.gc()和Runtime.gc()来启动垃圾回收,但JVM并不保证垃圾回收会开始,并且可以拒绝。
流程:当一个对象被创建后,首先会存入Eden区。GC会周期性的扫描内存,并将过期的对象进行标记和删除,存活的对象会有一个年龄,没扫描一次年龄+1.到一定年龄后会被移动到Survivor0.
同理,Survivor0会移动到Survivor1,Survivor1会移动到老生代。
老生代的对象被删除后,会留下空间碎片,一些算法会将这些碎片整理。
什么样的对象会被回收
1.如果一个对象没有被引用,或者没有被其他被引用的实例循环引用。
2.弱引用,虚引用会被直接回收,软引用在内存溢出时会被回收
垃圾回收器的种类
1.串行垃圾回收器
串行垃圾回收器通过持有应用程序所有的线程进行工作。它为单线程环境设计,只使用一个单独的线程进行垃圾回收,通过冻结所有应用程序线程进行工作,所以可能不适合服务器环境。它最适合的是简单的命令行程序。
2.并行垃圾回收器
并行垃圾回收器也叫做 throughput collector 。它是JVM的默认垃圾回收器。与串行垃圾回收器不同,它使用多线程进行垃圾回收。相似的是,它也会冻结所有的应用程序线程当执行垃圾回收的时候
3.并发扫描垃圾回收器
并发标记垃圾回收使用多线程扫描堆内存,标记需要清理的实例并且清理被标记过的实例。并发标记垃圾回收器只会在下面两种情况持有应用程序所有线程。
- 当标记的引用对象在tenured区域;
- 在进行垃圾回收的时候,堆内存的数据被并发的改变
4.G1垃圾回收器
G1垃圾回收器适用于堆内存很大的情况,他将堆内存分割成不同的区域,并且并发的对其进行垃圾回收。G1也可以在回收内存之后对剩余的堆内存空间进行压缩。并发扫描标记垃圾回收器在STW情况下压缩内存。G1垃圾回收会优先选择第一块垃圾最多的区域
判断对象是否可以回收的具体算法
首先每个对象有一个finalize方法,在对象被回收之前会调用一次这个方法。
Java对象是否存活的判断算法——根搜索算法
它把内存中的每一个对象都看作一个节点,并且定义了一些对象作为根节点“GC Roots”。如果一个对象中有另一个对象的引用,那么就认为第一个对象有一条指向第二个对象的边。JVM会起一个线程从所有的GC Roots开始往下遍历,当遍历完之后如果发现有一些对象不可到达,那么就认为这些对象已经没有用了,需要被回收。
这个算法的关键就在于GC Roots的定义,有四种作为GC Roots的对象
1.虚拟机栈中的引用的对象,我们在程序中正常创建一个对象,对象会在堆上开辟一块空间,同时会将这块空间的地址作为引用保存到虚拟机栈中,如果对象生命周期结束了,那么引用就会从虚拟机栈中出栈,因此如果在虚拟机栈中有引用,就说明这个对象还是有用的,这种情况是最常见的。
2.在类中定义了全局的静态的对象,也就是使用了static关键字,由于虚拟机栈是线程私有的,所以这种对象的引用会保存在共有的方法区中,显然将方法区中的静态引用作为GC Roots是必须的。
3.常量引用,就是使用了static final关键字,由于这种引用初始化之后不会修改,所以方法区常量池里的引用的对象也应该作为GC Roots。
4.在使用JNI技术时,有时候单纯的Java代码并不能满足我们的需求,我们可能需要在Java中调用C或C++的代码,因此会使用native方法,JVM内存中专门有一块本地方法栈,用来保存这些对象的引用,所以本地方法栈中引用的对象也会被作为GC Roots。
关于JVM各个区域储存的东西:http://blog.csdn.net/fastthinking/article/details/37567993
每个线程维护一个自己的栈,用来储存函数调用信息。而静态的成员变量或者常量(static final)是线程无关的(无论几个线程在调用当前对象,静态变量或常量只有一个),他们会被储存在方法区中。
虚拟机栈区 :也就是我们常说的栈区,线程私有,存放基本类型,对象的引用和 returnAddress ,在编译期间完成分配。
堆区 , JAVA 堆,也称 GC 堆,所有线程共享,存放对象的实例和数组, JAVA 堆是垃圾收集器管理的主要区域。
方法区 :所有线程共享,存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。这个区域的内存回收目标主要是针对常量池的对象的回收和对类型的卸载。
程序计数器 :线程私有,每个线程都有自己独立的程序计数器,用来指示下一条指令的地址。