在java中垃圾回收是系统自动完成的,了解它对优化应用程序有很大的帮助。那么我们就从下面几个方面来了解垃圾回收机制:
给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。
优点:简单,高效,现在的objective-c用的就是这种算法。缺点:很难处理循环引用,相互引用的两个对象则无法释放。
从GC Roots(每种具体实现对GC Roots有不同的定义)作为起点,向下搜索它们引用的对象,可以生成一棵引用树,树的节点视为可达对象,反之视为不可达。
在Java语言中,可以作为GC Roots的对象包括下面几种:
真正标记以为对象为可回收状态至少要标记两次。
引用类型有四种类型分别是:强引用(Strong Reference)、软引用(soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference),其中强引用是我们常用到的。下面来说明下他们什么时候会被垃圾回收机制回收。
强引用:这种最顽强,只要有一个引用存在,永远都不会被回收掉。
软引用:一般是指还有用,但是非必须的对象。在内存空间不足的情况下,会回收掉此部分内存,如果还不够则会抛出内存溢出异常。
弱引用:一般指非必须的对象,比软引用还要弱,它只能生存到下一次垃圾回收前,如果一旦发生垃圾回收,它将会被回收掉。
虚引用:最弱的引用关系,无法通过虚引用来取得一个对象的实例。为一个对象设置虚引用的唯一目的就是能够在这个对象被回收的时候收到一个系统通知。
并行(Parallel):指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。
并发(Concurrent):指用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),用户程序在继续运行,而垃圾收集程序运行于另一个CPU上。
新生代GC(Minor GC):指发生在新生代的垃圾收集动作,因为Java对象大多都具备朝生夕灭的特性,所以Minor GC非常频繁,一般回收速度也比较快。
老年代GC(Major GC / Full GC):指发生在老年代的GC,出现了Major GC,经常会伴随至少一次的Minor GC(但非绝对的,在Parallel Scavenge收集器的收集策略里就有直接进行Major GC的策略选择过程)。Major GC的速度一般会比Minor GC慢10倍以上。
吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值,即吞吐量 = 运行用户代码时间 /(运行用户代码时间 + 垃圾收集时间)。虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%。
因为垃圾回收的时候,需要整个的引用状态保持不变,否则判定是判定垃圾,等我稍后回收的时候它又被引用了,这就全乱套了。所以,GC的时候,其他所有的程序执行处于暂停状态,卡住了。
幸运的是,这个卡顿是非常短(尤其是新生代),对程序的影响微乎其微 (关于其他GC比如并发GC之类的,在此不讨论)。
所以GC的卡顿问题由此而来,也是情有可原,暂时无可避免。
finalize需要注意地方
同一个对象的finalize 方法只会执行一次。
在给对象A赋值为空后进行手动调用gc回收内存,会执行复写了finalize方法,在finalize中再将A赋值一个非空的值,再次进行赋值为空,调用gc,之后就不会再执行finalize方法了。
这个按名字就很容易理解,就是标记哪些是可回收的,然后进行清除回收处理。标记-清除算法分为两个阶段:标记阶段和清除阶段,标记阶段主要为标记那些对象是可以回收的,清除就是回收标记过的那部分内存空间。
优点:简单,易实现
缺点:容易产生内存碎片,对于后面分配大空间时,找不到足够的空间,而主动会触发一次内存回收,增加内存回收的次数。
此方法将内存按容量分为两块,例如A、B两块,每次只使用其中的一块,当要进行回收操作时,将A中还存活的对象复制到B块中(假设上次使用A),然后对A中所有对象清空就又构成一个完整的内存块。这种方法就避免了标记清除的内存碎片问题。
优点:快速高效,不会产生内存碎片。
缺点:可用内存会减少一半,因为是按照均分的。
注意:效率也与存活对象的多少有关,如果存活对象多,复制就多,效率就低了。
标记整理法就是在标记清除方法上进行的优化,主要是在标记完成后将这些存活的对象向一端移动,然后将末尾边界后的所有内存空间清除。
优点:适合存活对象多的,不产生内存碎片
分代回收算法实际上是把复制算法和标记整理法的结合,并不是真正一个新的算法,一般分为:老年代(Old Generation)和新生代(Young Generation),老年代就是很少垃圾需要进行回收的,新生代就是有很多的内存空间需要回收,所以不同代就采用不同的回收算法,以此来达到高效的回收算法。
新生代:由于新生代产生很多临时对象,大量对象需要进行回收,所以采用复制算法是最高效的。
老年代:回收的对象很少,都是经过几次标记后都不是可回收的状态转移到老年代的,所以仅有少量对象需要回收,故采用标记清除或者标记整理算法。
所以以上整个过程就达到了最高效的回收办法。
注意:新生代与老年代分配的比例为 2:1
分代算法主要核心思想是上面两种算法的结合,但是它里面还有一些特殊的地方,所以这里进行特别的说明。
上面用到的复制算法,按照前面讲的,按照空间1:1分配的,但是分代算法中不是这样的,而是按照8:2 (4:1)的比例进行分配的,其中后面一块又等分成了两块,也就是8:1:1的比例,8为新生代(Eden区),1分别为两块等大小的Survivor区。
所有新创建的对象都是放在Eden区的,所以有很多的临时变量,故每次大部分回收的垃圾都是在Eden区的,所以Eden区会分配那么多的空间,那为什么要分两块Survivor区呢? 是因为,在进行一次coping算法回收时将Eden区中存活的对象复制到Survivor区,然后在进行一次回收时,Survivor区的存活对象没地方存放了,因为Eden区每次都有新创建的对象存在。故在新建一块Survivor B区,这时将Eden和Survivor A区存活的对象放在B区,然后清空Eden和Survivor A区。这样就相当于A和B每次回收后都有一个是全新的也就是空的,就是为了循环这种操作。
垃圾收集算法一个很大的缺点就是难以控制垃圾回收所占用的CPU时间,以及何时需要进行垃圾回收。火车算法是分代收集器所用的算法,目的是在成熟对象空间中提供限定时间的渐进收集。目前应用于SUN公司的Hotspot虚拟机上。
在火车算法中,内存被分为块,多个块组成一个集合。为了形象化,一节车厢代表一个块,一列火车代表一个集合。
注意每个车厢大小相等,但每个火车包含的车厢数不一定相等。垃圾收集以车厢为单位,收集顺序按被创建的先后顺序进行。
具体参考: http://blog.csdn.net/feier7501/article/details/21748615
常见的垃圾收集器有:serial收集器、Parallel收集器、Parallel Old 垃圾收集器、CMS(Concurrent Mark-Sweep)收集器、G1收集器.其中Serial收集器为串行收集器,其他均为并行收集器。
串行垃圾回收器(Serial Garbage Collector)
并行垃圾回收器(Parallel Garbage Collector)
并发标记扫描垃圾回收器(CMS Garbage Collector)
G1垃圾回收器(G1 Garbage Collector)
最古老,最稳定,简单而高效,可能会产生较长的停顿。
Serial是一个 单线程
的收集器,它不仅仅只会使用一个CPU或一条线程去完成垃圾收集工作,并且在进行垃圾收集的同时,必须暂停其他所有的工作线程,直到垃圾收集结束。
Serial垃圾收集器虽然在收集垃圾过程中需要暂停所有其他的工作线程,但是它简单高效,对于限定单个CPU环境来说,没有线程交互的开销,可以获得最高的单线程垃圾收集效率,因此Serial垃圾收集器依然是java虚拟机运行在Client模式下默认的新生代垃圾收集器。
Serial Old是Serial垃圾收集的老年代版本。它同样是个单线程的收集器,使用标记-整理算法,这个收集器也主要是运行在Client默认的java虚拟机默认的年老代垃圾收集器。
ParNew收集器其实就是Serial收集器的 多线程
版本,除了使用多条线程进行垃圾收集之外,其余行为包括Serial收集器可用的所有控制参数、收集算法、Stop The World、对象分配规则、回收策略等都与Serial收集器完全一样,在实现上,这两种收集器也共用了相当多的代码。ParNew收集器是许多运行在Server模式下的虚拟机中首选的新生代收集器。
Parallel Scavenge是一个新生代收集器,使用多线程和复制算法。相比其他收集器,只有这个收集器是针对系统吞吐量进行改进,适用于后台运算并且交互不多的程序。其他收集器则更关注改善收集时的停顿时间,适用于用户交互的程序。
Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。在注重吞吐量以及CPU资源敏感的场合,都可以优先考虑Parallel Scavenge加Parallel Old收集器。
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。目前很大一部分的Java应用集中在互联网站或者B/S系统的服务端上,这类应用尤其重视服务的响应速度,希望系统停顿时间最短,以给用户带来较好的体验。CMS收集器是基于“标记—清除”算法实现的。整个过程需要下面四个步骤。
优点:并发收集、低停顿。
缺点:
CMS收集器对CPU资源非常敏感,以为在并发阶段占用一部分线程(CPU资源),导致应用程序变慢,总吞吐量变低。CMS默认启动的回收线程数是 (CPU数量+3)/4
,也就是当CPU在4个以上时,并发回收时垃圾收集线程不少于25%的CPU资源,并且随着CPU数量的增加而下降。
CMS收集器无法处理浮动垃圾,可能出现“Concurrent Mode Failure”失败而导致另一次Full GC的产生。也是由于在垃圾收集阶段用户线程还需要运行,那也就还需要预留有足够的内存空间给用户线程使用,因此CMS收集器不能像其他收集器那样等到老年代几乎完全被填满了再进行收集,需要预留一部分空间提供并发收集时的程序运作使用。要是CMS运行期间预留的内存无法满足程序需要,就会出现一次“Concurrent Mode Failure”失败,这时虚拟机将启动后备预案:临时启用Serial Old收集器来重新进行老年代的垃圾收集,这样停顿时间就很长了。
CMS是一款基于“标记—清除”算法实现的收集器,这意味着收集结束时会有大量空间碎片产生。空间碎片过多时,将会给大对象分配带来很大麻烦,往往会出现老年代还有很大空间剩余,但是无法找到足够大的连续空间来分配当前对象,不得不提前触发一次Full GC。
浮动垃圾:由于CMS并发清理阶段用户线程还在运行着,伴随程序运行自然就还会有新的垃圾不断产生,这一部分垃圾出现在标记过程之后,CMS无法在当次收集中处理掉它们,只好留待下一次GC时再清理掉,本地无法清理的垃圾则称为浮动垃圾。
G1收集器是当前收集器技术发展最前沿的成果,一款面向服务端应用的垃圾收集器。基于标记-整理算法,也就是说不会产生内存碎片,可以精确控制停顿。基本不牺牲吞吐量的前提下完成低停顿的内存回收。这是由于它将新生代、老年代划分为多个区域,并维护一个每个区域收集的优先列表,保证了在有限的时间内可以获得最高的收集效率。收集的范围是整个JAVA堆。而不是在区分新生代,老年代。
执行过程:
在G1之前的其他收集器进行收集的范围都是整个新生代或者老年代,而G1不再是这样。使用G1收集器时,Java堆的内存布局就与其他收集器有很大差别,它将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分Region(不需要连续)的集合。
G1收集器之所以能建立可预测的停顿时间模型,是因为它可以有计划地避免在整个Java堆中进行全区域的垃圾收集。G1跟踪各个Region里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region(这也就是Garbage-First名称的来由)。这种使用Region划分内存空间以及有优先级的区域回收方式,保证了G1收集器在有限的时间内可以获取尽可能高的收集效率
ParNew收集器在单CPU的环境中绝对不会有比Serial收集器更好的效果,甚至由于存在线程交互的开销,该收集器在通过超线程技术实现的两个CPU的环境中都不能百分之百地保证可以超越Serial收集器。然而,随着可以使用的CPU的数量的增加,它对于GC时系统资源的有效利用还是很有好处的。
Parallel Scavenge收集器的特点是它的关注点与其他收集器不同,CMS等收集器的关注点是尽可能地缩短垃圾收集时用户线程的停顿时间,而Parallel Scavenge收集器的目标则是达到一个可控制的吞吐量(Throughput)。由于与吞吐量关系密切,Parallel Scavenge收集器也经常称为“吞吐量优先”收集器。
Parallel Scavenge收集器与ParNew收集器的一个重要区别是它具有自适应调节策略。
GC自适应的调节策略(GC Ergonomics):
Parallel Scavenge收集器有一个参数-XX:+UseAdaptiveSizePolicy。当这个参数打开之后,就不需要手工指定新生代的大小、Eden与Survivor区的比例、晋升老年代对象年龄等细节参数了,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量,这种调节方式称为GC自适应的调节策略(GC Ergonomics)。
了解这些是为了解决实际问题,Java虚拟机会把每次触发GC的信息打印出来来帮助我们分析问题,所以掌握触发GC的类型是分析日志的基础。
GC_FOR_MALLOC表示是在堆上分配对象时内存不足触发的GC。
GC_CONCURRENT当我们应用程序的堆内存达到一定量,或者可以理解为快要满的时候,系统会自动触发GC操作来释放内存。
GC_EXPLICIT表示是应用程序调用System.gc、VMRuntime.gc接口或者收到SIGUSR1信号时触发的GC。
GC_BEFORE_OOM表示是在准备抛OOM异常之前进行的最后努力而触发的GC。
配置 | 描述 |
---|---|
-XX:+UseSerialGC | 串行垃圾回收器 |
-XX:+UseParallelGC | 并行垃圾回收器 |
-XX:+UseConcMarkSweepGC | 并发标记扫描垃圾回收器 |
-XX:ParallelCMSThreads= | 并发标记扫描垃圾回收器, =为使用的线程数量 |
-XX:+UseG1GC | G1垃圾回收器 |
配置 | 描述 |
---|---|
-Xms | 初始化堆内存大小 |
-Xmx | 堆内存最大值 |
-Xmn | 新生代大小 |
-XX:PermSize | 初始化永久代大小 |
-XX:MaxPermSize | 永久代最大容量 |
java -Xmx12m -Xms3m -Xmn1m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseSerialGC -jar java-application.jar
内存回收与垃圾收集器在很多时候都是影响系统性能,并发能力的主要因素之一,虚拟机之所以提供多种不同的收集器及大量的参数调节,是因为只有根据具体实际需要,实现方式选择最优的收集方式才能保证最优的性能。没有固定收集器、参数组合,也没有最优的调优方法,虚拟机也没有什么必然的内存回收行为。因为直到现在为止还没有最好的收集器出现,更加没有万能的收集器。
新生代收集器:Serial、ParNew、Parallel Scavenge
老年代收集器:CMS、Serial Old、Parallel Old
收集器适用场景:
用户交互:ParNew、CMS
高吞吐量:Parallel Scavenge
上面有7中收集器,分为两块,上面为新生代收集器,下面是老年代收集器。如果两个收集器之间存在连线,就说明它们可以搭配使用。
用文字描述收集器可配合使用结果如下:Serial/Serial Old、Serial/CMS、ParNew/Serial Old、ParNew/CMS、Parallel Scavenge/Parallel Old、Parallel Scavenge/Serial Old、CMS/Serial Old
-XX:+<option> 启用选项 -XX:-<option> 不启用选项 -XX:<option>=<number> -XX:<option>=<string>
参数 | 描述 |
---|---|
-XX:+UseSerialGC | Jvm运行在Client模式下的默认值,打开此开关后,使用Serial + Serial Old的收集器组合进行内存回收 |
-XX:+UseParNewGC | 打开此开关后,使用ParNew + Serial Old的收集器进行垃圾回收 |
-XX:+UseConcMarkSweepGC | 使用ParNew + CMS + Serial Old的收集器组合进行内存回收,Serial Old作为CMS出现“Concurrent Mode Failure”失败后的后备收集器使用。 |
-XX:+UseParallelGC | Jvm运行在Server模式下的默认值,打开此开关后,使用Parallel Scavenge + Serial Old的收集器组合进行回收 |
-XX:+UseParallelOldGC | 使用Parallel Scavenge + Parallel Old的收集器组合进行回收 |
-XX:SurvivorRatio | 新生代中Eden区域与Survivor区域的容量比值,默认为8,代表Eden:Subrvivor = 8:1 |
-XX:PretenureSizeThreshold | 直接晋升到老年代对象的大小,设置这个参数后,大于这个参数的对象将直接在老年代分配 |
-XX:MaxTenuringThreshold | 晋升到老年代的对象年龄,每次Minor GC之后,年龄就加1,当超过这个参数的值时进入老年代 |
-XX:UseAdaptiveSizePolicy | 动态调整java堆中各个区域的大小以及进入老年代的年龄 |
-XX:+HandlePromotionFailure | 是否允许新生代收集担保,进行一次minor gc后, 另一块Survivor空间不足时,将直接会在老年代中保留 |
-XX:ParallelGCThreads | 设置并行GC进行内存回收的线程数 |
-XX:GCTimeRatio | GC时间占总时间的比列,默认值为99,即允许1%的GC时间,仅在使用Parallel Scavenge 收集器时有效 |
-XX:MaxGCPauseMillis | 设置GC的最大停顿时间,在Parallel Scavenge 收集器下有效 |
-XX:CMSInitiatingOccupancyFraction | 设置CMS收集器在老年代空间被使用多少后出发垃圾收集,默认值为68%,仅在CMS收集器时有效,-XX:CMSInitiatingOccupancyFraction=70 |
-XX:+UseCMSCompactAtFullCollection | 由于CMS收集器会产生碎片,此参数设置在垃圾收集器后是否需要一次内存碎片整理过程,仅在CMS收集器时有效 |
-XX:+CMSFullGCBeforeCompaction | 设置CMS收集器在进行若干次垃圾收集后再进行一次内存碎片整理过程,通常与UseCMSCompactAtFullCollection参数一起使用 |
-XX:+UseFastAccessorMethods | 原始类型优化 |
-XX:+DisableExplicitGC | 是否关闭手动System.gc |
-XX:+CMSParallelRemarkEnabled | 降低标记停顿 |
-XX:LargePageSizeInBytes | 内存页的大小不可设置过大,会影响Perm的大小,-XX:LargePageSizeInBytes=128m |
~ | 新生代GC方式 | 老年代和持久代GC方式 |
---|---|---|
-XX:+UseSerialGC | Serial 串行GC | Serial Old 串行GC |
-XX:+UseParallelGC | Parallel Scavenge 并行回收GC | Serial Old 并行GC |
-XX:+UseConcMarkSweepGC | ParNew 并行GC | CMS并发GC, 当出现“Concurrent Mode Failure”时采用Serial Old 串行GC |
-XX:+UseParNewGC | ParNew 并行GC | Serial Old 串行GC |
-XX:+UseParallelOldGC | Parallel Scavenge 并行回收GC | Parallel Old 并行GC |
-XX:+UseConcMarkSweepGC -XX:+UseParNewGC | Serial 串行GC | CMS 并发GC 当出现“Concurrent Mode Failure”时采用Serial Old 串行GC |
参考文章:
理解Java垃圾回收机制(1)
理解Java垃圾回收机制(2)
深入理解JVM : Java垃圾收集器