JVM自动内存管理机制
Jvm的自动内存管理机制,主要包括内存回收和内存分配。
内存回收
对于内存回收机制主要围绕“哪些内存需要回收?”、“什么时候回收?”、“如何回收?”这三个问题来展开。
一、哪些内存需要回收?
不可能再被任何途径使用的对象需要被回收。
如何判断一个对象是否还可以再被引用?
这里有两个方法可以做到:(1 引用计数法 (2 可达性分析
引用计数法:给对象维护一个计数器,每次被引用计数器的值+1,每次引用被释放,计数器的值-1,当计数器的值为0时,认为它不可能再被引用了。
可达性分析:从GCRoots向下搜索,走过的路径为引用链,当一个对象到GCRoots没有任何引用链相连则证明对象不可用。
由于引用计数法很难解决对象循环引用的场景,因此一般都使用可达性分析来判断一个对象是否存活。
二、什么时候回收?
2.1 新生代的回收时机
新的对象需要在Eden区申请内存,但Eden区没有足够的连续的空间分配给对象会触发一次minor GC
2.2 老年代的回收时机
从新生代过来的对象需要在老年代申请空间,但老年代没有足够的连续的空间来分配,会触发一次major GC。
HotSpot VM 老年代给新生代做空间担保时,若老年代连续可用空间小于历次晋升到老年代对象的平均大小,触发一次major GC。
三、如何回收?
3.1 回收算法
标记-清除(Mark-Sweep):标记存活的对象,把剩下的对象清除掉。问题:会产生大量的空间碎片。
复制(Copying):把内存分为两块相同大小的空间,一块使用称为From,一块保留称为To,回收时把From里面存活的对象复制到To中,复制完成后清除To中的全部内容。解决了空间碎片的问题,但空间利 用率太低。HotSpot利用新生代对象存活时间短,一次GC大部分对象会被回收,小部分对象需要复制这一特点,将新生代分为一块Eden区和两块大小相同的Survior区,每次使用 Eden+Survivor空闲一块Survivor,通过参数SuriviorRatio来控制Eden区和Survior的比例从而提升空间利用率。
标记-整理(Mark-Compact):标记存活的对象,并让它们向一端移动,清除移动边界以外的对象。可以解决空间碎片的问题。
3.2 垃圾回收器
HotSpot虚拟机的垃圾回收器:Serial、ParNew、Parallel Scavenge、CMS、Serial Old、Parallel Old、G1。
Young Generation:
Serial收集器:单线程,收集时暂停所有工作线程,直到它收集完成。
ParNew收集器:多线程,使用多条线程进行垃圾回收,同样需要暂停所有的工作线程
Parallel Scavenge收集器:多线程,和ParNew的区别在于,这个收集器关注的是吞吐量
吞吐量 = 程序执行时间/程序执行时间+垃圾回收时间,这个收集器提供可以精准控制吞吐量的参数:
-XX:MaxGCPauseMillis最大垃圾回收停顿时间
-XX:GCTimeRatio吞吐量大小
-XX:+UseAdaptiveSizePolicy开启自适应调节策略,自动调节各个参数来提供最适合的停顿时间或最大的吞吐量。
Young Generation一般使用的是复制算法。
Serial、ParNew可以和CMS搭配使用,但Parallel Scavenge不行。
Tenured Generation:
Serial Old收集器:Serial收集器的老年代版本,单线程,使用标记整理算法,可以和Parallel Scavenge搭配使用,也可以作为CMS的后备方案
Parallel Old收集器:Parallel Scavenge的老年代版本,多线程,使用标记整理算法, 可以和Parallel Scavenge搭配使用实现真正的吞吐量优先GC。
CMS(Concurrent Mark Sweep)收集器:并发收集,以获取最短的回收停顿时间为目标的收集器,使用标记清除算法(为什么不用标记-整理算法,整理时需要控制并发,停掉工作线程,会延长stop-the-world的时间,和获取最短的回收停顿时间为目标的理念相冲突)。
CMS回收的过程:一、初始标记 二、并发标记 三、重新标记 四、并发清除
初始标记:stop-the-world标记GCRoots可以直接关联的对象
并发标记:完成余下的GCRoots Tracing标记(用户线程和GC线程并发执行)
重新标记:stop-the-world修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录
并发清除:执行清除操作,和用户线程并发执行
CMS回收器的缺陷:
一、对cpu的资源敏感,由于GC过程有两个阶段是并发进行的,会抢占cpu资源,降低系统的吞吐量。
二、浮动垃圾,在并发清除阶段,也会产生不可达对象,这些对象只能等到下次GC时才能被回收。由于浮动垃圾的存在,CMS不能等到内存被填满才进行垃圾回收,必须预留足够的空间,通过-XX:CMSInitiatingOccupancyFraction参数设置触发CMS回收的百分比阈值。当CMS GC期间预留的内存无法满足程序需求时会出现Concurrent Mode Failure 失败,会临时启用后备方案使用Seria Old进行老年代GC,会出现较长的stop-the-world停顿。
三、标记-清除会产生大量的空间碎片,若老年代还有很多空间,但无法找到足够大的连续空间来分配当前对象会提前出发full GC
–XX:+UseCMSCompactAtFullCollection参数设置在CMS要进行Full GC是开启内存碎片的合并整理过程,整理过程不能并发,需要stop-the-world
-XX:CMSFullGCsBeforeCompaction = ?,设置执行多少次不压缩的Full GC后接着来一次带压缩的Full GC
G1收集器:并发收集器,追求更小的stop-the-world时间。
G1收集器的特点:
1)javad堆的内存布局.将整个java堆分为多个大小相等的独立区域(Region),虽然还保留了新生代和老年代的概念,但新生代和老年代不将再是物理隔离的,它们都是部分Region的集合.
2)分代收集,G1收集器可以独自管理整个GC堆,并且可以根据对象的存活时间采用不同的方式收集。
3)空间整合,G1收集器从整体来看是基于标记-整理算法来实现,局部来看(两个Region之间)是基于复制算法来实现的,这就意味着G1收集器可以规避掉CMS算法的空间碎片的问题
4)可预测的停顿。G1相对于CMS收集器的另一个优势是可以建立一个可预测的停顿时间模型。通过-XX:MaxGCPauseMillis=? 来设置最大GC时间。
5)化整为零的GC思想.G1会跟踪各个Region里面的垃圾堆积的价值大小(回收空间、回收时间)并在后台维护一个优先级列表,优先回收优先级高的Region,而不是毫无计划的在堆上进行全区域垃圾回收,这样可以保证G1收集器可以在有限的时间可以获得更可能高的收集效率。
G1收集器在进行可达性分析时为了避免全堆扫描,在Region中会记录一个Remember Set来保存哪些对象引用过这个Region
新生代收集:stop-the-world,Eden和Survivor区存活的对象被复制到另一块Survivor区,如果年龄达到阈值或Survivor区的空间不足会直接分配到老年代,并清空已经处理过的Eden和Survivor区。
老年代收集:初始标记、并发标记、最终标记、筛选回收(最后也会把存活对象复制到另一个Region中,从而避免了空间碎片的问题)
为什么选择Serial Old 作为CMS的后备方案而不选择多线程并行的Parallel Old,原因在于Serial Old可以和年轻代的三种搭配使用,而Parallel Old只能和Parallel Scavenge搭配使用。
并行和并发:这里说的并行是多线程进行GC,但暂停工作线程,并发说的是GC和工作线程一起多线程执行,也就是说,串行和并行都需要stop-the-world,并发不需要stop-the-world。
内存分配
对象的内存分配主要在堆上进行,对于新对象主要分配在Eden区,少数情况也会直接分配到老年代。
1.1 对象优先在Eden去分配
新对象进入Eden区,当Eden区容量不足时,进行一次minior GC。GC时,当Eden区+Survivor From区复制到Surivivor To区,Surivivor To区容量不足时,Eden区+Survivor From区的对象直接进入老年代。
1.2 大对象直接进入老年代
-XX:PretenureSizeThreshold参数设置对象大小阈值,大于这个值的对象直接进入老年代
1.3 长期存活的对象直接进入老年代
Survivor区中的对象每熬过一次minor GC年龄就增加1岁,-XX:MaxTenuringThreshold设置年龄阈值,达到阈值的对象直接进入老年代
1.4 年龄相同对象所占空间超过Survivor区的一半,则大于等于这个年龄的对象直接进入老年代
1.5 空间分配担保
当新生代采用Eden、Survivor式的复制算法时需要老年代对其进行内存担保(因为minior GC时,如果Survivor区的容量不足以接纳Survivor区+Eden区的对象,则他们将全部进入老年代)
Minior GC之前先检查是否可以确保此次GC的安全,
先检查老年代的最大可用连续空间是否大于新生代所有对象的总空间,如果成立则可以确保此次monior GC是安全的,如果不成立,检查老年代最大可用连续空间是否大于历次晋升到老年代对象的平均大小,如果大于尝试一次minior GC, 否则进行一次Full GC.
HotSpot VM常用参数表:
-XX:设置 -XX:+参数 (开启?功能) -XX:-参数(关闭?功能) -XX:参数 = ?(设置参数的值)
-Xms? 设置堆的初始大小
-Xmx? 设置堆的最大值
-Xmn? 设置新生代的大小
-Xss? 设置每个线程的栈的大小
-XX:SurvivorRatio=? 设置Eden区和Survior区的大小比值
-XX:CMSInitiatingOccupancyFraction=?设置CMS收集器在老年代空间阈值,达到这个阈值进行垃圾回收
-XX:MaxTenuringThreshold=?晋升到老年代的对象年龄阈值