Java虚拟机的内存结构分为五个部分,分别是:程序计数器、Java虚拟机栈、本地方法栈、堆、方法区。

既然是存储空间,为避免在Java程序运行期间发生内存溢出的情况,就需要一个能及时清理掉不再使用的内容,回收对应的内存空间的角色存在,这个角色就是垃圾收集器。现在很多内存的动态分配与内存的回收技术已经十分成熟,可以看作是进入了“自动化”的时代;了解垃圾回收机制和内存动态分配的目的在于:当需要排查各种内存溢出,内存泄露问题时,当垃圾收集成为系统达到更高并发量的瓶颈时,就有必要对这些技术的实施进行监控和调节。

回到Java虚拟机,对于垃圾收集器,我们不禁有些疑问:何时回收垃圾,清除哪些内容?接下来就要解决这些问题。

程序计数器、Java虚拟机栈、本地方法栈都是线程私有的,也就是每条线程都拥有这三块区域,而且会随着线程的创建而创建,线程的结束而销毁。何时对这三个区域的内存进行回收就很明确了。Java虚拟机栈、本地方法栈中的栈帧会随着方法的开始而入栈,方法的结束而出栈,并且每个栈帧中的本地变量表都是在类被加载的时候就确定的。因此以上三个区域的垃圾收集工作具有确定性,垃圾收集器能够清楚地知道何时清扫这三块区域中的哪些数据。

但是对于Java堆和方法区则不一样,这两部分是线程共享的,并且都在JVM启动时创建,一直得运行到JVM停止时。因此它们没办法根据线程的创建而创建、线程的结束而释放。

堆中存放JVM运行期间的所有对象,虽然每个对象的内存大小在加载该对象所属类的时候就确定了,但究竟创建多少个对象只有在程序运行期间才能确定。

方法区中存放类信息、静态成员变量、常量。类的加载是在程序运行过程中,当需要创建这个类的对象时才会加载这个类。因此,JVM究竟要加载多少个类也需要在程序运行期间确定。

这两部分内存的分配和回收是动态的,所以Java虚拟机对于这两部分的垃圾收集做了许多工作。

堆内存的回收

判断对象是否存活

在对堆进行对象回收之前,首先要判断哪些是无效对象。一个对象不被任何对象或变量引用,那么就是无效对象,需要被回收。一般有两种判别方式:

● 引用计数算法:

每个对象都有一个计数器,当这个对象被一个变量或另一个对象引用一次,该计数器加一;若该引用失效则计数器减一。当计数器为0时,就认为该对象是无效对象。

优点:实现简单,效率高

缺点:无法解决循环引用的问题,即对象A中引用了对象B,对象B中引用了对象A,除此之外再无别的对象引用A和B。

● 可达性分析法

所有和GC Roots直接或间接关联的对象都是有效对象,和GC Roots没有关联的对象就是无效对象。

GC Roots是指:

1.虚拟机栈(栈帧中的本地变量表)中引用的对象;

2.方法区中类静态属性引用的对象;

3.方法区中常量引用的对象;

4.本地方法栈中JNI(即Native方法)引用的对象

GC Roots并不包括堆中对象所引用的对象,这样就不会出现循环引用。

回收对象的过程

要真正的回收一个对象,至少要经历两次标记过程:如果对象在进行可达性分析后发现未与GCRoot相链接引用链,那这个对象会被第一次标记并进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。当finalize()方法未被覆盖,或是虚拟机已经调用过了finalize()方法,虚拟机认定这两种情况为“没有必要执行”,即回收对象的内存空间。如果在执行finalize()方法时,将this赋给了某一个引用,那么该对象就重生了。如果没有,那么就会被垃圾收集器清除。

finalize()方法不确定性大,开销大,无法保证顺利执行,所以要释放资源,不建议使用finalize()方法,使用try-finally或者其他方法。

回收方法区

在方法区中进行垃圾回收一般“性价比”是比较低的。在堆中,尤其是新生代中,常规应用进行一次垃圾回收可回收70%~95%的空间,方法区的垃圾回收效率远远低于此。

回收的内容

废弃常量和无用的类

如何判断废弃常量

清除废弃的常量和清除对象类似,只要常量池中的常量不被任何变量或对象引用,那么这些常量就会被清除掉。

如何判断无用的类

需要满足3个条件才能算是”无用的类“:

  • 该类所有实例都已被回收,也就是Java堆中不存在该类的任何实例;

  • 加载该类的ClassLoader已经被回收;

  • 该类对应的java.lang.Class对象没有被任何对象或变量引用 ,无法在任何地方通过反射访问该类的方法。


Java中引用的种类

Java中根据生命周期的长短,将引用分为4类。

1. 强引用

我们平时所使用的引用就是强引用。

A a = new A();

也就是通过关键字new创建的对象所关联的引用就是强引用。

只要强引用存在,该对象永远也不会被回收。

2. 软引用

只有当堆即将发生OOM异常时,JVM才会回收软引用所指向的对象。

软引用通过SoftReference类实现。

软引用的生命周期比强引用短一些。

3. 弱引用

只要垃圾收集器运行,软引用所指向的对象就会被回收。

弱引用通过WeakReference类实现。

弱引用的生命周期比软引用短。

4. 虚引用

虚引用也叫幽灵引用,它和没有引用没有区别,无法通过虚引用访问对象的任何属性或函数。

一个对象关联虚引用唯一的作用就是在该对象被垃圾收集器回收之前会受到一条系统通知。

虚引用通过PhantomReference类来实现。