JVM内存模型和垃圾回收算法

一、内存模型

        JVM在执行java程序时,会将它管理的内存划分为若干个不同的区域,每个区域都有自己的用途和创建销毁的时间。主要分为两大部分:线程私有区和共享区。

线程私有区

1、虚拟机栈

与线程的生命周期相同。它是用来管理java方法啊执行的内存模型。每个方法执行时都会创建一个栈帧(stack frame),存储方法的局部变量表、操作数栈、动态链接、返回值和返回地址等信息。栈的大小决定了方法可调用的最大深度。若请求的栈深度超过可用的最大深度,则会抛出stackOverflowError。

2、本地方法栈

和虚拟机栈类似,但它不为java方法服务,而是本地(native)方法。

3、程序计数器

若执行的是java方法,则程序计数器用来记录正在执行的指令字节码地址;若执行的是本地(native)方法,则计数器为空。

线程共享区

1、方法区

用于存放被虚拟机加载的类的元数据信息,比如常量、静态变量和即时编译器编译后的代码。

2、堆

用于存放对象实例和数组,是垃圾回收的主要区域。

二、垃圾回收算法

标记-清除算法

先标记出所有需要回收的对象,标记完成后统一回收

复制算法

将内存按容量划分为大小相等的两块,每次只使用其中一块,这块内存不足时,将仍存活的对象复制到另外一块,然后再清理当前块的内存空间

标记-整理算法

先标记出所有需要回收的对象,然后将存活的对象向一端移动,然后清理端边界意外的内存

分代收集算法

将内存分为新生代和老年代。

1、新生代采用复制算法。

将新生代按8:1:1的比例分为Eden、survivor from和survivor to三个区域。

第一步,Eden区内存充足的情况下,对象优先选择此区域;

第二步,内存不够时,会进行minor gc,也就是将Eden区中存活的对象复制到survivor from中,然后再清空Eden区;

第三步,若Eden区再次内存不够时,会再次进行minor gc,会将Eden区和survivor from区中存活的对象复制到survivor to中,然后再清空Eden区和survivor from区;并将survivor to重新作为suvivor from区,将原survivor from区作为survivor to区

注意,进入老年代的策略:

(1)每进行一次minor gc,对象的年龄会+1,当年龄达到15(默认是15,可以通过-XX:MaxTenuringThreshold设置)时,对象会被转移到老年代;

(2)若当前survivor区域中,一批对象(年龄在1-n的对象)的总大小超过了当前区域内存的50%,那大于等于这批对象年龄n的对象,直接进入老年代

(3)大对象(超过PretenureSizeThreshold参数设置的大小的对象)直接进入老年代

(4)minor gc后剩余存活对象的大小超过survivor区,则将这些对象转移至老年代

2、老年代采用标记-整理算法。

若老年代的连续空间大于新生代对象总大小或者大于历次晋升到老年代的对象的平均大小,进行minor gc,否则进行Full gc。

若Full gc后,空间仍不足,则抛出OOM异常。

三、垃圾收集器

Serial(串行)收集器

采用复制算法,针对新生代。

单线程收集器,只会使用一个CPU或一个线程区完成垃圾收集。且在进行垃圾收集时,必须暂停其他所有工作线程,直至垃圾收集结束。

ParNew收集器

ParNew收集器是Serial收集器的多线程版本,针对新生代。除了使用多线程外,其他的参数、算法、对象分配规则、回收策略等等与Serial收集一致。经常与CMS收集器配合使用。

适合多CPU环境,可通过-XX:ParallerGCThreads参数设置线程数量。

Parallel Scavenge收集器

Parallel Scavenge收集器也是一个针对新生代的多线程收集器,也使用复制算法。

与其他收集器不同的是,此收集器更关注吞吐量(应用程序线程用时占程序总用时的比例)的控制,而其他收集器关注的是尽可能缩短STW时间。

除了使用-XX:GCTimeRatio等参数来控制吞吐量之外,此收集器还提供了一个开关参数(-XX:+UseAdaptiveSizePolicy),打开该参数后,虚拟机会根据当前系统的运行情况,自动调整新生代的大小、Eden和Survivor的比例、今生老年代的年龄等参数。

Parallel Scavenge收集器无法和CMS收集器配合使用,所以在Parallel Old收集器(JDK1.6)出现之前,如果新生代选择Parallel Scavenge收集器,则老年代只能选择Serial Old收集器。

Serial Old收集器

采用标记-整理算法的单线程收集器,针对老年代。

Parallel Old收集器

Parallel Old收集器是针对老年代的多线程收集器,使用标记-整理算法。

和Parallel Scavenge收集器一样,关注于吞吐量的控制。在JDK1.6及以后版本,通常与Parallel Scavenge收集器配合使用。

CMS(Concurrent Mark Sweep)收集器

采用标记-清除算法,针对老年代。

CMS收集器分为4个步骤:

1、初始标记:标记GC Roots直接关联到的对象,耗时短。需要STW。

2、并发标记:进行GC Roots Tracing,耗时最长。

3、重新标记:修正并发标记过程中,因程序运行而导致标记产生变动的对象的标记记录。此阶段也需要STW,但耗时比并发标记时间短。

4、并发清除:清除标记的对象。

从上述步骤可以看出,CMS收集过程中的并发标记和并发清除阶段可以与应用程序线程一起工作,而以上几个收集器必须STW。这就可以通过并发收集来保证低停顿。

但它还有一些缺点:

1、在并发阶段,因为会和应用程序的线程并行,所以会占用一部分CPU资源,导致应用程序相应变慢,总吞吐量降低;

2、因为重新标记过程只会修正已经标记的对象,这就导致在并发标记阶段产生的新的对象垃圾,只能再下次GC时清理;

3、由于采用标记-清除算法,这就意味着收集过程结束后,会有大量内存碎片产生。若碎片太多,就会导致虽然有空间剩余,但无法找到足够大的连续空间来分配大对象,就会报Concurrent Mode Failure错误,并触发Full GC。

G1收集器

与其他收集器不同的是:

1、G1收集器会将堆内存分割成许多大小相等的独立区域(默认为2048块,且最多为2048),但仍会区分新生代和老年代,也会有Eden区和Survivor区。

2、G1收集器会针对新生代和老年代,而不是其他收集器只针对某一代。

3、G1收集器针对子部分采用复制算法,所以不会产生内存碎片,而整体采用标记-整理算法。

G1的收集过程可能有4个阶段:

(1)新生代GC

Eden区被占满,则触发新生代GC,采用复制算法;回收后所有Eden区被清空,Suvivor收集未被回收的数据;但可能会因为部分Suvivor区和Eden区的对象晋升老年代导致老年代区域增多。

(2)并发标记周期

分为以下几步:

  • 初始标记:标记GC Roots直接可达的对象。会伴随一次新生代GC。需要STW
  • 根区域扫描:扫描由Survivor区直接可达的老年代区域,并标记这些对象。这个过程可与应用程序并发执行,但不能和新生代GC同时发生,若需要新生代GC,则必须等扫描结束后才可执行,因此会导致新生代GC延长。
  • 并发标记:并发标记整个堆内的存活对象,且这个过程可以被新生代GC打断。
  • 重新标记:修正并发标记过程中发生改变的记录。此过程采用SATB(Snapshot-At-The-Begining)算法完成,即在标记之初为存活对象创建一个快照,有助于加快此过程的速度。此过程需要STW。
  • 筛选区域:计算各个区域的回收价值和成本进行排序,识别需要混合回收的区域。需要STW。
  • 并发清理:识别并清理完全空闲的区域。并发执行。

(3)混合回收

针对上个步骤筛选出来的回收价值较高的区域进行回收,采用复制算法。这个过程会进行正常的新生代GC,也会对被标记的老年代进行回收。

新生代GC—>并发标记周期—>混合回收 这个过程会重复多次,直到达到G1MixedGCCountTarget的循环次数或G1HeapWastePercent允许的垃圾空间占比阈值。

(4)可能需要的Full GC

在标记周期,但在混合回收之前,老年代被耗尽;

在混合回收时,老年代在释放出足够内存之前被耗尽;

新生代收集时,Survivor和老年代中没有足够的空间来存储幸存对象;

大对象(G1中超过区域大小的一半)找不到合适的空间进行分配。

四、常用的JVM参数

-Xms:堆初始内存大小

-Xmx:最大堆内存

-Xmn:新生代内存大小

-Xss:设置栈空间大小

-XX:SurvivorRatio:设置Eden和Survivor区的空间比例,默认为8

-XX:NewRatio:设置年轻代和年老代的比例大小,默认值为2

-XX:MaxTenuringThreshold:设置对象经过多少次minor gc进入老年代

-XX:PretenureSizeThreshold:设置大对象的标准

-XX:-HandlePromotionFailure:是否允许担保失败(JDK7之后失效)

-XX:+UseParallelOldGC:设置老年代并行垃圾回收

-XX:+UseParallelGC:设置年轻代垃圾回收为并行

-XX:+UseSerialGC:设置年轻代和年老代垃圾回收为串行

-XX:+UseG1GC:并行、并发和增量式压缩垃圾收集器,不区分年轻代和老年代。将对空间划分为多个大小相等的区域,进行垃圾回收时,优先收集存活对象较少的区域。

-XX:G1HeapRegionSize:指定每个分区的大小(1MB-32MB,且必须为2的n次幂)。默认将整堆分为2048个分区。

-XX:G1NewSizePercent:设置G1算法新生代内存初始空间占比(默认5%)

-XX:G1MaxNewSizePercent:设置G1算法新生代最大内存空间占比

-XX:G1MixedGCLiveThresholdPercent:分区中垃圾数量超过这个值才会回收该分区

-XX:G1MixedGCCountTarget:一次回收过程中指定做几次混合回收

-XX:G1HeapWastePercent:默认值为10%。可回收的垃圾占堆内存比例低于此百分比后,则不再进行混合回收

-XX:InitiatingHeapOccupancyPercent:老年代占用空间占比阈值(默认45%),达到这个阈值则执行新生代和老年代的混合收集

-XX:+UseConcMarkSweepGC:短暂停顿的并发收集。年轻代:通过-XX:+UseParNewGC来控制使用普通或并行垃圾回收;老年代只能使用并发标记-清除算法

-XX:+ParallelGCThreads:并行线程数量,默认为4

-XX:MaxGCPauseMillis:控制最大的垃圾收集停顿时间

-XX:GCTimeRatio:设置吞吐量大小,取值0-100,系统花费不超过1/(1+n)的时间用于垃圾收集

-XX:+PrintGCDetails:打印GC详情日志

-XX:+PrintGCDateStamps:打印GC耗时

-XX:+PrintTenuringDistribution:打印年龄信息

-XX:+HeapDumpOnOutOfMemoryError:当抛出OOM时,进行HeapDump

-XX:HeapDumpPath=路径:设置HeapDump文件路径

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值