垃圾回收
-
判断对象是否已死
(1) 引用计数算法:给每一个对象中添加一个引用计数器,即每当一个地方引用此对象时,则计数器就加1,当引用失效时,则计数器就减1;任何时刻计数器为0的对象是不可能再被使用。
- 优点:引用计数算符实现比较简单,判断效率高
- 缺点:很难解决对象之间循环引用的问题。
注意:目前主流的Java虚拟机并没有采用引用计数算法来管理内存。
(2) 可达性分析算法:通过一系列的称为"GC Roots"的对象作为起始点从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链,则证明此对象时不可用的。
在Java语言中,可以作为GC Roots的对象包括以下几种:
- 虚拟机栈(栈帧中的本地变量表)中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中(Native方法)引用的对象
-
引用的分类
(1) 背景:传统中理解一个对象只有被引用或是没有被引用两种状态,但是对于如何描述 "食之无味,弃之可惜"的对象无能力。我们希望描述这样一类对象:当内存空间还足够时,则能保留在内存中,如果内存空间在进行垃圾回收之后还是非常紧张,则可以抛弃这些对象(很多系统的缓冲功能都符合这样的应用场景)。
(2) 在JDK1.2之后,把对象的引用分为四种级别,从而使程序能更加灵活的控制对象的生命周期:
- 强引用(Strong Reference)
- 软引用(Soft Reference)
- 弱引用(Weak Reference)
- 虚引用(Phantom Reference)
(3) 引用的级别由高到低:强引用 > 软引用 > 弱引用 > 虚引用
-
强引用:是程序中使用最普遍的引用,类似"Object obj = new Object();"这类引用,只要强引用还存在,垃圾回收器永远不会回收掉强引用中的对象,即使当内存空间不足时,JVM抛出 OutOfMemoryError,也不回收。
-
软引用:用来描述一些还有用但并非必需的对象。如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。在JDK1.2之后,提供了SoftReference类实现软引用。
-
弱引用:也是用来描述一些并非必需的对象。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否, 都会回收它的内存。在JDK1.2之后,提供了WeakReference类实现弱引用。
-
虚拟引用:顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收,也无法通过虚拟引用来获取一个实例对象。为一个对象设置虚拟引用关联的唯一目的是让当前这个对象被垃圾回收器回收时收到一个通知。在JDK1.2之后,提供了PhantomReference类实现虚拟引用。
-
不同区域的垃圾回收
方法区:即HotSpot虚拟机中的永久代,永久代的垃圾收集主要回收两部分:废弃常量和无用的类对象。
-
废弃常量:假设字符串常量"abc"已经进入常量池中,但是当前系统没有任何一个String类型引用指向"abc"常量,也没有其他地方使用"abc"字面值常量,如果发生内存回收,而且又有必要的情况下,会对"abc"常量进行清除。
-
无用的类对象:
-
该类所有的实例对象都已经被回收,即Java堆中不存在任何该类的实例
-
加载该类的ClassLoader已经被回收
-
该类对应的java.lang.Class对象没有任何地方被引用,无法在任何地方
通过反射访问该类的方法
注意:此处无用对象满足3个条件可以回收,但是不是必须。是否需要被回收可以通过 -Xnoclassgc 参数进行控制;同时还可以使用-XX:+TraceClassLoading查看类加载的信息。
-
堆区:尤其是在新生代的垃圾回收中,常规应用进行一次垃圾回收一般可以回收70%~95%空间。
-
堆内存分配图:
-
简述:新生区是对象的创建、应用、消亡的区域,一个对象在这里产生、应用、最终被垃圾回收器收集,消亡。新生区又分为两部分:伊甸区和幸存者区。所有新创建的对象(new) 都是在伊甸区; 幸存者分为两个:幸存者0区和1区,当伊甸区的空间用完时,程序 需要创建新的对象 ,JVM对象伊甸区开始进行垃圾回收,应用的是YGC,将伊甸区不再使用的对象进行销毁,然后将伊甸区剩余的对象移到幸存者0区,0区 满了,对0区进行垃圾销毁,存活的对象移到幸存者1区,如果1区也满了,则再将1区的移动到养老区;如果养老区也 满了,此时将JVM将开启 FullGC(简称:FGC),进行 养老区的内存清理。但是 如果执行Full GC之后 依然无法保存 新的对象,则产生OOM异常:堆内存溢出。
-
-
垃圾回收的算法
(1) 标记-清除算法(Mark-Sweep):它是最基础的垃圾回收算法,其他算法都是基于这种思想而改进的。标记-清除算法分为 “标记” 和 “清除” 两个阶段:首先标记出所需要回收的对象,在标记完后统一回收所有被标记的对象。
-
描述如下:
-
缺点:
(1) 标记和清除两个过程效率都不高
(2) 标记清除后会产生大量的不连续的内存碎片,后续会发生大对象找不到可利用空间的问题
(2) 复制算法(Copying):它将可用内存分为两块,每次只用其中的一块;当这块内存用完以后,将还存活的对象复制到另一块上面,然后再把已经使用的内存空间一次清理掉。
-
描述如下:
-
分析:这种算法虽然实现简单,内存效率高,不易产生碎片,但是最大的问题是可用内存被压缩到了原本的一半。且存活对象增多的话,Copying算法的效率会大大降低。
-
应用:目前商业版的虚拟机都采用复制算法回收新生代,新生代中的98%的对象是"朝生夕死",所以将堆内存分为一块较大的Eden空间和两块较小的Servivor(幸存者)空间,HotSpot虚拟机默认Eden和Servivor大小比例为8:1。每一次使用Eden和其中一块Servivor,当回收时,将Eden和Servivor中还存活着的对象一次性的复制到另外一块Servivor,最后清理掉Eden和使用过的Servivor空间。
(3) 标记-整理算法(Mark-Compact):标记操作和”标记-清除“算法一样,后续操作变成不直接清理对象,而是在清理无用对象的时候完成让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
- 描述如下:
- 分析:不会产生内存碎片,在标记的基础上需要移动对象,还是会降低效率的。
- 应用:老年代的对象存活率较高,一般采用此收集算法进行回收。
(4) 分代收集算法(Generational Collection):目前商业虚拟机的垃圾回收都是采用的 “分代收集”,它的核心思想是根据对象存活的生命周期将内存划分为若干个不同的区域。一般情况下将堆区划分为老年代(Tenured Generation)和新生代(Young Generation),老年代的特点是每次垃圾收集时只有少量对象需要被回收,而新生代的特点是每次垃圾回收时都有大量的对象需要被回收,那么就可以根据不同代的特点采取最适合的收集算法。
- 大部分JVM的GC对于新生代都采取Copying算法
- 老生代因为每次只回收少量对象,因而采用Mark-Compact算法
-