JVM垃圾回收

JVM总体结构

  1. 类加载子系统和方法区:类加载子系统负责从文件系统或网络中加载Class信息,加载的类信息存放于一块称为方法区的内存空间。除了类信息外,方法区中可能还会存放运行时常量池信息,包括字符串常量和数字常量(这部分常量信息是class文件中常量池部分的内存映射)。
  2. Java堆:java堆在虚拟机启动时建立,它是Java程序最主要的内存工作区域,几乎所有的Java对象实例都存放在Java堆中。堆空间是所有线程共享的,这是一块与Java应用密切相关的内存空间。
  3. 直接内存:Java的NIO库允许Java程序使用直接内存。直接内存是在Java堆外的,直接向系统申请的内存空间,通常访问直接内存的速度会优于Java堆,因此出于性能的考虑,读写频繁的场合可能会考虑使用直接内存。由于直接内存在Java堆外,因此它的大小不会受限于Xmx指定的最大堆大小,但是系统内存是有限的,Java堆和直接内存的总和依然受限于操作系统给出的最大内存。
  4. 垃圾回收系统:垃圾回收系统是Java虚拟机的重要组成部分,垃圾回收器可以对方法区、Java堆和直接内存进行回收。其中,Java堆是垃圾回收的工作重点。与C/C++不同,Java中所有的对象空间释放都是隐式的,也就是说,Java中没有类似free()或者delete()这样的函数释放指定的内存空间,对于不再使用的Java对象,垃圾回收系统会在后台默默工作,默默查找、标识并释放垃圾对象,完成包括Java堆、方法区和直接内存中的全自动化管理。
  5. Java栈:每一个虚拟机线程都有一个私有的Java栈,一个线程的Java栈在线程创建的时候被创建,Java栈中保存着帧信息,Java栈中保存着局部变量,方法参数,同时和Java方法的调用、返回密切相关。
  6. 本地方法栈:本地方法栈和Java栈非常类似,最大的不同在于Java栈用于方法的调用,而本地方法栈则用于本地方法的调用,作为Java虚拟机的重要扩展,Java虚拟机允许Java直接调用本地方法(通常使用C编写)
  7. PC(Program Counter):PC寄存器也是每个线程私有的空间,Java虚拟机会为每个Java线程创建PC寄存器。在任意时刻,一个Java线程总是在执行一个方法,这个正在被执行的方法称为当前方法。如果当前方法不是本地方法,PC寄存器就会指向当前正在被执行的指令。如果当前方法是本地方法,那么PC寄存器的值就是undefined
  8. 执行引擎:执行引擎是Java虚拟机的最核心组件之一,它负责执行虚拟机的字节码,现代虚拟机为了提高执行效率,会使用即时编译技术将方法编译成机器码后再执行。
JVM堆结构图及分代

JVM内存分代策略: Java虚拟机根据对象存活周期不同,把堆内存分为几块,一般分为新生代、老年代和永久代(对HotSpot虚拟机而言)。
新创建的对象会在新生代中分配内存,经过多次回收仍然被活下来的对象存放在老年代中,静态属性、类信息存放在永久代中,新生代中的对象存活时间短,需要在新生代区域中频繁进行GC,老年代中的对象存活周期长,内存回收的频率相对较低,不需要频繁进行回收,永久代中的回收效果太差,一般不进行垃圾回收,还可以根据不同年代的特点采用合适的垃圾收集算法。

  • 新生代(Young Generation)
    新生成的对象优先存放在新生代中,新生代对象朝生夕死,存活率极低,在新生代中,常规应用进行一次垃圾收集一般可以回收70%~95%的空间,回收率很高。
    HotSpot将新生代划分为三块,一块较大的Eden空间和两块较小的Survivor空间,默认比例为8:1:1。划分的目的是因为HotSpot采用复制算法来回收新生代,设置这个比例是为了充分利用内存空间,减少浪费。新生代的对象在Eden区分配(大对象除外,大对象直接进入老年代),当Eden区没有足够的空间进行分配时,虚拟机将发起一次Minor GC。GC开始时,对象只存在于Eden区和From Survivor区,To Survivor区是空的(作为保留区域)。GC进行时,Eden区中所有存活的对象都会被复制到To Survivor区,而在From Survivor区中,仍存活的对象会根据他们的年龄之决定去向,年龄值达到年龄阈值(默认为15,新生代中的对象每熬过一轮垃圾回收,年龄值就加1,GC分代年龄存储在对象的header中)的对象会被移到老年代中,没有达到阈值的对象会被复制到To Survivor区。接着清空Eden区和From Survivor区,新生代中存活的对象都在To Survivor区。接着From Survivor区和To Survivor区会交换他们的角色,也就是新的To Survivor区就是上次GC清空的From Survivor区,新的From Survivor区就是上次GC的To Survivor区,总之,不管怎样都会保证To Survivor区在一轮GC后是空的。GC时,当To Survivor区没有足够的空间存放上一次新生代收集下来的存活对象时,需要依赖老年代进行分配担保,将这些对象存放在老年代中。
  • 老年代(Old Generation)
    在新生代中经历了多次(具体看虚拟机配置的阈值)GC后仍然存活下来的对象会进入老年代中。老年代中的对象生命周期较长,存活率较高,在老年代中进行GC的频率相对而言较低,而且回收的速度也比较慢。
  • 永久代(Permanent Generation)
    永久代存储类信息、常量、静态变量、即时编译器编译后的代码等数据,对这一区域而言,Java虚拟机规范指出可以不进行垃圾收集,一般而言不会进行垃圾回收。
垃圾回收常见算法
  1. 引用计数(Reference Counting):引用计数是比较古老的垃圾回收算法,原理是此对象有一个引用,即增加一个计数,删除一个引用则减少一个计数。垃圾回收时,只用收集计数为0的对象。此算法最致命的是无法处理循环引用问题。
  2. 复制(Copying):此算法把内存空间划分为两个相等的区域,每次只使用其中一个区域。垃圾回收时,遍历当前使用区域,把正在使用中的对象复制到另一个区域中。此算法每次只处理正在使用中的对象,因为复制成本较小,同时复制过去以后还能进行相应的内存整理,不会出现“碎片”问题。当然,此算法也有很明显的缺点,就是需要两倍的内存空间。
  3. 标记–清除(Mark-Sweep):此算法执行分为两个阶段,第一阶段从引用根节点开始标记所有被引用的对象,第二阶段遍历整个堆,把未标记的对象清除。此算法需要暂停整个应用,同时会产生碎片。
  4. 标记–整理(Mark-Compact):此算法结合了“标记–清除”和“复制”两个算法的优点,也是分两个阶段,第一阶段从根节点开始始标记所有被引用的对象,第二阶段遍历整个堆,清除未标记对象并且把存活对象“压缩”到堆的其中一块,按顺序排放。此算法避免了“标记–清除”的碎片问题,同时也避免了“复制”算法的空间问题。
JVM中的垃圾收集器

【Scavenge GC和Full GC的区别】
新生代GC(Scavenge GC):Scavenge GC指发生在新生代的GC,因为新生代的Java对象大多都是朝生夕死,所以Scavenge GC非常频繁,一般回收速度也比较快。当Eden空间不足以为对象分配内存时,会触发Scavenge GC。
一般情况下,当薪对象生成,并且在Eden申请空间失败时就会触发Scavenge GC,堆Eden区域进行GC,清除非存活对象,并且把尚且存活的对象移动到Survivor区,然后整理Survivor的两个区。这种方式的GC是对年轻代的Eden区进行的,不会影响到老年代。因为大部分对象都是从Eden区开始的,同时Eden区不会分配的很大,所以Eden区的GC会频繁进行。因而,这里需要使用速度快、效率高的算法,使Eden区能尽快空闲出来。
老年代GC(Full GC/Major GC):Full GC指发生在老年代的GC,出现了Full GC一般会伴随着至少一次的Minor GC(老年代的对象大部分都是Minor GC过程中从新生代进入到老年代),比如:分配担保失败。Full GC一般会比Minor GC慢10倍以上,当老年代内存不足或者显示调用System.gc()方法时,会触发Full GC。
【次收集和全收集的区别】
次收集:当年轻代堆空间紧张时会被触发,相对于全收集而言,收集间隔较短。
全收集:当老年代或持久代堆空间满了,会触发全收集操作,可以使用System.gc()来显式的启动全收集,全收集一般根据堆大小的不同,需要的时间也不尽相同,但一般会比较长,不过如果全收集的时间超过3~5秒钟,那就太长了。

新生代垃圾收集器

串行收集器(Serial)
serial收集器是Hotspot运行在client模式下的默认新生代收集器,他的特点是只用一个CPU/一条收集线程去完成GC工作,而且在进行垃圾收集时必须暂停其他所有的工作线程(Stop The World简称STW)。可以使用-XX:+UserSerialGC打开。

虽然是单线程收集,但他却简单而高效,在VM管理内存不大的情况下能够收集几十M~一两百M。
并行收集器(ParNew)
ParNew收集器其实是前面的Serial收集器的多线程版本,除使用多条线程进行GC外,包括Serial可用的所有控制参数、收集算法、STW、对象分配规则、回收策略等都与Serial完全一样也是VM启用CMS收集器-XX:UserConcMarkSweepGC的默认新生代收集器。

由于存在线程切换的开销,ParNew在单CPU的环境中比不上Serial,且在通过超线程技术实现的两个CPU的环境中也不能100%保证超过Serial,但随着可用CPU数量的增加,收集效率肯定也会大大增加(ParNew收集线程数与CPU数量相同,因此在CPU数量过大的环境中,可用-XX:ParallelGCThreads=<N>参数控制GC线程数)
Parallel Scavenge收集器
与ParNew类似,Parallel Scavenge也是使用复制算法,也是并行多线程收集器,但与其他收集器关注尽可能缩短垃圾收集时间不同,Parallel Scavenge更关注系统吞吐量。
系统吞吐量 = 运行用户代码时间/(运行用户代码时间+垃圾收集时间)
停顿时间越短就越适用于用户交互的程序,良好的响应速度能提升用户体验,而高吞吐量则适合于后台运算而不需要太对交互的任务,可以最高效的利用CPU时间,尽快完成程序的运算任务,Parallel Scavenge提供如下参数设置系统吞吐量:

Parallel Scavenge参数描述
-XX:MaxGCPauseMillis(毫秒数)收集器将尽力保证内存回收花费的时间不超过设定值,但如果太小将导致GC的频率增加
-XX:GCTimeRatio(整数:0<GCTimeRatio<100)是;垃圾收集时间占总时间的比率
-XX:+UseAdaptiveSizePolicy启动GC自适应调节策略,不需要手工指定-Xmn、-XX:MaxGCPauseMillis、-XX:GCTimeRatio等细节参数,VM会根据当前系统的运行状况收集性能监控信息、动态调整这些参数以提供最适合的停顿时间或最大的吞吐量
老年代垃圾收集器

Serial Old收集器
Serial Old是Serial收集器的老年代版本,同样是单线程收集器,使用“标记–整理”算法。
Parallel Old收集器
Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记–整理”算法,吞吐量优先,主要与Parallel Scavenge配合在注重吞吐量及CPU资源敏感系统内使用:

CMS收集器
CMS(Cpncurrent-Mark Sweep)收集器是一款具有划时代意义的收集器,一款真正意义上的并发收集器,虽然现在已经有了理论意义上表现更好的G1收集器,但现在主流互联网企业线上选用的仍然是CMS(如Taobao、微店等)。

分区收集-G1收集器

G1(Garbage-First)是一款面向服务端的收集器,主要目标用于配备多CPU的服务器、治理大内存。
–XX:+UseG1GC启动G1收集器。
与其他基于分代的收集器不同,G1将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔阂了,它们都是一部分Region(不需要连续)的集合。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值