目录
第3章垃圾收集器和内存分配策略
1.对象是否存活的判断
GCRoot的对象包括下面几种:
引用的四种类型
生存还是死亡:
回收方法区
垃圾收集算法
分代收集算法:
垃圾收集器:
常用JVM配置参数
内存分配和回收策略
动态对象年龄判定
Minor GC 和 Full GC 触发条件
空间分配担保
方法一
引用计数算法:对于一个对象A,只要有任何一个对象引用了A,则A的引用计数器就加1,当引用失效时,引用计数器就减1。只要对象A的引用计数器的值为0,则对象A就不可能再被使用。
缺点:很难解决对象之间相互循环引用的问题。引用和去引用伴随加法和减法,影响性能 。
方法二:
可达性分析算法:通过一系列的称为“GC Roots”的对象作为起始点,这些节点开始行下搜索,搜索所走过的路径称为引用链,当一个对象到GC Root没有任何引用链相连时,则这个对象不可达,证明此对象时不可用的。
1.虚拟机栈中引用的对象
2.方法区中类静态属性引用的对象
3.方法区中常量引用的对象
4.本地方法栈中JNI引用的对象
1.强引用:在程序代码中普遍存在的,如 Object obj=new Object();只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。
2.软引用:用来描述还有用但非必须的对象,系统将要发生内存溢出之前,就会把这些对象列进回收范围之中,进行回收。
3.弱引用:描述非必须的对象,比软引用更弱,该对象只能生存到下一次gc发生之前。无论当前内存是否足够。
4.虚引用:是一种最弱的引用关系。一个对象的生存时间不会受虚引用的影响,也无法通过虚引用来取得一个对象实例。虚引用存在的唯一目的就是:在对象被收集器回收时收到一个系统通知。
可达性算法中不可达的对象,真正宣告死亡,至少要经历两次标记过程。如果对象在进行可达性分析后发现没有与GC Root相连接的引用链时,那它将会第一次标记并且进行第一次筛选,筛选条件是此对象是否有必要执行finize()方法,当对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机吊用过,都视为没有必要执行。是对象呗判定为必要执行finalize()方法,那么这个对象将会放置在一个F队列中,并由一个虚拟机自动建立一个低优先级的finalizer线程去执行他,但并不承诺等待他运行结束。对象要在finalize()中成功拯救自己,只要重新与引用链上的任意一个对象建立关联即可。
第二次标记时它将会被移除即将回收的集合,如果他这时还没有逃脱,基本上就真的被回收了
永久代(方法区)的垃圾收集主要回收两部分内容:废弃常量和无用的类。
废弃常量回收:1、如果没有任意对象引用该常量的话,该常量称为废弃常量
2、如果常量成为废弃常量,并且此时发生必要的内存回收时,该常量就会被回收。
判断一个类是否是“无用的类”:1.该类的所用的是实例都已经被回收。
2.加载该类的classloader已经被回收
3.该类对应的java.lang.class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
满足以上3条无用类可以进行回收,是否进行回收还要看虚拟机提供的参数。在大量使用反射,动态代理,CGlib等框架,动态生成jsp以及osgi这类频繁自定义class'loader的场景都需要虚拟机具备卸载的功能。
1.标记-清楚算法:算法分为“标记”和“清楚”两个阶段。首先标记处所有需要回收的对象,在标记完成之后统一回收所有被标记的对象。
不足之处:1.效率问题:标记和清楚两个过程的效率都不高。
2.空间问题:标记清楚之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发一次垃圾收集动作。如图
2.复制算法:它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当一块内存用完了,就将还存活着的对象复制到另一块上面,然后再把已使用过的内存空间一次清理掉。
优点:每次都是对整个半区进行内存回收,内存分配时就不用考虑内存碎片等复杂问题。只要移动堆顶指针按顺序分配内存就可以了,简单高效。
缺点:内存缩小为原来的一半。
改进:在新生代对象中大部分对象都是“朝生夕死”,所以我们把内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中一块Survivor,当回收时,将Eden和刚才用过的Survivor空间中还存活的对象一次性复制到另一块Survivor中,最后清理掉Eden和刚才用过的Survivor空间。hotspot虚拟机默认Eden和survivor的大小比例是8:1。当Survivor空间不够用时,这些对象将直接进入老年代。
3.标记-整理:首先标记所有需要回收的对象,然后让所有存活的对象都向一端移动,然后直接清理掉边界以外的内存。
java堆分为新生代和老年代,在新生代,由于每次收集都有大批对象死去,只有少量存活,就选用复制算法。在老年代中因为对象存活率高,没有额外空间对它进行分配担保,就必须使用“标记-整理”和"标记-清理"算法来回收。
一.Serial收集器(串行收集器)
1.Serial收集器是最基本,发展历史最悠久的收集器,jdk1.3以前是虚拟机新生代收集的唯一的选择。
2.他是一个单线程收集器,所谓的单线程是指他进行垃圾收集时,必须暂停其他所有的工作线程直到它收集结束。可能导致较长的停顿。
3.新生代采用复制算法,老年代(Serial Old)采用标记-整理算法
(是虚拟机运行在client模式下的默认新生代收集器)
使用参数:-XX:+UseSerialGC
二.ParNew收集器(并行收集器)
1.ParNew收集器其实是Serial收集器的多线程版本(并行版本)。
2.新生代是并行的(多线程的),老年代是串行的(单线程的),新生代采用复制算法,老年代采用标记整理算法
使用参数:-XX:UseParNewGC
限制线程数量:-XX:ParallelGCThreads
三、Parallel收集器
1.一种新生代收集器(Parallel Scanvenge),使用复制算法的收集器,而且是并行的多线程收集器。
2.Paralle收集器特点是它的关注点与其他收集器不同,更加关注吞吐量(所谓吞吐量就是cpu用于运行用户代码的时间与cpu总消耗时间的比值)。(高吞吐量主要用户后台交互比较少的任务)
3.Parallel Old是parallel收集器的老年代版本,使用多线程和标记-整理算法。
使用参数:-XX:+UseParallelGC
四、CMS收集器
1.CMS收集器是一种以获取最短回收停顿时间为目标的收集器。
2.CMS收集器是基于标记-清除算法实现的,是一种老年代收集器,通常与ParNew一起使用
3.它的运行过程相对前面几种收集器来说更复杂一些,整个过程分为4步:
初始标记:需要“stop the world”,初始标记仅仅只是标记一下GC Root能关联到的对象,速度很快。
并发标记:是主要标记过程,全部对象,这个标记过程是和用户线程并发执行的。
重新标记:需要“stop the world”,为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录
(停顿时间比初始标记长,但比并发标记短得多)
并发清除:和用户线程并发执行的,基于标记结果,直接清理对象。
特点:1、并发收集,低停顿
2、对cpu资源非常敏感。在并发阶段,由于与用户线程并发执行,所以会影响系统整体吞吐量和性能。
3.清理不彻底,CMS收集器无法处理浮动垃圾,(浮动垃圾:并发清理用户线程产生的垃圾,CMS无法在当次收集中处理掉他们,只好留到下一次GC再处理)
4.CMS收集器不像其他收集器那样等到老年代几乎完全被填满了再进行收集,需要预留一部分空间供用户线程程序运行使用。使用-xx:CMSInitiatingOccupancyFration的值来设置触发GC阈值
5.由于是标记清除算法收集结束时会有大量的空间碎片产生,会出现老年代有大量空间,但无法找到足够大的连续的空间来分配当前对象,不得不提前触发一次Full GC。(CMS提供了一个开关:-XX:UseCMSCompactAtFullCollection 开关参数,用于在CMS收集器顶不住要进行FUll GC时开启)内存碎片合并整理过程,整理过程是独占的,耗时长。
五、G1收集器
http://blog.jobbole.com/109170/
1.最前沿的收集器
2.G1将新生代和老年代取消了,取而代之的是将堆划分为若干的区域,仍然属于分代收集器,区域的一部分包含新生代,新生代采用复制算法,老年代采用标记-整理算法
具备以下特点:
并行与并发:G1能充分利用多cpu,多核环境下的硬件优势,来缩短stop-the-world,是并发的收集器。
分代收集:G1不需要其他收集器就能独立管理整个GC堆,他能够采用不同的方式去处理新建对象、存活一段时间的对象和熬过多次GC的对象。
空间整合:与CMS标记-清除算法不同,G1从整体来是基于标记-整理算法,从局部上看基于复制算法实现,G1运作期间不会产生内存空间碎片
可预测的停顿:可以预测停顿时间。
1、-XX:+printGC:打印GC的简要信息
2、-XX:PrintGCDetails打印GC的详细信息
3、-XX:PrintGCTimeStamps 打印GC发生的时间戳
4、-Xloggc:log/gc.log 指定GC log的位置,文件输出方式
5、-XXTracingClassLoading 监控类的加载
-Xmx 20M指定最大堆(20m) -Xms 5m 指定最小堆(5m)
6.-XX:OnOutOfMemoryError="" 当发生outofmemory错误时,执行一个指定位置的脚本
7.-Xmn设置新生代大小
8、-XX:NewRatio=4 设置新生代和老年代的比值 (1/4)
9、-XX:ServivorRatio=8 设置servivor区和eden区的比值(1/8)
10、-XX:PermSize设置永久区的初始空间,-XX:MaxPermSize设置永久区的最大空间
对象在堆上内存分配:
1.在线程的本地分配缓冲区(每个线程可以在堆中预先分配得到一片区域,作为本地线程分配缓冲区(TLAB)):如果启动了本地线程分配缓冲。当该线程执行时,有对象创建的话,就该在该线程的TLAB中分配缓冲,当该线程的TLAB用完了才申请堆中的空闲内存。
2.在Eden区:大多数情况下,对象都会在新生代的Eden区中分配内存,而因为新生代对象大部分是朝生夕死的,所以新生代会频繁的进行垃圾回收。
3.在老年区:需要大量连续空间的对象,会直接在老年代分配内存。长期存活的对象晋入老年区
-XX:PretenureSizeThreshold=3145728(3M) 大于这个预设值的对象将直接在老年代分配
-XX:MaxTenuringThreshold=15 设置晋升老年代的年龄阈值(15岁)
虚拟机并不是永远地要求对象的年龄必须达到了MaxTenuringThreshold才能晋升老年代,如果Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象直径进入老年代,无需等到阈值要求的年龄
Minor GC:新生代的GC,值发生在新生代的垃圾收集动作。
Minor GC触发条件:当Eden区满的的时候,触发Minor GC
Major GC/Full GC:指发生在老年代的GC
Full GC触发条件:1.调用System.gc时,系统建议执行Full GC,但是不是必然执行
2.老年代空间不足。
3.方法区空间不做
4.通过Minor GC后进入老年代的平均大小大于老年的可用内存
5.由Eden区,from Survivor区向To Survivor区复制是,由于大小大于to survivor区的大小,则让对象进入老年区,但是老年区大小小于这部分对象的大小则会发生Full GC。
在发生Minor GC之前,虚拟机会先检查老年代最大可用连续空间是否大于新生代所有对象总空间,如果条件成立,则这次minor GC确保安全,如果不成立,则虚拟机会查看HandlePromotionFalture(=true/false) 设置值是否允许担保失败,如果运行,那么会继续检查老年代最大可用连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次Minor GC,尽管有风险,如果小于或者HandlerPromotionFailtrue设置不允许,那这时要改为进行一次Full GC。