JVM(二)-垃圾回收与内存分配

参考:《深入理解JVM——高级特性与最佳实践》

一、对象生存周期

程序计数器、虚拟机栈、本地方法栈随线程生灭,不需要GC。GC关注的主要是java堆和方法区。

引用计数法

缺点:很难解决对象之间相互循环引用的问题。

可达性分析算法

通过一系列程为“GC Roots”的对象作为起始点,从这些及诶单开始向下搜索,所走过的路径称为“引用链-Reference Chain”。

可作为GC Roots的对象包括下面几种:

  1. 虚拟机栈(栈帧中的本地变量表)中的引用对象

  2. 方法区中类静态属性引用的对象

  3. 方法区中常量引用的对象

  4. 本地方法中JNI(即native方法)引用的对象

引用:reference类型的数据中存储的数值代表的是另外一块内存的起始地址。

分为

  • 强引用(只要强引用还存在,关联的永远不会被回收)、

  • 软引用(系统将要OOM时,将关联的对象列进回收范围进行二次回收)、

  • 弱引用(无论当前内存是否够用,都回收)、

  • 虚引用(为一个对象设置虚应用的唯一目的就是对象被回收时收到一个系统通知)

reference类型:Java的数据类型分为两类:primitive和reference类型。primitive类型可以hold数字和布尔数据;reference类型可以hold对象,接口和数组类型的数据的指针。可以想象后一种数据比较复杂,往往是一段数据,不像primitive的数据是比较单纯的数据单元。

java堆中对象回收过程:可达性分析之后,第一次标记-->第一次筛选,对象覆盖finalize()方法且没有执行过-->对象放置在F-Queue队列-->由一个徐及其自动建立、低优先级的Finalize线程执行-->GC进行第二次标记,此时如果对象重新与引用链上任何一个对象建立关联,则被移除“即将回收”集合。

也就是说一个对象的finalize()被执行,但依然可以存活。

注意:任何一个对象的finalize()方法都只被系统自动调用一次,如果兑现面临下一次回收,finalize方法不会再次执行。因此只能“自救”一次。

System.gc():代码显示调用GC.-XX:+DisableExplicitGC,这个参数作用是禁止代码中显示调用GC。如果加上了这个JVM启动参数,那么代码中调用System.gc()没有任何效果,相当于是没有这行代码一样。

方法区废弃常量回收:没有任何对象引用常量池中这个常量,则被清出常量池

方法区类的回收:判断废弃类需要同时满足三个条件——①所有实例已被回收②加载该类的Class、Loader已回收③该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

而具体是否回收,由参数控制:-Xnoclassgc -verbese:class -XX:+TraceClassLoading -XX:+TraceClassUnLoading

二、GC算法

标记-清除算法:先标记,后清除

缺点:①标记和清除的效率都不高②会产生大量不连续的内存碎片

复制算法:内存划分为两块,每次只使用其中的一块。一块的内存用完就把存活的对象复制到另一块,然后清空自己。

缺点:将内存缩小为原来的一半。

运用:回收新生代,不需要按1:1划分内存。

标记-整理:让所有存活的对象向一端移动。适用于老年代。

分代收集法:把java堆分为新生代和老年代。

三、HotSpot算法实现

枚举根节点:可达性分析GC Roots节点主要在全局性引用(常量或静态属性)与执行上下文(栈帧中的本地变量表)中,如果要逐个检查会消耗很多时间;GC停顿(在分析期间整个执行系统被冻结在某个时间点)也会影响时间的敏感性。

精确GC:准确区分指针和非指针,虚拟机直接得知哪些地方存放着对象引用。OopMap记录对象内什么偏移量上是什么数据类型。

safePoint:程序执行时并非在所有地方都能停下来开始GC,只有在安全点才能停下来。一般是方法调用、循环跳转、异常跳转等“长时间执行”的指令。

抢断式中断:GC发生时所有线程都中断,然后恢复那些不在安全点的线程,跑到安全点。------不建议使用

主动式中断:设置中断标志,线程跑到安全点时轮询这个标志,自己中断挂起。

saveRegion:一段代码片段中,引用关系不发生变化,包括线程处于sleep和blocked的状态。

线程执行到安全区,标识自己,那么当GC时就不用管已标识的线程了;在线程需要离开安全区时,会检查GC是否已经完成。

四、垃圾收集器

Serial:单线程,stop the world。新生代Serial,复制算法;老年代SerialOld,标记整理算法。可控参数:Eden与Survivor区的比例-XX:SurvivorRatio、晋升老年代对象年龄-XX:PretenureSizeThreshold、-XX:HandlePromotionFailure

parNew:Serial的多线程版本。使用-XX:+UseConcMarkSweepGC选项后的默认新生代回收器,也可以使用-XX:UseParNewGC强制指定。默认开启的收集线程数与CPU数量相同,在CPU非常多(32个),可以使用-XX:ParallelGCThreads来限制垃圾回收线程数。

并行(Parallel):多条垃圾回收线程并行工作,用户线程等待。

并发(Concurrent):用户程序继续运行,垃圾收集程序运行于另一个CPU上。

Parallel Secvenge:复制算法,并行多线程。CMS等收集器主要是尽可能缩短GC时间,而Parallel Scavenge是达到一个可控制的吞吐量(=运行用户代码时间/(运行用户代码时间+GC时间))。最大垃圾收集停顿时间(大于0的ms)-XX:MaxGCPauseMillis,吞吐量大小(大于0小于100的整数):-XX:GCTimeRatio,打开-XX:UseAdaptiveSizePolicy参数就不需要指定Xmn,-XX:SurvivorRatio和-XX:PretenureSizeThreshold等细节参数了。虚拟机会根据当前运行情况收集性能动态调整最合适的停顿时间或最大吞吐量,称为自适应调节策略(GC Ergonomics)。

Serial Old:作为CMS的后备预案,在并发收集发生Concurrent Mode Failure 时使用。

Parallel Old:使用多线程和“标记-整理”算法。

CMS(Concurrent Mark Sweep):标记-清除,目标获取最短回收停顿时间。

  • GC过程如下:
  1. 初始标记:stop the world, 只标记GC Roots能直接关联到的对象。
  2. 并发标记:GC Roots Tracing的过程,与用户线程一起工作。
  3. 重新标记:stop the world,修正并发标记期间因用户程序运行而导致的标记变动。停顿时间大于初始标记小于并发标记。
  4. 并发清除:与用户线程一起工作。
  • 缺点:
  1. 在并发阶段,虽然不会导致用户线程停顿,但是会因为占用了一部分线程而导致应用程序变慢,吞吐量会降低。
  2. 无法处理浮动垃圾(并发阶段产生的、本次GC无法回收的垃圾),可能出现“concurrent mode failure”失败而导致另一次Full GC的产生。CMS不能等老年代几乎填满再回收,需要预留一部分给并发收集时的程序使用,因此提高参数-XX:CMSInitiatingOccupancyFraction的值来提高出发百分比(默认68%),以便减低回收次数提高性能。要是运行期间预留内存无法满足需求,发生“concurrent mode failure”,启动Serial Old后备预案。
  3. 碎片空间过多,给大对象分配带来麻烦,需要执行FullGC。-XX:+UseCMSCompactAtFullCollection用于在CMS顶不住要进行FullGC时开启内存碎片的合并整理过程。碎片整理时间无法并发,停顿时间变长;提供参数-XX:CMSFullGCBeforeCompaction,设置执行多少次不压缩的Full GC后执行一次压缩的(默认为0,每次都压缩)。

G1:最新成果。优点:并行与并发、分代收集、空间整合、可预测的停顿。

将整个Java堆划分为多个大小相等的独立区域Region,新、老声带不再是物理隔离,是一部分不需要连续的Region的集合。G1跟踪每个Region中垃圾堆积的价值大小(回收获得的空间大小以及所需要的时间)在后台维护一个优先劣列表。

RemenberedSet:每一个Region都有一个对应的RemenberedSet,虚拟机发现爱你程序在对Refenrence类型的数据进行写操作时,会产生一个WriteBarrier暂时中断写操作,检查Reference引用的对象是否处于不同的Region中。如果是,把引用信息记录到被引用对象所属Region的RemenberedSet中。GC时可保证不对全堆进行扫描也不会遗漏。

  • GC过程如下:
  1. 初始标记:stop the world, 标记GC Roots能直接关联到的对象,修改TAMS值,让下一阶段用户程序并发运行时能在正确可用的Region中创建新对象。

  2. 并发标记:GC Roots Tracing的过程,找出存活对象,与用户线程一起工作。

  3. 最终标记:stop the world,修正并发标记期间因用户程序运行而导致的标记变动。这一部分记录在RemenberedSetLog中,最终合并到RemenberedSet中,并行。

  4. 筛选回收:对各个Region的回收价值和成本进行排序,制定回收计划。因为只回收一部分Region,停顿用户线程(也可以不停顿,并发)将大幅度提高收集效率。

GC日志
开启GC日志:JVM 加启动参数 -Xloggc:,-XX:+PrintGC或-verbose:gc

五、内存分配策略

对象优先新生代在Eden区分配,当没有足够空间,虚拟机发起一次MinorGC。

MinorGC:新生代的GC

MajorGC/FullGC:老年代的GC,经常伴随着一次MinorGC。一般速度比MinorGC慢10倍以上。

大对象直接进入老年代。参数-XX:PretenureSizeThreshold大于这个值的对象直接在老年代分配,避免在Eden和两个Survivor之前发生大量复制。

长期存活对象进入老年代。对象在Eden出生,每经过一次MinorGC还存活且能被移入Survivor的话年龄加1。年龄达到阈值(默认15,通过参数-XX:MaxTenuringThreshold设置)晋升如老年代。

动态对象年龄判定:并不是永远要求对象的年龄必须打到MaxTenuringThreshold才能晋升老年代,如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于等于该年龄的对象就可以直接进入老年代。

空间分配担保:只要老年代的连续空间大于新生代对象总大小或者历次晋升的平均大小就会进行MinorGC,否则进行FullGC。

你可能感兴趣的:(JVM(二)-垃圾回收与内存分配)