Bootstrap

垃圾回收简介

GC是垃圾收集的意思,内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java提供的GC功能可以自动监测对象是否超过作用域从而达到自动回收内存的目的,Java语言没有提供释放已分配内存的显示操作方法。Java程序员不用担心内存管理,因为垃圾收集器会自动进行管理。要请求垃圾收集,可以将相关对象设置为null或调用System.gc() ,但后者将会严重影响代码性能,因为一般每一次显式调用System.gc()都会停止所有的响应,去检查内存中是否有可回收的对象。这样会对程序的正常运行造成极大的威胁。另外,调用该方法并不能保证JVM立即进行垃圾回收,仅仅是通知JVM要进行垃圾回收了,具体回收与否完全由JVM决定。这样做是费力不讨好。

垃圾回收可以有效的防止内存泄露,有效的使用可以使用的内存。垃圾回收器通常是作为一个单独的低优先级的线程运行,不可预知的情况下对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收,程序员不能实时的调用垃圾回收器对某个对象或所有对象进行垃圾回收。在Java诞生初期,垃圾回收是Java最大的亮点之一,因为服务器端的编程需要有效的防止内存泄露问题,然而时过境迁,如今Java的垃圾回收机制已经成为被诟病的东西。移动智能终端用户通常觉得iOS的系统比Android系统有更好的用户体验,其中一个深层次的原因就在于Android系统中垃圾回收的不可预知性。

有两种算法可以判定对象是否存活:
1.引用计数算法:给对象中添加一个引用计数器,每当一个地方应用了对象,计数器加1;当引用失效,计数器减1;当计数器为0表示该对象已死、可回收。但是它很难解决两个对象之间相互循环引用的情况。
2.可达性分析算法:通过一系列称为“GC Roots”的对象作为起点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连(即对象到GC Roots不可达),则证明此对象已死、可回收。Java中可以作为GC Roots的对象包括:虚拟机栈中引用的对象、本地方法栈中Native方法引用的对象、方法区静态属性引用的对象、方法区常量引用的对象。
在主流的商用程序语言(如我们的Java)的主流实现中,都是通过可达性分析算法来判定对象是否存活的。

新生代:包含Eden与两个Survivor,刚刚新建的对象在Eden中,经历一次Minor GC,Eden中的存活对象就会被移动到第一块survivor space S0,Eden被清空;等Eden区再满了,就再触发一次Minor GC,Eden和S0中的存活对象又被送入第二块survivor space S1,S0和Eden被清空,然后下一轮S0与S1交换角色,如此循环往复。如果对象的复制次数达到16次,该对象就会被送到老年代中。

老年代:如果某个对象经历了几次垃圾回收之后还存活,就会被存放到老年代中。老年代的空间一般比新生代大。

Minor GC:发生在新生代,频率高,速度快(大部分对象活不过一次Minor GC)
Major GC:发生在老年代,速度慢
Full GC:清理整个堆空间

在垃圾收集过程中,可能会将对象移动到不同区域:
  - 伊甸园(Eden):这是对象最初诞生的区域,并且对大多数对象来说,这里是它们唯一存在过的区域。
  - 幸存者乐园(Survivor):从伊甸园幸存下来的对象会被挪到这里。
  - 终身颐养园(Tenured):即老年代,这是足够老的幸存对象的归宿。年轻代收集(Minor-GC)过程是不会触及这个地方的。当年轻代收集不能把对象放进终身颐养园时,就会触发一次完全收集(Major-GC),这里可能还会牵扯到压缩,以便为大对象腾出足够的空间。
  与垃圾回收相关的JVM参数:
  -Xms / -Xmx — 堆的初始大小 / 堆的最大大小
  -Xmn — 堆中新生代的大小,实际可用空间 = Eden + 1个Survivor,即90%
       -Xss — 每个线程堆栈的大小,一般来说如果栈不是很深的话,1M绝对够用了
       -XX:NewSize / XX:MaxNewSize — 设置新生代大小/新生代最大大小
  -XX:NewRatio — 新生代与老年代的比例,2代表新生代占整个堆空间的1/3,老年代占2/3

  -XX:-DisableExplicitGC — 让System.gc()不产生任何作用
  -XX:+PrintGCDetails — 打印GC的细节
  -XX:+PrintGCDateStamps — 打印GC操作的时间戳
  -XX:PrintTenuringDistribution — 设置每次新生代GC后输出幸存者乐园中对象年龄的分布
  -XX:InitialTenuringThreshold / -XX:MaxTenuringThreshold:设置老年代阀值的初始值和最大值
  -XX:TargetSurvivorRatio:设置幸存区的目标使用率


常见的垃圾回收方式如下:

标记清除法:这是垃圾收集算法中最基础的,分为标记和清除两个阶段,它的思想是标记哪些要被回收的对象,然后统一回收。这种方法很简单,但是效率不高,标记和清除的效率都很低;此外会产生大量不连续的内存碎片,从而导致以后程序在分配较大对象时由于没有充足的连续内存而提前触发一次 GC 操作。

复制算法:为了解决效率问题,复制算法将可用内存按容量划分为相等的两部分,然后每次只使用其中的一块,当一块内存用完后就将还存活的对象复制到第二块内存上,然后一次性清除完第一块内存,再将第二块上的对象复制到第一块。但是这种方式内存的代价太高,每次基本上都要浪费一半的内存;于是将该算法进行了改进,内存区域不再是按照 1:1 去划分,而是将内存划分为 8:1:1 三部分,较大那份内存是 Eden 区,其余是两块较小的内存区叫 Survior 区,每次都会优先使用 Eden 区,若 Eden 区满则将对象复制到第二块内存区上,然后清除 Eden 区,如果此时存活的对象太多,以至于 Survivor 不够时,会将这些对象通过分配担保机制复制到老年代中。

标记整理法:这种方法主要是为了解决标记清除法产生大量内存碎片的问题;当对象存活率较高时,也解决了复制算法的效率问题。它的不同之处就是在清除对象的时候先将可回收对象移动到一端,然后清除掉端边界以外的对象,这样就不会产生内存碎片了,但是会增加停顿时间。

分代收集法:现在的虚拟机垃圾收集大多采用这种方式,它根据对象的生存周期,将堆分为新生代和老年代。在新生代中,由于对象生存期短,每次回收都会有大量对象死去,那么这时就采用复制算法,新生代又分为Eden和两个Survivor,大小比例默认8:1:1。老年代里的对象存活率较高,没有额外的空间进行分配担保,所以可以使用标记整理法或标记清除法。
大对象直接进入老年代:JVM中有个参数配置-XX:PretenureSizeThreshold,令大于这个设置值的对象直接进入老年代,目的是为了避免在Eden和Survivor区之间发生大量的内存复制。
------------------------------------------
Java对象使用后设置为null并不会减少内存占用,设置为null只是栈中指向的引用为null,但是new出来的对象还是存在于堆里面的,需要等到GC算法调用时才能将没有栈指向的对象给回收掉。
---------------------------------------------------------------------------------------------
垃圾回收的作用:(1)释放没用的对象;(2)清除内存记录碎片。
垃圾回收的优点:(1)自动释放内存空间;(2)减轻编程负担,提高编程效率。
垃圾回收的缺点:(1)开销影响性能(Jave虚拟机必须跟踪程序中的有用对象,才可以确定哪些对象是无用对象,并最终释放无用对象);(2)垃圾回收算法的不完备性(算法不能保证100%收集到所有废弃内存)。
垃圾回收的特点:(1)工作目标是回收无用对象的内存空间,这些对象都是JVM堆内存里的内存空间,对其它资源无能为力;(2)为了更快地回收某些对象,可以将其引用指向null;(3)垃圾回收发生的不可预知性;(4)垃圾回收的精确性主要包括两方面,一是垃圾回收机制能够精确地标记活着的对象,二是垃圾回收器能够精确定位对象之间的引用关系,前者是完全回收所有废弃对象的前提,否则会有内存泄露,后者是实现归并和复制算法的必要条件,用于对象重新分配,减少内存碎片产生。

转载于:https://www.cnblogs.com/yuanfei1110111/p/10134371.html

;