堆内存详解
了解一下,为什么要对堆内存分代?当然,不分代也能实现它所做的事件,之所以分代,其实是为了优化 GC 性能。
如果没有分代,那么所有的对象都会放在一块内存区域中,GC 的时候寻找垃圾对象,就须要对整个内存区域进行扫描,这样会很大水平上影响 GC 效率,在 Java 中,很多对象都是“朝生夕死”的,如果把内存空间划分区域的话,将新创建的对象放到某个区域中,GC 的时候优先回收这部分“朝生夕死”的对象,这样就会腾出很大的空间来。
所以就对堆内存进行了分代,上面这个图就是堆内存的分布图
堆内存被划分为两块,别离是 年老代 和老年代。
年老代
年老代其实分为两局部,别离是 1 个 Eden 区和 2 个 Survivor 区(别离叫 from 和 to),默认比例是 8:1:1,个别状况下,新创建的对象根本都会放到 Eden 区,(除非一些特地大的对象会间接放到老年代),当 Eden 没有足够的空间的时候,就会触发 jvm 发动一次 Minor GC,如果对象通过一次 Minor GC 还存活,并且又能被 Survivor 空间承受,那么将被挪动到 Survivor 空间当中,对象在 Survivor 区中每熬过一次 Minor GC,年龄就会减少一岁,当它的年龄减少到肯定水平(默认 15 岁)时,就会被移到老年代中,当然降职老年代的年龄是能够设置的。
复制整顿算法
因为年老代中的对象根本都是朝生夕死的(80% 以上),所以在年老代的垃圾回收算法应用的是复制整顿算法,复制整顿算法的根本思维就是将内存分为两块,每次只用其中一块,当这一块内存用完,就将还活着的对象复制到另外一块下面。
长处:不会产生内存碎片
毛病:会开拓新的空间,也就是 To survivor,用来保留有用对象
复制对象会破费一些工夫
在 GC 开始的时候,对象只会存在于 Eden 区和名为“From”的 Survivor 区,Survivor 区的“To”是空的。紧接着进行 GC,通过一轮 Minor GC 后,Eden 区中所有存活的对象都会被复制到“To”,而在“From”区中,仍存活的对象会依据他们的年龄值来决定去向。年龄达到肯定值 (年龄阈值,能够通过 -XX:MaxTenuringThreshold 来设置) 的对象会被挪动到年轻代中,没有达到阈值的对象会被复制到“To”区域。通过这次 GC 后,Eden 区和 From 区曾经被清空。这个时候,“From”和“To”会替换他们的角色,也就是新的“To”就是上次 GC 前的“From”,新的“From”就是上次 GC 前的“To”。不管怎样,都会保障名为 To 的 Survivor 区域是空的。Minor GC 会始终反复这样的过程,直到“To”区被填满,“To”区被填满之后,会将所有对象挪动到年轻代中。
老年代
当年老带随着一直地 Minor GC,from survivor 中的对象会一直成长,当 from survivor 中的对象成长大 15 岁的时候,就会进入老年代,随着 Minor GC 的继续进行,老年代中对象也会持续增长,最终老年代的空间也会不够用,此时就会执行老年代的 GC–>Major GC。Major GC 应用的算法是标记革除算法或者标记 - 压缩算法。
标记革除
步骤
【1】首先会去根对象的汇合中进行遍历,发现对象还存在援用,就是存活的对象并打上一个存活的标记
【2】再去根对象汇合进行二次遍历,将没有被打上标记的对象革除掉。
优缺点
长处:可能进入老年代的对象,个别都绝对稳固,被回收的数量较少,缩小对磁盘的清理,如果采纳复制整顿算法,被复制的对象会有很多。
毛病:尽管垃圾失去了回收,然而回收当前,堆内存中呈现了不间断的现状 — 内存碎片,导致大对象无奈创立
标记压缩
和标记革除算法基本相同,惟一不同的就是,在革除实现之后,会把存活的对象向内存的一边进行压缩,这样就能够解决内存碎片问题。上篇文章曾经具体介绍过了,这里就不再赘述了。
堆内存常见的参数配置
【1】-XX:NewSize 和 -XX:MaxNewSize
用于设置年老代的大小,倡议设为整个堆大小的 1 / 3 或者 1 /4, 两个值设为一样大。
【2】-XX:SurvivorRatio
用于设置 Eden 和其中一个 Survivor 的比值,这个值也比拟重要。
【3】-XX:+PrintTenuringDistribution
这个参数用于显示每次 Minor GC 时 Survivor 区中各个年龄段的对象的大小。
【4】-XX:InitialTenuringThreshol 和 -XX:MaxTenuringThreshold
用于设置降职到老年代的对象年龄的最小值和最大值,每个对象在保持过一次 Minor GC 之后,年龄就加 1。