记录学习路上的所见,若有纰漏还望多多包涵。 Alice程序员
垃圾(Garbage)就是程序须要回收的对象,若是一个对象不在被直接或间接地引用,那么这个对象就成为了「垃圾」,它占用的内存须要及时地释放,不然就会引发「内存泄露」。有些语言须要程序员来手动释放内存(回收垃圾),有些语言有垃圾回收机制(GC),例如我正在学习的Java语言,存在垃圾回收机制。算法
在学习Java GC 以前,咱们须要记住一个单词:stop-the-world 。它会在任何一种GC算法中发生。stop-the-world 意味着JVM由于须要执行GC而中止了应用程序的执行。当stop-the-world 发生时,除GC所需的线程外,全部的线程都进入等待状态,直到GC任务完成。GC优化不少时候就是减小stop-the-world 的发生,缩短因GC线程中断时间,保证程序运行的效率。缓存
标记清除(Mark and Sweep)是最先开发的GC算法。并发
原理:两次扫描,第一次从根开始将可能被引用的对象进行以递归的方式进行标记标记,第二次将未标记的对象进行回收,回收成功后将标记的引用对象清除标记为下次的GC回收作准备。函数
GC时间:与存活对象数和程序对象总数有关,由于要对全部对象进行递归查看是否能够进行回收。性能
思考:若是存活对象的数量少,那么标记阶段的实际标记不多,剩下的全为不在引用的对象,那么标记阶段的效率便会下降,从而整个GC过程的效率也会下降。学习
标记清除的方式节省内存可是两次扫描须要更多的时间,对于垃圾比例较小的状况占优点。优化
复制收集(Copy and Collection)会将从根开始被引用的对象复制到另外的空间中去,而后再舍弃掉旧的空间,利用新的空间。 线程
原理:复制收集的方式只须要对对象进行一次扫描。准备一个「新的空间」,从根开始,对对象进行扫,若是存在对这个对象的引用,就把它复制到「新空间中」。一次扫描结束以后,全部存在于「新空间」的对象就是全部的非垃圾对象。对象
思考:复制收集具备局部性,在复制收集的过程当中,会按照对象被引用的顺序将对象复制到新空间中。因而,关系较近的对象被放在距离较近的内存空间的可能性会提升,这叫作局部性。局部性高的状况 下,内存缓存会更有效地运做,程序的性能会提升。
复制收集更快速可是须要额外开辟一块用来复制的内存,对垃圾比例较大的状况占优点。
引用计数是指,针对每个对象,保存一个对该对象的引用计数,该对象的引用增长,则相应的引用计数增长。若是对象的引用计数为零,则回收该对象。
原理:每一个对象中保存该对象的引用计数,当引用发生增减时对计数进行更新,发生时间点:变量赋值、对象内容更新、函数结束(局部变量再也不被引用)。当一个对象的引用计数为零时则说明它再也不被引用,所以释放相应的内存空间。
思考:引用计数最大的优势就是容易实现。成本小,基本上引用计数为0的时候垃圾会被当即回收,而其余方法难以预测对象的生命周期,垃圾存在的时间都会比这个方法长。这种垃圾回收方式产生的中断时间最短。
但若是对象中存在循环引用,就没法被回收。引用计数不适合在并行中使用,多个线程同时操做引用计数,会引发数值不同的问题从而致使内存错误。因此引用计数必须采用独占方式,若是引用操做频繁,那么加锁等并发控制机制的开销是至关大的。
JVM GC只回收堆区和方法区内的对象。而栈区的数据,在超出做用域后会被JVM自动释放掉,因此其不在JVM GC的管理范围内。
断定对象是否可回收的标准:
- 对象没有引用
- 做用域发生未捕获异常
- 程序在做用域正常执行完毕
- 程序执行了System.exit()
- 程序发生意外终止(被杀线程等)
在Java程序中不能显式的分配和注销缓存,由于这些事情JVM都帮咱们作了,那就是GC。
有些时候咱们能够将相关的对象设置成null 来试图显示的清除缓存,可是并非设置为null 就会必定被标记为可回收,有可能会发生逃逸。
将对象设置成null 至少没有什么坏处,可是使用System.gc() 便不可取了,使用System.gc() 时候并非立刻执行GC操做,而是会等待一段时间,甚至不执行,并且System.gc() 若是被执行,会触发Full GC ,这很是影响性能。
Java的垃圾回收是分代回收的,分为三代:新生代,老年代,持久代。
新生代(Young generation):绝大多数最新被建立的对象都会被分配到这里,因为大部分在建立后很快变得不可达,不少对象被建立在新生代,而后“消失”。对象从这个区域“消失”的过程咱们称之为:Minor GC 。
老年代(Old generation):对象没有变得不可达,而且重新生代周期中存活了下来,在新生代的战场上奋勇存活下来的对象,是不会轻易狗带的,嘻嘻。会被拷贝到这里。其区域分配的空间要比新生代多。也正因为其相对大的空间,发生在老年代的GC次数要比新生代少得多。对象从老年代中消失的过程,称之为:Major GC 或者 Full GC。
持久代(Permanent generation)也称之为 方法区(Method area):用于保存类常量以及字符串常量。注意,这个区域不是用于存储那些从老年代存活下来的对象,这个区域也可能发生GC。发生在这个区域的GC事件也被算为 Major GC 。只不过在这个区域发生GC的条件很是严苛,必须符合如下三种条件才会被回收:
一、全部实例被回收
二、加载该类的ClassLoader 被回收
三、Class 对象没法经过任何途径访问(包括反射)
为了更好的理解GC,咱们来学习新生代的构成,它用来保存那些第一次被建立的对象,它被分红三个空间:
· 一个伊甸园空间(Eden)
· 两个幸存者空间(Fron Survivor、To Survivor)
默认新生代空间的分配:Eden : Fron : To = 8 : 1 : 1
每一个空间的执行顺序以下:
一、绝大多数刚刚被建立的对象会存放在伊甸园空间(Eden)。
二、在伊甸园空间执行第一次GC(Minor GC)以后,存活的对象被移动到其中一个幸存者空间(Survivor)。
三、此后,每次伊甸园空间执行GC后,存活的对象会被堆积在同一个幸存者空间。
四、当一个幸存者空间饱和,还在存活的对象会被移动到另外一个幸存者空间。而后会清空已经饱和的哪一个幸存者空间。
五、在以上步骤中重复N次(N = MaxTenuringThreshold(年龄阀值设定,默认15))依然存活的对象,就会被移动到老年代。
从上面的步骤能够发现,两个幸存者空间,必须有一个是保持空的。若是两个两个幸存者空间都有数据,或两个空间都是空的,那必定是你的系统出现了某种错误。
咱们须要重点记住的是,对象在刚刚被建立以后,是保存在伊甸园空间的(Eden)。那些长期存活的对象会经由幸存者空间(Survivor)转存到老年代空间(Old generation)。
也有例外出现,对于一些比较大的对象(须要分配一块比较大的连续内存空间)则直接进入到老年代。通常在Survivor 空间不足的状况下发生。
老年代空间的构成其实很简单,它不像新生代空间那样划分为几个区域,它只有一个区域,里面存储的对象并不像新生代空间绝大部分都是朝闻道,夕死矣。这里的对象几乎都是从Survivor 空间中熬过来的,它们毫不会轻易的狗带。所以,Full GC(Major GC)发生的次数不会有Minor GC 那么频繁,而且作一次Major GC 的时间比Minor GC 要更长(约10倍)。
2018.01.30 记