只有排除了自己代码的问题后,再进行内存调优,内存调优都是从新生代开始,因为新生代优化空间更大一些
TLAB:thread-local allocation buffer(可防止多个线程创建对象时的干扰)
每个线程都在Eden区中分配私有的区域,即TLAB;
当new一个对象时,首先检查TLAB缓冲区中是否有可用内存,如果有,会优先在这一块空间进行对象的分配。之所以这么做,因为对象的分配也有线程安全的问题:比如线程1需要使用这段内存,在分配还没结束的过程中,线程2也要用这段内存,就会造成内存的分配混乱。因此在做对象的内存分配时,也要做一个线程的并发安全的保护,当然这个操作是由jvm做的
能否减少线程之间对内存分配的并发冲突呢?可以使用TLAB线程分配局部缓冲区,他的作用就是使用自己私有的伊甸园内存进行对像的分配,这样的话,多个线程即使同时创建对象,也不会产生对内存占用的干扰。所以伊甸园区创建对象效率时非常高的
学过的所有的垃圾回收器,在新生代垃圾回收采用的都是复制算法
复制算法是将伊甸园和from中存活对象复制到to中,然后释放伊甸园和from所有内存,所以说死亡对象回收零代价
新生代垃圾回收,绝大部分用过即死,只有少部分存货下来
MinorGC的时间与FullGC的时间相差1-2个数量级别
-Xmn :设置新生代的初始和最大值
新生代进行调优,有的人认为就是将新生代进行调大,这是一个最有效的方式,但是也要注意新生代在调大的过程中存在的问题
Sets the initial and maximum size (in bytes) of the heap for the young generation (nursery). GC is
performed in this region more often than in other regions. If the size for the young generation is
too small, then a lot of minor garbage collections are performed. If the size is too large, then only
full garbage collections are performed, which can take a long time to complete. Oracle
recommends that you keep the size for the young generation greater than 25% and less than
50% of the overall heap size.
不是越大越好
新生代内存太小:频繁触发Minor GC,会STW,会使得吞吐量下降
如果设置小了,可用空间少,创建新对象时,一旦发现新生代空间不足,就会触发MinorGC,会产生STW,产生短暂的暂停
新生代内存太大:①老年代内存占比有所降低(堆空间一定前提下),新生代认为空闲空间很多,新创建的对象不会触发GC。②但是老年代的空间紧张,再触发垃圾回收,就是FullGC了。③FullGC的STW要比MinorGC更长。④如果新生代过大,引发的垃圾回收类型可能就是FullGC,就需要占用更长的时间才能完成垃圾回收。会更频繁地触发Full GC。而且触发Minor GC时,清理新生代所花费的时间会更长
oracle给的建议是:堆的25%<新生代<堆的50%
实际生产过程中,随着新生代的增加,吞吐量会有一个拐点,新生代如果设置的太小会引发的缺点,以及这个新生代如果。原因是新生代比较大,造成回收时间的变长
新生代内存设置为能容纳[并发量*(一次请求-响应所产生的对象)]的数据为宜
但是总的原则啊,我们还是要将新生代调的尽可能大。你刚才不是。还说啊,新生代的空间大了,那将来这个垃圾回收的时间变长了?但是我们刚才还有一个因素没有考虑进去。什么因素呢?就是新生代垃圾的回收都是复制算法。算法也是分成两个阶段,第1个阶段标记,第2个阶段呢去进行复制,那么这两个阶段哪个阶段花费的时间更多一些呢,其实是复制。因为复制牵扯到这个对象的占用内存块移动。另外呢你要更新对象引用的地址,这个速度相对更耗时一些。而新生代的对象,大家想想啊绝大部分都是朝生夕死的,也就是说最终只有少量的对象啊,只有少量的对象会存活下来的,所以既然是少量的对象存货,那它复制所占用的时间其实也是相对较短的,而标记时间,相对于复制时间来讲,显得不是那么重要,所以我们新生代调大的情况下。因为主要耗费的时间还是在复制上。即使增的很大效率也不会有特别明显的下降,这是对新生代大小设置的一个补充。
那究竟把它设成多大比较合适呢?我们的一个理想情况是,我的一个新生代能够容纳请求和响应过程中一次请求和响应过程中所产生的对象乘上并发量。举一个例子,比如说我一次请求响应的过程中呢,可能会创建很多新的对象,那这些新的对象加起来大约呢,比如说占到了512K的内存,那这是我的并发量呢,大概是1000。也就是,同一时刻,有1000个用户来访问我。这时候我的新生代,它的一个比较理想的内存就是。每一个请求响应乘以我的并发量,也就是512k×1000,大约是512M。为什么我们设成这么大就比较理想呢?这是因为你这一次请求响应的过程以后,其中大部分对象都会被回收。只要你这一次请求加上你的并发量所占用内存不超过我新生代的内存,它就不会触发。或较少的触发新生代的垃圾回收。这样就可以大约估算出新生代内存占用到底划分成多少比较合理。
幸存区大到能保留【当前活跃对象+需要晋升对象】
新生代还有一块区域,我们称之为叫幸存区。幸存区呢,它的内存设置要遵从这么几个规则。
第1个呢,就是我们要考虑到幸存区啊,它也要足够的大,大到呢,能够保留当前的活跃对象和需要晋升的对象。这是什么意思呢?解释一下啊,幸存区中,你可以把它看成有两类对象:第1类对象呢,它是生命周期较短,也许下一次垃圾的时候就要把它回收掉了,但是由于现在还正在使用,它暂时不能回收,所以它就留在我们的幸存区中;另一类呢是他肯定将来会被晋升到老年的 ,因为大家都在用它,但是由于年龄还不够,所以暂时还存活在幸存区当中,没有被晋升。那幸存区中呢,就可以看成是这两类对象,存储的都是这两类对象。一类是马上就要被回收的;一类是将来肯定要晋升的。
那幸存区呢,你的大小就得大到把这两类对象都能够容纳。为什么这么说?这里要提到幸存区的一个晋升规则,如果幸存区比较小,它就会由jvm动态的去调整你的这个晋升的阈值。也许你本来有一些对象,轮不到他晋升,它的寿命还不够,但是由于幸存区的内存太少,导致提前把一些对象晋升到老年代区。也许是刚才我们说的这种存活时间较短的对象,提前被晋升到了老年代,那这样有什么问题呢?或者说有什么缺点呢,就是如果你本来一个存活时间短的对象被晋升到了老年代,那就意味着,他得等到老年代内存不足时触发Full GC时才能把它当成垃圾进行回收。所以这就是变相的延长了这个对象的生存的时间。这是一个不太好的地方。我们最好能实现,就是这种存活时间短的,在下次新生代的垃圾回收里就把它回收掉了。真正需要,长时间存活的对象,我才把它晋升到老年代,这是一条规则。
晋升阈值配置得当,让长时间存活对象尽快晋升
-XX:MaxTenuringThreshold=threshold 调整最大晋升阈值
-XX:+PrintTenuringDistribution
事物呢还是都有两面性,那么我们一方面希望的是存活时间短的对象让他留在幸存区,以便下一次垃圾回收能把它回收掉;而另一方面呢,我们又希望这个长时间存活的对象,他应该尽快的被晋升。
为什么这么说呢,因为如果你是一个长时间存活的对象,你把它留在幸存区里,只能够浪费我们幸存区的这个内存,并且呢,因为我们的新生代垃圾回收都是复制算法,要把这个幸存区中的这些对象,下次存活了又要把它进行复制复制,从from复制到to,我们前面也说过,那么这个新生代复制算法主要的耗费时间就是在这个对象的复制上,如果有大量的这些长时间存活的对象,他们不能及早的晋升,那么他们就相当于留在这个幸存区,被复制来复制去,这样呢,对我的性能其实反而是一个负担。所以遇到这种情况呢就要设置一下它的晋升阈值,把晋升阈值调的比较小,让这些长时间存活的对象呢,能够尽快的晋升到老年代区。这个参数:-XX:MaxTenuringThreshold=threshold 可以调整最大晋升阈值。
有的时候我们还需要把它晋升的一些详细信息显示出来,便于我去判断到底应该把晋升阈值设置为多少更为比较合适,相关的参数是下面这个参数:-XX:+PrintTenuringDistribution 这个参数呢,带上以后它就会在每次垃圾回收时把survival幸存区中的这些存活对象、详情显示出来。第一列,就是它的一个对象的年龄我们可以从这个事例中看到,年龄为1的,是刚逃过一次MinorGCC的这个对象,它占用的空间大小是多少;年龄2的对象在这个幸存区中的对象占用了多少的空间;为3的占用空间是多少;这是年龄为1、2、3的对象各自占用空间数,后面是他们的累计总和数——空间占用的累计总和,比如说1跟2加起来是3058888,123加起来是31784800。通过每次把这个幸存区中不同年龄的对象它所占用的空间打印,我们可以更细致的去决定到底把最大的晋升阈值调成多少比较合适,让那些长时间存在的影响能够尽早的晋升。这就是对新生代内存调优的一个说明。