做笔记,没有太详细,图很少。如果有兴趣可以进一步交流
垃圾回收
1.什么样的对象需要被GC
判断算法 (理论,方法论)
1.引用计数算法
引用计数法就是当一个对象B引用对象A的时候,对象A会有个引用计数,
当A的引用计数不为0的时候是不会被回收的。这样会存在问题,如果A引用B, B也引用A这样的话AB2个对象什么时候都不会被回收。
2.可达性分析 GC Roots
它会分析GC Root到各个对象中是否可达,如果可达将不会被回收,否则就会被回收,比如AB都是可达的,C不是那么C将会被回收。
如果AB2个相互可达会只要沒有其他GC Root对AB可达那么他们会一起被回收。
什么情况下可以成为GC Root?
1.虚拟机栈中本地变量表引用的对象
2.方法区中
类静态变量引用的对象
常量引用的对象
3.本地方法栈中JNI引用的对象
不可达是不是就一定会被回收?
不会,可以通过finalize()让对象再次可达
引用:
强引用:
强引用就是指在程序代码之中普遍的存在,类似“Object obj=new Object()”
这类的引用只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。
软引用:
软引用是用来描述一些还有用但并非必须的对象,对应软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收,如果这次回收还没有足够内存才会抛出内存溢出异常。
弱引用:
弱引用是用来描述非必须对象的,无论当前内存是否足够都会被回收。
虚引用:
最弱的一种引用关系,完全会对其他生产时间构成有影响,业务费通过虚引用来气的一个对象实例,唯一目的就是再回收时收到一个系统通知。
2. 垃圾收集算法
1. 标记-清除算法
算法分为“标记”和“清除”两个阶段:首先标记处所有需要回收的对象,在标记完成后统一回收所有标记的对象。
主要的2个不足:
1. 一个是效率问题:标记和清除2个过程的效率都不高
2. 一个是空间问题:标记清除后会产生大量不连续的内存碎片,空间碎片大多可能会导致以后再程序运行过程中需要分配大对象时,无法找到足够的连续内存而不得不提前出发另一次垃圾回收动作
2. 标记-整理算法
标记整理算法跟“标记-清除”算法类似,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让存活的对象都向另一端移动,然后直接清理掉边界的内存。
3. 复制回收算法
它将可用内存按容量划分为大小相等的2块,每次只使用其中一块,当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把使用过的内存空间一次清理掉。这样使得每次都是对整个半区进行内存回收,没存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,内存分配时也不用考虑内存碎片等复杂情况,只要移动堆顶指针,按照顺序分配内存即可,实现简单,运行高效,
缺点:这种算法的代价是将内存缩小为原来的一半,内存利用率太小。
4. 分带收集算法
这种算法并没有什么新的思想,只是根据对象的存活周期的不同将内存划分为几块。一般是把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记-清理”或“标记-整理”算法来进行回收。方法区永久代,回收方法同老年代。
IBM公司的专门研究表明,新生代中的对象98%是“朝生夕死”的,所有并不需要按照1:1的比例来划分内存空间,每次使用Eden和其中一块Survivor。当回收时,将Eden和Survivor中还存活着的对象一次性地复制到另外一款Survivor空间上,最后清理掉Eden和刚才使用过的Survivor空间。HostSpot虚拟机默认Eden和Survivor的大小比例是8:1,也就是每次新生代中可用内存空间为整个新生代容量的90%(80%+10%)只有10的内存会被“浪费”掉。当然,98%的对象可回收只是一般场景下的数据,我们没办法保证每次回收都只有不多余10%的对象存活,
当Survivor空间不够用时需要依赖其他内存(这里指老年代)进行分配担保。
Java 1.7有永久代的概念,java1.8之后就改成了Meta Space是使用本地内存来存储类元数据信息。这样就意味着不再会有内存溢出等问题了,但是呢当Meta Space中的数据越来越大时会占用原本不属于java的内存,影响机器效率。
流程:当系统创建对象的时候,总是的eden区操作,如果这个区满了那么就会触发一次YoungGC也就是年轻代的垃圾回收,这个时候就会把还有用的放入S0区,清理干净Eden区,当前Eden区再次被用完就会再次触发YoungGC,然后会将Eden和s0区还在使用的对象复制到s1区中,下一次YoungGC则是将Eden去和s1区还在使用的对象放入s0区。如此反复,有些对象在s区和Eden区来回移动几次之后(默认15次可以配置)将被复制到老年代,当老年代也存放不下的时候回触发一次Full GC回收年轻代和老年代。
年轻代使用复制回收算法,老年代使用标记整理算法
Jvm内存模型
3. 垃圾收集器
并行:指多条垃圾收集线程并行工作,但此时用户线程任然处于等待状态
并发:指用户线程与垃圾手机线程同时执行,(但并不一定是并行的,可能会交替执行)用户程序在继续运行,而垃圾手机程序运行与另一个CPU上。
1. Serial收集器(新生代,串行,复制回收算法)
它在进行垃圾收集时,必须暂停其他所有的工作线程(用户线程)。是Jvm client模式下默认的新生代收集器。对于限定单个CPU的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得最高的单线程收集效率。
Serial收集器对于运行在Client模式下的虚拟机来说是一个很好的选择。
2. Parnew收集器(新生代,并行,复制回收算法)
其实就是serial收集器的多线程版本,除了使用多条线程进行垃圾收集之外,其余行为与Serial收集器一样。
3. Parallel Scavenge 收集器(新生代,并行,复制回收算法)
类似ParNew,但更加关注吞吐量
特点是它的关注点与其他收集器不同,CMS等收集器的关注点是尽可能地缩短垃圾收集时用户线程的停顿时间,而parallel Scavenge收集器的目标则是达到一个可控制的吞吐量。吞吐量= 程序运行时间/(程序运行时间 + 垃圾收集时间),虚拟机总共运行了100分钟。其中垃圾收集花掉1分钟,那吞吐量就是99%。
Parallel Scavege收集器还提供了一个参数:
-XX:UseAdaptiveSizePolicy:这是一个开关参数,当这个参数打开之后,就不需要手工指定新生代的大小,(-Xmn)、Eden与Survivor区的比列(-XX:SurvivorRatio)、晋升老年代对象的年龄(-XX:PretenureSizeThreshold)等细节参数了,虚拟机会根据监测结果自动分配,这种方式称之为GC自适应调节策略。
4. Serial Old收集器(串行,标记整理)
是Serial的老年代版本,也是一个单线程收集器,使用标记整理算法。
这个收集器的主要作用也是用于在Client模式下使用,如果是Server模式下,它主要还有两大用途,一是在JDK1.5以及之前的版本可以与Parallel Scavege收集器搭配使用,而是作为CMS收集器的后备预案,在并发收集发生Concurrent Mode Failure时使用。
5. Parallel Old收集器(并行,标记整理)
Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。
6. CMS收集器
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。CMS收集器是基于“标记-清除”
整个收集过程:
1. 初始标记:
初始标记仅仅只是标记出GC ROOTS能直接关联到的对象,速度
很快。
2. 并发标记:
主要标记过程,标记全部对象。对GC TOOTS对象做进一步的是否可达运
算。并发的,不会导致用户线程停顿,但是会占用一部分线程而导致应用程序变慢,总吞吐量会降低。
3. 重新标记:
由于并发标记时,用户线程依然运行,因此在正式清理前,再做修正。
针对并发标记过程中其他还在运行的线程做一次重新标记。
4. 并发清除:
基于重新标记结果,直接清理对象。
优点:
并发收集,低停顿
缺点:
是对CPU资源非常敏感,无法处理浮动垃圾,收集结束会产生大量空间碎片。
如果CMS运行期间预留的内存无法满足程序需要,就会出现一次“Concurrent Mode Failur”失败,这是虚拟机将会启动后备预案:临时启用|Serial Old收集器来重新进行老年代的垃圾收集,这样停顿时间就长了。
7. G1收集器
堆内存被划分为固定大小的多个区域.每个heap区(Region)的大小在JVM启动时就
确定了. JVM 通常生成 2000 个左右的heap区, 根据堆内存的总大小,区的size范围允许为 1Mb 到 32Mb.
heap区可以分配为 Eden, Survivor, 或 old generation(老年代)区. 此外,还有第四种类型的对象被称为巨无霸区域(Humongous regions),这种巨无霸区是设计了用来保存比标准块(standard region)大50%及以上的对象, 它们存储在一组连续的区中. 最后一个类型是堆内存中的未使用区(unused areas).
G1相对于CMS的区别在:
1. G1在压缩空间方面有优势
2. G1通过将内存空间分成区域(Region)的方式避免内存碎片问题
3. Eden, Survivor, Old区不再固定、在内存使用效率上来说更灵活
4. G1可以通过设置预期停顿时间(Pause Time)来控制垃圾收集时间避免应用雪崩现象
5. G1在回收内存后会马上同时做合并空闲内存的工作、而CMS默认是在STW(stop the world)的时候做
6. G1会在Young GC中使用、而CMS只能在O区使用
G1收集过程:
1. 初始标记:
仅仅只标记一下GC Roots能直接关联到的对象。需要停止用户线程,但是时
间很短。
2. 并发标记:
从GC root开始对堆中对象进行可达性分析,找到存活对象,此阶段耗时很长,
但可以与用户程序并发执行。
3. 再次标记:
为了修正在并发编辑期间因用户程序继续运作而导致标记成圣变动的那一部
分标记记录,此阶段可并行。
4. 拷贝清理
G1选择“活跃度(liveness)”最低的区域, 这些区域可以最快的完成回收. 然后这些区域和年轻代GC在同时被垃圾收集 . 在日志被标识为 [GC pause (mixed)]. 所以年轻代和老年代都在同一时间被垃圾收集.
G1运行期间不会产生内存空间碎片,收集后能提供规整的可用内存。
可预测的停顿:
G1除了追求底停顿外还建立可预测的停顿时间模型,能让使用者明确指定一个长度为M毫秒的试卷片段内,消耗在垃圾收集上的时间不得超过N毫秒。
它有计划的避免在整个java对中进行全区域的垃圾收集,G1跟踪哥哥Region里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需要时间的经验值),在后台委会一个有限列表,每次根据允许的手机时间,优先回收最大Region。这种使用Region划分内存空间以及有优先级的区域回收方式,保证了G1收集器在有限的时间内可以获取尽量可能高的收集效率。