Java GC与内存分配策略

1、回收区域

主要区域:Java堆、方法区。程序计数器、虚拟机栈、本地方法栈3个区域随线程而生,随线程而灭,不需要过多考虑回收的问题。

2、对象是否可回收?

可达性分析算法:通过一系列称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没用任何引用链相连时,则证明此对象时不可用的,即该对象可回收。

可作为GC Roots的对象:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象
  • 方法区中类静态属性(static)引用的对象
  • 方法区中常量final引用的对象
  • 本地方法栈中JNI(native方法)引用的对象

3、引用的定义

JDK1.2以前,只有“引用”的概念,无法描述“当内存空间还足够时则保留在内存中;当垃圾回收后内存还吃紧时则可以抛弃”的对象,很多系统的缓存功能符合这样的应用场景。JDK1.2后Java对引用进行了扩充,分为以下4类:
  • 强应用,类似Object o = new Object()这类,只要强引用还可用,则垃圾收集器永远不会回收掉被引用的对象;
  • 软引用,SoftReference类,非必须对象。在系统将要发生OOM之前,将会把这些对象列为二次回收的范围,如果二次回收后还没有足够的内存才会抛出OOM异常;
  • 弱引用,WeakReference类,只能生存到下一次垃圾收集发生之前;
  • 虚引用,PhantomReference类,被该类引用关联的对象会在被回收时收到一个系统通知。

4、方法区回收

方法区也叫永久代,主要回收两部分内容:废弃常量和无用的类。
常量回收条件:与对象类似,没有任何对象引用常量池中的该变量时,如果有必要就可回收该常量的区域;
类可回收条件:
  • 该类所有实例都已被回收,即java堆中不存在该类的任何实例。
  • 加载该类的ClassLoader类加载器已经被回收。
  • 该类对应的Java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
上述条件说明类可回收,但不一定就必须回收,可通过参数-Xnoclassgc参数控制其是否会被回收。

5、垃圾收集算法


6、HotSpot算法实现

枚举根节点:可作为GC Roots的节点主要在全局性引用(例如常量或类静态属性)与执行上下文(例如栈帧中的本地变量表)。现今应用方法区就数百兆记,如果要逐个检查其中的引用,那么必然消耗很多时间;且可达性分析时必须保证对象引用关系的相对稳定,因此枚举根节点时必须停顿Java执行线程(Stop The World)。

HotSpot中使用一组称为OopMap的数据接口来存储对象引用信息,在类加载完成时就把对象内什么偏移量上是什么类型的数据计算出来,在JIT编译过程中也会在特定位置记录下栈和寄存器中哪些位置是引用。

由于引用关系变化频繁,如果为每条指令都生成对应OopMap则需要大量额外的空间,因此只在称为安全点的位置才记录这些信息。程序执行并非在所有地方都能停顿下来GC,只有在到达安全点时才能暂停。常见的安全点如方法调用、循环跳转、异常跳转等“长时间”执行的指令序列服用。

由于线程处于sleep状态或者Blocked状态时,无法响应JVM中段请求,从而无法到达安全点,因此出现了安全区域的概念。将一段代码内引用关系不会发生变化的区域成为安全区域,这个区域内的任意地方开始GC都是安全的,从而解决了无法到达安全点的问题。

7、垃圾收集器

7.1 Serial/Serial Old收集器


虚拟机运行在Client模式时默认的新生代收集器,简单而高效,对于桌面应用程序收集几十兆到几百兆的新生代停顿时间仅几十毫秒到一百多毫秒,是个较好的选择。

7.2 ParNew收集器


Serial收集器的多线程版本,区别仅在于使用多条线程进行垃圾收集。在JDK1.5中使用CMS来收集老年代的时候,新生代只能选择PareNew或者Serial收集器中的一个。

7.3 Parallel Scavenge收集器

ParallelScavenge收集器是一个新生代收集器,它也是使用复制算法的收集器,又是并行的多线程收集器。ParallelScavenge收集器的目标则是达到一个可控制的吞吐量(Throughput)。所谓吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值,即吞吐量 = 运行用户代码时间 /(运行用户代码时间 + 垃圾收集时间),虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%。

Parallel Scavenge收集器提供了两个参数用于精确控制吞吐量,分别是控制最大垃圾收集停顿时间的-XX:MaxGCPauseMillis参数以及直接设置吞吐量大小的-XX:GCTimeRatio参数。

 

MaxGCPauseMillis参数允许的值是一个大于0的毫秒数,收集器将尽可能地保证内存回收花费的时间不超过设定值。不过大家不要认为如果把这个参数的值设置得稍小一点就能使得系统的垃圾收集速度变得更快,GC停顿时间缩短是以牺牲吞吐量和新生代空间来换取的:系统把新生代调小一些,收集300MB新生代肯定比收集500MB快吧,这也直接导致垃圾收集发生得更频繁一些,原来10秒收集一次、每次停顿100毫秒,现在变成5秒收集一次、每次停顿70毫秒。停顿时间的确在下降,但吞吐量也降下来了。

 

GCTimeRatio参数的值应当是一个大于0且小于100的整数,也就是垃圾收集时间占总时间的比率,相当于是吞吐量的倒数。如果把此参数设置为19,那允许的最大GC时间就占总时间的5%(即1 /(1+19)),默认值为99,就是允许最大1%(即1 /(1+99))的垃圾收集时间。

 

由于与吞吐量关系密切,Parallel Scavenge收集器也经常称为“吞吐量优先”收集器。除上述两个参数之外,Parallel Scavenge收集器还有一个参数-XX:+UseAdaptiveSizePolicy值得关注。这是一个开关参数,当这个参数打开之后,就不需要手工指定新生代的大小(-Xmn)、Eden与Survivor区的比例(-XX:SurvivorRatio)、晋升老年代对象年龄(-XX:PretenureSizeThreshold)等细节参数了,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量,这种调节方式称为GC自适应的调节策略(GC Ergonomics)。如果读者对于收集器运作原来不太了解,手工优化存在困难的时候,使用Parallel Scavenge收集器配合自适应调节策略,把内存管理的调优任务交给虚拟机去完成将是一个不错的选择。只需要把基本的内存数据设置好(如-Xmx设置最大堆),然后使用MaxGCPauseMillis参数(更关注最大停顿时间)或GCTimeRatio(更关注吞吐量)参数给虚拟机设立一个优化目标,那具体细节参数的调节工作就由虚拟机完成了。自适应调节策略也是Parallel Scavenge收集器与ParNew收集器的一个重要区别。

7.4 Parallel Old收集器


Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。这个收集器是在JDK 1.6中才开始提供的. 在注重吞吐量以及CPU资源敏感的场合,

都可以优先考虑Parallel Scavenge加Parallel Old收集器。

7.5 CMS收集器


CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。目前很大一部分的Java应用集中在互联网站或者B/S系统的服务端上,
这类应用尤其重视服务的响应速度,推荐使用CMS收集器。

从名字(包含“Mark Sweep”)上就可以看出,CMS收集器是基于“标记—清除”算法实现的,它的运作过程相对于前面几种收集器来说更复杂一些,整个过程分为4个步骤,包括:
初始标记(CMS initial mark),stop the world,快
并发标记(CMS concurrent mark),并发,慢
重新标记(CMS remark),stop the world, 中
并发清除(CMS concurrent sweep),并发,慢
其中,初始标记、重新标记这两个步骤仍然需要“Stop The World”。初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快,并发标记阶段就是进行GC Roots Tracing的过程,

而重新标记阶段则是为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短。
3个明显缺点:

1、CMS收集器对CPU资源非常敏感。CMS默认启动的回收线程数是(CPU数量+3)/ 4,也就是当CPU在4个以上时,并发回收时垃圾收集线程不少于25%的CPU资源,并且随着CPU数量的增加而下降。

2、CMS收集器无法处理浮动垃圾(Floating Garbage),可能出现“Concurrent Mode Failure”失败而导致另一次Full GC的产生。由于CMS并发清理阶段用户线程还在运行着,需要预留一部分空间提供并发收集时的程序运作使用。

在JDK 1.6中,CMS收集器的启动阈值已经提升至92%。要是CMS运行期间预留的内存无法满足程序需要,就会出现一次“Concurrent Mode Failure”失败,这时虚拟机将启动后备预案:临时启用SerialOld收集器来重新进行老年代的垃圾收集,这样停顿时间就很长了。所以说参数-XX:CMSInitiatingOccupancyFraction设置得太高很容易导致大量“ConcurrentMode Failure”失败,性能反而降低。
3、碎片过多时,将会给大对象分配带来很大麻烦,往往会出现老年代还有很大空间剩余,但是无法找到足够大的连续空间来分配当前对象,不得不提前触发一次Full GC。为了解决这个问题,CMS收集器提供了一个-XX:+UseCMSCompactAtFullCollection开关参数(默认就是开启的),用于在CMS收集器顶不住要进行FullGC时开启内存碎片的合并整理过程,内存整理的过程是无法并发的,空间碎片问题没有了,但停顿时间不得不变长。虚拟机设计者还提供了另外一个参数-XX:CMSFullGCsBeforeCompaction,这个参数是用于设置执行多少次不压缩的FullGC后,跟着来一次带压缩的(默认值为0,表示每次进入Full GC时都进行碎片整理)。

8、HotSpot垃圾收集器组合


9、GC参数总结

10、内存分配与回收策略

对象优先在Eden分配、大对象直接进入老年代、长期存活的对象将进入老年代

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值