JVM堆中新生代Eden与两个Survivor区:
1.为什么会有年轻代
我们先来屡屡,为什么需要把堆分代?不分代不能完成他所做的事情么?其实不分代完全可以,分代的唯一理由就是优化GC性能。你先想想,如果没有分代,那我们所有的对象都在一块,GC的时候我们要找到哪些对象没用,这样就会对堆的所有区域进行扫描。而我们的很多对象都是朝生夕死的,如果分代的话,我们把新创建的对象放到某一地方,当GC的时候先把这块存“朝生夕死”对象的区域进行回收,这样就会腾出很大的空间出来。
2.年轻代中的GC
HotSpot JVM把年轻代分为了三部分:1个Eden区和2个Survivor区(分别叫from和to)。默认比例为8:1,为啥默认会是这个比例,接下来我们会聊到。一般情况下,新创建的对象都会被分配到Eden区(一些大对象特殊处理),这些对象经过第一次Minor GC后,如果仍然存活,将会被移到Survivor区。对象在Survivor区中每熬过一次Minor GC,年龄就会增加1岁,当它的年龄增加到一定程度时,就会被移动到年老代中。
因为年轻代中的对象基本都是朝生夕死的(80%以上),所以在年轻代的垃圾回收算法使用的是复制算法,复制算法的基本思想就是将内存分为两块,每次只用其中一块,当这一块内存用完,就将还活着的对象复制到另外一块上面。复制算法不会产生内存碎片。
在GC开始的时候,对象只会存在于Eden区和名为“From”的Survivor区,Survivor区“To”是空的。紧接着进行GC,Eden区中所有存活的对象都会被复制到“To”,而在“From”区中,仍存活的对象会根据他们的年龄值来决定去向。年龄达到一定值(年龄阈值,可以通过-XX:MaxTenuringThreshold来设置)的对象会被移动到年老代中,没有达到阈值的对象会被复制到“To”区域。经过这次GC后,Eden区和From区已经被清空。这个时候,“From”和“To”会交换他们的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。不管怎样,都会保证名为To的Survivor区域是空的。Minor GC会一直重复这样的过程,直到“To”区被填满,“To”区被填满之后,会将所有对象移动到年老代中。
3.一个对象的这一辈子
我是一个普通的java对象,我出生在Eden区,在Eden区我还看到和我长的很像的小兄弟,我们在Eden区中玩了挺长时间。有一天Eden区中的人实在是太多了,我就被迫去了Survivor区的“From”区,自从去了Survivor区,我就开始漂了,有时候在Survivor的“From”区,有时候在Survivor的“To”区,居无定所。直到我18岁的时候,爸爸说我成人了,该去社会上闯闯了。于是我就去了年老代那边,年老代里,人很多,并且年龄都挺大的,我在这里也认识了很多人。在年老代里,我生活了20年(每次GC加一岁),然后被回收。
4.有关年轻代的JVM参数
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。
堆分配参数
-XX:+PrintGC使用这个参数,虚拟机启动后,只要遇到GC就会打印日志。
-XX:UseSerialGC配置串行回收器
-XX:+PrintGCDetails可以查看详细配置信息,包括各个区的情况
-Xms:设置java程序启动时的初始堆大小
-Xmx:设置java程序能获得的最大堆大小
-Xmx20m -Xms5m -XX:+PrintCommandLineFlags: 可以将隐式或者显示传给虚拟机的参数输出
总结:在实际工作中,我们可以直接将初始的堆大小设置相等,这样做的好处式可以减少程序运行时的垃圾回收次数,从而提高性能。
新生代的配置
-Xmn:设置新生代的大小,设置一个比较大的新生代会减少老年代的大小,这个参数堆系统性能的GC行为有很大的影响,新生代代销一般会设置整个堆空间的1/3到1/4左右。
-XX:SurvivorRatio:用来设置新生代中eden空间的from/to空间的比例。含义:-XX:SurvivorRatio=eden/from=eden/to
总结:不同堆分布情况,对系统执行会产生一定的影响,在实际工作中,应该根据系统的特点做出合理的配置,基本策略:尽可能将对象预留在新生代,减少老年代的GC次数
除了可以设置老年代的绝对大小(-Xmn),还可以使用(-XX:NewRatio)设置新生代和老年代的比例:-XX:NewRatio=老年代/新生代
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZcsJqe7D-1575447631397)(C:\Users\wu\AppData\Roaming\Typora\typora-user-images\1573630619978.png)]
垃圾收集算法(一)
引用计数法:这是一个比较古老而经典的垃圾收集算法,其核心就是在对象被其他所引用时计数器加1,而当引用失效时则减1,但是这种方式有非常严重的问题:无法处理循环引用的情况、还有就是每次进行加减操作比较浪费系统的性能。
标记清除法:就是分为标记和清除两个阶段进行处理内存中的对象,当然这种方式也有非常大的弊端,就是空间碎片的问题。垃圾回收后的空间不是连续的,不连续的内存空间的工作效率要低于连续的内存空间。
复制算法:其核心思想就是将内存空间分为两块,每次只使用其中一块,在垃圾回收时,将正在使用的内存中所有的对象,反复去交换两个内存的角色,完成垃圾收集。(java中新生代的from和to空间就是使用这个算法)
标记压缩法:标记压缩在标记清除基础之上做了优化,把存活的对象压缩到内存一端,而后进行垃圾清楚。(java中老年代就是使用这个算法)
垃圾收集算法(二)
分代算法:就是根据对象的特点把内存分为N块,而后根据每个内存的特点使用不同的算法。
对于新生代和老年代来说,新生代回收频率很高,但是每次回收耗时都很短,而来年代回收频率较低,但是耗时会相对较长,所以应该尽量减少老年代的GC.
分区算法:期主要就是将整个内存分为N多个小的独立的空间,每个小空间都可以独立使用,这样细粒度的控制一次回收都少个小空间和那个小空间,而不是对整个空间进行GC,从而提升性能,并减少GC的停顿时间。
-XX:MaxTenuringThreshold,默认情况下为15
总结:根据设置MaxTenuringThreshold参数,可以指定新生代对象经过多少次回收后进入老年代,另外大大对象(新生代eden区无法装入时,也会直接进入老年代)。JVM里有个参数可以设置对象的大小超过指定的大小之后,直接晋升为老年代。
-XX:PretenreSizeThreshold
总结:使用PretenreSizeThreshold可以进行指定今日老年代的对象大小,但是要注意TLAB区域优先分配空间。
TLAB
TLAB全称是Thread Local Allocation Buffer即线程本地分配的缓存,从名字上看来是一个线程专用的内存分配区域,是为了加速对象分配而生的。每一个线程都会产生一个TLAB,该线程独享的工作区域,java虚拟机使用这种TLAB区来避免多线程冲突问题,提高了对象分配的效率。TLAB空间一般不会太大,当大对象无法TLAB分配时,则会分配在堆上。
-XX:+UseTLAB使用TLAB
-XX:+TLABSize设置TLAB大小
-XX:TLABRefillWasterFraction设置维护进入TLAB空间的单个对象大小,他是一个比例值,默认是64,即如果对象大于整个空间的1/64,则在堆上创建对象。
-XX:+PrintTLAB查看TLAB信息
-XX:ResizeTLAB自调整TLABRefillWasteFraction阀值
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yzneIXDK-1575447631398)(C:\Users\wu\AppData\Roaming\Typora\typora-user-images\1573713643087.png)]