垃圾回收
判断对象已死
引用计数算法可达性分析算法(java使用这一种) 垃圾收集算法垃圾收集器分代收集(Generational Collection)相关概念
内存划分新生代老年代永久代Minor GC和 Full GC的区别 Minor GC、Major GC和Full GC
垃圾回收Garbage Collection(GC)
判断对象已死 引用计数算法引用计数算法是在对象中加入一个计数器,当对象被引用,计数器+1,当引用失效,计数器-1这种算法实现简单,效率高,但是有一个严重的问题会导致内存泄漏,那就是对象之间循环引用,比如说A对象持有B对象的引用,B对象持有A对象的引用,那么A和B的计数器值永远>=1,也就是说这两个对象永远不会被回收,这是一堆垃圾。 可达性分析算法(java使用这一种)
Java中定义了一些起始点,称为GC Root,当有对象引用它的时候,就把对象挂载在它下面,形成一个树状结构,当一个对象处于一个这样的树里时,就认为此对象是可达的,反之是不可达。
GC ROOT:
虚拟机栈中引用的对象:
public static void main(String[] args) {
Rumenz a = new Rumenz();
a = null;
}
a是栈帧中的本地变量,a就是GC Root,由于a=null,a与new Rumenz()对象断开了链接,所以对象会被回收。
方法区类的静态成员引用的对象:
public class Rumenz{
public static Rumenz r;
public static void main(String[] args){
Rumenz a=new Rumenz();
a.r=new Rumenz();
a=null;
}
}
栈帧中的本地变量a=null,由于a断开了与GC Root对象(a对象)的联系,所以a对象会被回收。由于给Rumenz的成员变量r赋值了变量的引用,并且r成员变量是静态的,所以r就是一个GC Root对象,所以r指向的对象不会被回收。
方法区常量引用的对象:
public class Rumenz{
public static final Rumenz r=new Rumenz();
public static void main(String[] args){
Rumenz a=new Rumenz();
a=null;
}
}
常量r引用的对象不会因为a引用的对象的回收而被回收。
本地方法栈中JNI(Java Native Interface)引用的对象
垃圾收集算法标记清除:
算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象(引用计数法或者可达性分析),在标记完成后统一回收掉所有被标记的对象。它是最基础的收集算法,后续的收集算法都是基于这种思路并对其缺点进行改进而得到的。两个缺点:一个是效率问题,标记和清除过程的效率都不高;另外一个是空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致,当程序在以后的运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
复制:
复制算法在标记清除算法的基础上,针对内存碎片问题做了一下优化,此算法把内存分为大小相同的两块,每次在使用的时候只使用其中的一块。
当一块内存用完的时候。把存活对象复制到另外的一块中,然后清除当前这块中的所有的对象,如此反复。缺点:使用当前算法,解决了内存碎片化严重的问题,但是存在缺陷就是每次只使用一半的空间,空间利用率受到影响。同时对于存活周期长的对象,复制次数多。
标记整理:
复制收集算法在对象存活率较高时就要执行较多的复制操作,效率将会变低。更关键的是,如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选用这种算法。根据老年代的特点,有人提出了另外一种“标记-整理”(Mark-Compact)算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
分代收集算法:
GC分代的基于一个假设:绝大部分对象的生命周期都非常短暂,存活时间短。“分代收集”(Generational Collection)算法,把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记-清理”或“标记-整理”算法来进行回收。
垃圾收集器
如果两个收集器之间存在连线,那说明它们可以搭配使用。虚拟机所处的区域说明它是属于新生代收集器还是老年代收集器。没有最好的垃圾收集器,更加没有万能的收集器,只能选择对具体应用最合适的收集器。
Serial收集器:
采用复制算法的单线程的收集器,单线程一方面意味着它只会使用一个CPU或一条线程去完成垃圾收集工作,另一方面也意味着它进行垃圾收集时必须暂停其他线程的所有工作,直到它收集结束为止。(STW[stop the work])Serial收集器依然是虚拟机运行在Client模式下的默认新生代收集器。
ParNew收集器:
ParNew收集器其实就是Serial收集器的多线程版本,除了使用多条线程进行垃圾收集外,其余行为和Serial收集器完全一样,包括使用的也是复制算法。是Server模式下的虚拟机首选的新生代收集器除了Serial收集器外,目前只有它能与CMS收集器配合工作ParNew收集器在单CPU的环境中绝对不会有比Serial收集器更好的效果,甚至由于线程交互的开销,该收集器在两个CPU的环境中都不能百分之百保证可以超越Serial收集器。随着可用CPU数量的增加,它对于GC时系统资源的有效利用还是很有好处的。它默认开启的收集线程数与CPU数量相同,在CPU数量非常多的情况下,可以使用-XX:ParallelGCThreads参数来限制垃圾收集的线程数。
Parallel Scavenge收集器:
Parallel Scavenge收集器也是一个新生代收集器,也是用复制算法的收集器,也是并行的多线程收集器。Parallel Scavenge收集器的目标则是达到一个可控制的吞吐量。所谓吞吐量的意思就是CPU用于运行用户代码时间与CPU总消耗时间的比值,即吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间),虚拟机总运行100分钟,垃圾收集1分钟,那吞吐量就是99%。另外,Parallel Scavenge收集器是虚拟机运行在Server模式下的默认垃圾收集器。高吞吐量则可以高效率利用CPU时间,尽快完成运算任务,主要适合在后台运算而不需要太多交互的任务。虚拟机提供了-XX:MaxGCPauseMillis和-XX:GCTimeRatio两个参数来精确控制最大垃圾收集停顿时间和吞吐量大小。不过不要以为前者越小越好,GC停顿时间的缩短是以牺牲吞吐量和新生代空间换取的。由于与吞吐量关系密切,Parallel Scavenge收集器也被称为“吞吐量优先收集器”。
Serial Old收集器:
Serial收集器的老年代版本,同样是一个单线程收集器,使用“标记-整理算法”,这个收集器的主要意义也是在于给Client模式下的虚拟机使用。
Parallel Old收集器:
Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法在注重吞吐量以及CPU资源敏感的场合,都可以优先考虑Parallel Scavenge收集器+Parallel Old收集器的组合。
CMS收集器:
CMS(Conrrurent Mark Sweep)收集器是以获取最短回收停顿时间为目标的收集器。使用标记 - 清除算法1.初始标记:标记GC Roots能直接关联到的对象,时间很短;2.并发标记:进行GCRoots Tracing(可达性分析)过程,时间很长;3.重新标记:修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,时间较长;4.并发清除:回收内存空间,时间很长。并发标记与并发清除两个阶段耗时最长,但是可以与用户线程并发执行。
G1收集器:
并行和并发。使用多个CPU来缩短Stop The World停顿时间,与用户线程并发执行。分代收集。独立管理整个堆,但是能够采用不同的方式去处理新创建对象和已经存活了一段时间、熬过多次GC的旧对象,以获取更好的收集效果。空间整合。基于标记 - 整理算法,无内存碎片产生。可预测的停顿。能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒。使用G1收集器时,Java堆的内存布局与其他收集器有很大差别,它将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分(可以不连续)Region的集合。 分代收集(Generational Collection)相关概念
内存划分Java虚拟机将堆内存划分为新生代、老年代和永久代,永久代是HotSpot虚拟机特有的概念,它采用永久代的方式来实现方法区,其他的虚拟机实现没有这一概念,而且HotSpot也有取消永久代的趋势,在JDK 1.7中HotSpot已经开始了“去永久化”,把原本放在永久代的字符串常量池移出。永久代主要存放常量、类信息、静态变量等数据,与垃圾回收关系不大,新生代和老年代是垃圾回收的主要区域。
新生代新生成的对象优先存放在新生代中,新生代对象朝生夕死,存活率很低,在新生代中,常规应用进行一次垃圾收集一般可以回收70% ~ 95% 的空间,回收效率很高。HotSpot将新生代划分为三块,一块较大的Eden空间和两块较小的Survivor空间,默认比例为8:1:1。划分的目的是因为HotSpot采用复制算法来回收新生代,设置这个比例是为了充分利用内存空间,减少浪费。新生成的对象在Eden区分配(大对象除外,大对象直接进入老年代),当Eden区没有足够的空间进行分配时,虚拟机将发起一次Minor GC。GC开始时,对象只会存在于Eden区和From Survivor区,To Survivor区是空的(作为保留区域)。GC进行时,Eden区中所有存活的对象都会被复制到To Survivor区,而在From Survivor区中,仍存活的对象会根据它们的年龄值决定去向,年龄值达到年龄阀值(默认为15,新生代中的对象每熬过一轮垃圾回收,年龄值就加1,GC分代年龄存储在对象的header中)的对象会被移到老年代中,没有达到阀值的对象会被复制到To Survivor区。接着清空Eden区和From Survivor区,新生代中存活的对象都在To Survivor区。接着, From Survivor区和To Survivor区会交换它们的角色,也就是新的To Survivor区就是上次GC清空的From Survivor区,新的From Survivor区就是上次GC的To Survivor区,总之,不管怎样都会保证To Survivor区在一轮GC后是空的。GC时当To Survivor区没有足够的空间存放上一次新生代收集下来的存活对象时,需要依赖老年代进行分配担保,将这些对象存放在老年代中。 老年代
在新生代中经历了多次(具体看虚拟机配置的阀值)GC后仍然存活下来的对象会进入老年代中。老年代中的对象生命周期较长,存活率比较高,在老年代中进行GC的频率相对而言较低,而且回收的速度也比较慢。
永久代永久代存储类信息、常量、静态变量、即时编译器编译后的代码等数据,对这一区域而言,Java虚拟机规范指出可以不进行垃圾收集,一般而言不会进行垃圾回收。
Minor GC和 Full GC的区别新生代GC(Minor GC):Minor GC指发生在新生代的GC,因为新生代的Java对象大多都是朝生夕死,所以Minor GC非常频繁,一般回收速度也比较快。当Eden空间不足以为对象分配内存时,会触发Minor GC。老年代GC(Full GC/Major GC):Full GC指发生在老年代的GC,出现了Full GC一般会伴随着至少一次的Minor GC(老年代的对象大部分是Minor GC过程中从新生代进入老年代)。Full GC的速度一般会比Minor GC慢10倍以上。当老年代内存不足或者显式调用System.gc()方法时,会触发Full GC。 Minor GC、Major GC和Full GC
JVM在进行GC时,并非每次对三个内存(新生代、老年代、方法区)区域一起回收,大部分时候回收的都是新生代。
针对Hotspot VM 的实现,它里面的GC按照回收区域分为两种大类:一种是部分收集(Partial GC),另一种是整堆收集(Full GC)
Minor GC:
当年轻代(Eden区)满时就会触发 Minor GC,这里的年轻代满指的是 Eden区满。Survivor 满不会触发 Minor GC 。内存池被填满的时候,其中的内容全部会被复制,指针会从0开始跟踪空闲内存。Eden 和 Survivor 区进行了标记和复制操作,取代了经典的标记、扫描、压缩、清理操作。所以 Eden 和 Survivor 区不存在内存碎片。写指针总是停留在所使用内存池的顶部。执行 Minor GC 操作时,不会影响到永久代。从永久代到年轻代的引用被当成 GC roots,从年轻代到永久代的引用在标记阶段被直接忽略掉。对于大部分应用程序,Minor GC 操作时应用程序停顿导致的延迟都是可以忽略不计的。因为,大部分 Eden 区中的对象都能被认为是垃圾,永远也不会被复制到 Survivor 区或者老年代空间。如果正好相反,Eden 区大部分新生对象不符合 GC 条件,Minor GC 执行时暂停的时间将会长很多。
Major GC:
CMS收集器中,当老年代满时会触发 Major GC。目前,只有CMS收集器会有单独收集老年代的行为。其他收集器均无此行为。通常能单独发生收集行为的只是新生代的Minor GC,所以这里“反过来”的情况只是理论上允许,实际上除了CMS收集器,其他都不存在只针对老年代的收集。
Full GC:
Full GC 对收集整堆(新生代、老年代)和方法区的垃圾收集。当年老代满时会引发Full GC,Full GC将会同时回收新生代、年老代 ;当永久代满时也会引发Full GC,会导致Class、Method元信息的卸载 。调用System.gc时,系统建议执行Full GC,但是不一定会执行 。



