JVM垃圾回收算法

前言

  程序计数器,虚拟机栈,本地方法3个区域随线程而生,随线程而灭,栈中的栈帧随着方法的进入和退出有条不紊地执行着出栈和入栈操做,每个栈帧中分配多少内存,基本上是在类结构肯定下来就已知。所以这几个区域的内存分配和回收都具有肯定性。在这几个区域就不须要考虑太多回收问题。垃圾收集器主要关注于Java堆和方法区。算法

1、如何判断对象是否存活?

  首先说为何要判断是否存活,当垃圾收集器在对堆进行回收前,第一就是要肯定对象哪些是还在被引用的或者后面还须要被引用的,即存活,哪些是已经“死去”(即不可能再被任何途径使用)性能

一、引用计数算法spa

  在对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1,引用失效时就减1.任什么时候刻计数器为0的对象就是不可能再被使用的。这个方法效率挺高,大部分状况下也是很不错的算法。操作系统

  可是在JVM中会很难解决对象之间相互循环引用的问题,就若是两个对象之间相互调用,这时候就会发生相似死锁的状况,即这个地方相互调用会使得引用计数法始终认为有对象在引用当前对象,就一直计数值大于或等于1,也就没法通知GC收集器回收它们。可是实际的状况是这两个对象后面已经再也不调用,因此这个方法虽然简单高效,但不是咱们的首选。虚拟机也不是经过这个算法来判断对象是否存活的线程

二、可达性分析算法对象

  使用一系列的GC Roots的对象(包括:虚拟机栈中引用的对象,方法区中类静态属性引用的对象,方法区中常量引用的对象,本地方法栈中JNI引用的对象)做为起点,从节点开始向下搜索,当没有被GCRoots连接到的对象就能够回收,以下图的对象4和5就判断为可回收对象。blog

  

 

  在JDK1.2以后,Java对引用这个概念进行了扩充,也就是对象不只仅只有引用和没有引用两个概念,而是扩展到了4个:进程

  强引用:相似于“Object obj=new Object()”只要强引用在,垃圾收集器永远不会回收掉被引用的对象。内存

  软引用:是用来描述一些还有用可是并不是必需的对象,对于软引用对象,在内存溢出异常以前,会把这些对象列进回收范围之中进行第二次回收。虚拟机

  弱引用,比软引用更弱一点,被弱引用关联的对象只能生存到下一次垃圾收集发生以前。当垃圾收集发生时不管内存是否足够,都会只回收弱引用的对象。

  虚引用,最弱的引用关系,对象是否有虚引用对其生存时间是没有影响的。惟一目的就是能在这个对象被收集器回收时收到一个系统通知

  对象要想真正宣告“死亡”须要至少两次的标记过程,当对象在可达性分析时候发现没有被GC Roots链到那么对象就会进行第一次标记而且进行第一次筛选,筛选的条件就是判断该对象有没有必要执行finalize()方法,须要执行的话就会把对象放入F-Queue的对列中去执行该对象中的finalize()方法。若是finalize()方法让对象从新被GC Roots链到那么对象就从新活下来,不然就会进行第二次标记,等待垃圾回收的到来

2、垃圾收集算法

  目前来讲Java堆中的对象是分为新生代和老年代,对于新生代中的对象采用的是复制算法清理

一、复制算法

  它将可用内存空间划分为一块较大的Eden空间和两块较小的From Survivor(S0)和To Survivor(S1)空间。每次使用时只使用Eden和其中一块S区。好比此次使用的是S0区。回收时将Eden和S0区中的中还存活的对象一次性复制到S1中最后再清理Eden和S0中的对象,HotSpot虚拟机默认Eden:S0:S1之间大小比例是8:1:1,这是由于新生代中对象大多数甚至98%的都是“朝生夕死”。若是S区的大小不够那么就会依赖老年代的内存进行分配担保。

二、对象重新生代变成老年代的断定方法

  每经历一次Minor GC(复制算法回收对象)就会让对象的年龄加一,当对象年龄为15时就会把新生代的对象放入老年代中。

  若是Survivor区中的存放不下的对象就会放入老年代中:对象会优先在Eden区中分配,然后经过一次Minor GC就让对象进入Survivor区中,当Survivor区中存放不下该对象时就会将该对象放入老年代。

  新生成的大对象也会直接放入老年代中(能够经过-XX:+PretenuerSizeThreshold设置)超过这个size的对象一辈子成就会放入老年代。

 

 

三、标记—清理与标记—整理算法

  在老年代中由于对象存活率高,没有额外的空间对它进行分配担保,因此会采用标记—清理或标记—整理算法来进行回收对象

  标记—清理算法:首先标记出全部须要回收的对象,在标记完成以后统一回收全部标记的对象

 

  标记—整理算法:先标记全部可回收对象,让存活的对象向一端移动,而后直接清理掉端边界之外的内存

  上面的标记过程都是根据可达性分析算法中对象标记断定来实现的。

3、内存设置参数

  上面介绍了这么多,那咱们到底怎么操做里面的一些参数呢?

  -Xms:初始堆大小,JVM 启动的时候,给定堆空间大小。

  -Xmx:最大堆大小,JVM 运行过程当中,若是初始堆空间不足的时候,最大能够扩展到多 少。

  -Xmn:设置年轻代大小。整个堆大小=年轻代大小+年老代大小+持久代大小。持久代一 般固定大小为 64m,因此增大年轻代后,将会减少年老代大小。此值对系统性能影响较大, Sun 官方推荐配置为整个堆的 3/8。

  -Xss:设置每一个线程的 Java 栈大小。JDK5.0 之后每一个线程 Java 栈大小为 1M,之前每 个线程堆栈大小为 256K。根据应用的线程所需内存大小进行调整。在相同物理内存下,减 小这个值能生成更多的线程。可是操做系统对一个进程内的线程数仍是有限制的,不能无限生成。

  -XX:NewSize=n:设置年轻代大小

  -XX:NewRatio=n:设置年轻代和年老代的比值。如:为 3,表示年轻代与年老代比值为 1: 3,年轻代占整个年轻代+年老代和的 1/4

  -XX:SurvivorRatio=n:年轻代中 Eden 区与两个 Survivor 区的比值。注意 Survivor 区有两个。 如:3,表示 Eden:Survivor=3:2,一个 Survivor 区占整个年轻代的 1/5

  -XX:MaxPermSize=n:设置持久代大小 

  -XX:MaxTenuringThreshold:设置垃圾最大年龄。若是设置为 0 的话,则年轻代对象不经 过 Survivor 区,直接进入年老代。对于年老代比较多的应用,能够提升效率。若是将此值设置为一个较大值,则年轻代对象会在 Survivor 区进行屡次复制,这样能够增长对象再年轻代 的存活时间,增长在年轻代即被回收的几率。

相关文章
相关标签/搜索