Seda体系架构与Sun JVM垃圾回收机制的关系
使用Seda架构时总会利用队列传递消息。在java应用中需要注意使用seda队列对内存的使用模式,否则可能会因为jvmgarbage的原因降低系统的处理能力。
在讨论seda队列的机制之前,需要了解一下Sunjvmgarbage的的设计原则:
1)大部分对象总是会在新生代被分配以及释放。
2)旧生代对象引用新生代对象的情况很少出现。
如果违背以上原则,将可能诱发频繁的FullGC导致应用的处理能力急剧下降。严重的情况下会导致秒级频率的FullGC。
下面我们看一下Seda机制下的队列的问题。
1:原始问题:队列囤积的大量消息,那么将违背原则1),并诱发不必要的FullGC。
Seda的设计常常导致应用分成不同的片段,不同的片段之间使用队列传递消息衔接。这样一旦某个片段的执行速度相对较慢,则可能会导致队列囤积大量的消息。如果没有对队列的实现进行优化,则会违背garbage设计原则1。从而导致大量fullgc出现——因为此时队列囤积的消息往往会被转移到旧生代,那么将会出现频繁的释放旧生代内对象的情况。此时诱发高频率的fullgc。
解决这一问题的思路:
1)减少队列中消息的存储空间,并适当增加jvm新生代的内存比例,以避免队列的消息被转移到旧生代。
2)让队列的存储空间固定下来,不随着消息的个数变化而变化。
思路1的应对方法:
1)减少队列大小:此方法虽然简单,但较难实践。因为很难在系统的容错性以及队列的内存性能上进行平衡。从系统容错的角度,我们常常希望队列足够大,并且能够应对一定程度的峰值。
2)减少队列中消息对象的大小
一个消息对象中最大的内存开销往往是消息的数据内容,因此我们可以考虑使用单独的机制存储所有消息的数据内容,以避免队列占用过大的开销。在适当提高jvm新生代比例的情况下,可以有较高的几率让队列消息仍然保持在新生代内。
方法:最简单的方法是使用文件系统存储。或者可以使用单独维护的一片内存空间,该空间的内存大小相对固定,使用这个空间来分配每个消息内容所需要的存储。再次也可以考虑使用sunjvm的提供directmemory技术。
Mule的社区版本的队列使用了上述机制。
思路2的应对方法
内存队列初始创建时,就将消息对象的空间全部分配。添加消息到队列时,实际上是将消息的内容复制到队列中某个闲置的空消息。从队列取消息时,实际上是将队列内部的消息对象复制一份同样内容的消息并返回给接收者(或者直接引用)。
这样有以下2点优势:1)队列以及预分配的消息被最终被交换到旧生代后,将不再占用对新生代的内存。2)旧生代内的队列以及预分配消息不会被释放,从而满足了garbage的设计原则1。达到较好的gc效果。
注意事项:如果消息对象内部嵌套引用其他对象,务必要保证内部引用所有对象都按照预分配内存原则进行。否则将违背垃圾回收的设计原则2.如果内部的对象使用了jdk的类型,无法满足此原则时,此时必须要使用其他的方法来回避直接使用此类对象
譬如String。如果我们的消息对象使用了String属性,那么将无法满足该要求:无法为队列内部的预分配消息对象的String属性预留一定的内存空间,并且当PutMessage进入队列时,将要put的消息的String属性的内容拷贝给预分配了空间的消息的String属性。
而且我们也不能简单的把所要put消息的String对象简单的赋值给队列内部的Message对象对应的String成员。因为这将违背Garbage的设计原则2————旧生代对象(队列内的预分配消息对象)引用了在新生代上分配的String对象(被put的消息的String对象)。此时也会诱发不必要的FullGC。通常建议使用其他方法绕开对此类对象的使用,譬如使用char数组来代替String。通过简单的封装(getXXX),我们仍然可以让应用使用String类型的属性,而不是直接使用复杂的char数组数据结构。
LMAX的队列机制使用了这一方法,但使用时务必需要确保Message对象内部所有成员都能够预分配内存。