全部博文(323)
分类: Delphi
2008-06-16 22:45:39
无论如何,不要让程序范围级的变量,静态变量,长生命周期的类存储对象,事实上,你需要在内存模拟器中去分析泄漏。
异常的持久空间
容易误解JVM为持久空间分配内存的目的。堆仅仅存储类的实例,但JVM在堆中创建类实例之前,它必须把字节流文件(.class文件)装载到程序内存中。它利用内存中的字节流在堆中创建类的实例。JVM利用程序的内存来装载字节流文件,这个内存空间称为持久空间。图6显示了持久空间和堆的关系:它存在于JVM程序中,并不是堆的一部分。
Figure 6. The relationship between the permanent space and the heap
通常,你可能想让你的持久空间足够大以便于它能够装载你程序所有的类,因为很明显,从文件系统中读取类文件比从内存中装载代价高很多。JVM提供了一个参数让你不的程序不卸载已经装载到持久空间中的类文件:
–noclassgc
这个参数选项告诉JVM不要跑到持久空间去执行垃圾收集释放其中已经装载的类文件。这个参数选项很聪明,但是会引起一个问题:当持久空间满了以后依然需要装载新文件的时候JVM会怎么处理呢?我观测到的资料说明:如果JVM检测到持久空间还需要内存,就会调用主垃圾收集程序。垃圾收集器清除堆,但它并不会对持久空间进行任何操作,因此它的努力是白费的。于是JVM就再重新检测持久空间,看它是否满,然后再次执行程序,一遍的一遍重复。
我第一次碰到这种问题的时候,用户抱怨说程序性能很差劲,并且在运行了几次后就出现了问题,可能是内存溢出问题。在我调查了详细的关于堆和程序内存利用的收集器的记录后,我迅速发觉堆的状态非常正常,但程序确发生了内存溢出。这个用户维持了数千的JSP页面,在装载到内存前把他们都编译成了字节流文件放入持久空间。他的环境已经造成了持久空间溢出,但是在堆中由于用了 -noclassgc 选项,于是JVM并不去释放类文件来装载新的类文件。于是就导致了内存溢出错误,我把他的持久空间改为512M大小,并去掉了 -noclassgc 参数。
正像图7显示的,当持久空间变满了的时候,就引发垃圾收集,清理了乐园和幸存者空间,但是并不释放持久空间中的一点内存。
Figure 7. Garbage collection behavior when the permanent space becomes full. Click on thumbnail to view full-sized image.
注意
当设置持久空间大小时候,一般考虑128M,除非你的程序有很多的类文件,这个时候,你就可以考虑使用256M大小。如果你想让他能够装载所有的类的时候,就会导致一个典型的结构错误。设置成512M就足够了,它仅仅是暂时的时间的花费。把持久空间设置成512M大小就象给一个脚痛的人吃止痛药,虽然暂时缓解了痛,但是脚还是没有好,依然需要医生把痛治疗好,否则只是把问题延迟了而已。
线程池
外界同WEB或程序服务器连接的主要方法就是向他们发送请求,这些请求被放置到程序的执行次序队列中。和内存最大的冲突就是程序服务器所设置的线程池的大小。线程池的大小就是程序可以同时处理的请求的数量。如果池太小,请求就需要在队列中等待程序处理,如果太大,CPU就需要花费太多的时间在这些众多的线程之间来回的切换。
每个服务器都有一个SOCKET负责监听。程序把接受到的请求放到待执行队列中,然后将这个请求从队列移动到线程中被程序处理。
图8显示了服务器的处理程序。
Figure 8. 服务器处理请求的次序结构
线程池太小
每当我碰到有人抱怨装载速度的性能随着装载的数量的增加变的越来越糟糕的时候,我会首先检查线程池。特别是,我在看到下面这些信息的时候:
当一个线程池被待处理的请求装满的时候,响应的时间就变的极其糟糕,因为这些在队列中等待处理的请求会消耗很多的额外时间。这个时候,CPU的利用率会非常低,因为程序服务器没有时间去指挥CPU工作。这个时候,我会按一定幅度增加调节池的大小,并在未处理请求的数量减少前一直监视程序的吞吐量,你需要一个合理甚至更好的负载量者,一个精确的负载量测试工具可以准确的帮你测试出结果。当你观测吞吐量的时候,如果你发现吞吐量降低了,你就应该把池的大小下调一个幅度,一直到找到让它保持最大吞吐量的大小为止。
图9显示了连接池太小的情况
Figure 9. 所有的线程都被占用了,请求就只能在队列中等待
每当我阅读性能调整手册的时候,最让我头疼的就是他们从来不告诉你特殊情况下线程池应该是多大。由于这些值非常依赖程序的行为,他们只告诉你大普通情况下正确的大小,但是他们给了你一个范围内的值,这对用户很有利的。例如考虑下面2种情况::
但是,很多程序并没有在这种情况下动态的去调整的功能。多数情况下是做相同的事,但是应该为他们划分范围。因此,我建议你为一个CPU分配50到75个左右的线程。对一些程序来说,这个数量可能太少,对另一个些来说可能太多,我刚开始为每个CPU分配50到75个线程,然后根据吞吐量和CPU的性能,并做适当的调整。
线程池太大
除了线程池数量太小之外的情况外,环境也可能把线程数量配置的过大。当这些环境中的负载量不断增大的时候,CPU的使用率会持续无法降低,就没有什么响应请求的时间了,因为CPU只顾的在众多的线程之间来回的切换跳动,没时间让线程去做他们应该做的事了。
连接池过大的最主要的迹象就是CPU的使用率一直很高。有些时候,垃圾收集也可能导致CPU使用率很高,但是垃圾收集导致的CPU使用率很高和池过大导致的使用率有一个主要的区别就是:垃圾收集引起的只是短时间的高使用率就象个钉子,而池过大导致的就是一直持续很高呈线性。
这个情况发生的时候,请求会被放在队列中不被处理,但是不会始终如此,因为请求占用CPU的情况和程序占用的情况造成的后果不同。降低线程池的大小可能会让请求等待,但是让请求等待总比为了处理请求而让CPU忙不过来的好。让CPU保持持续的高使用率,同时性能不降低,新请求到来的时候放入到队列中,这是最理想的程序。考虑下面这个很类似的情况:很多高速公里有交通灯来保证车辆进入到拥挤的公里中。在我看来,这些交通灯根本没用,道理很充分。比如你来了,在交通灯后面的安全线上等待进入到高速公路上。如果所有的车辆都同时涌向公里,我们就动弹不得,但是只要减缓涌向高速公路车辆的速度,交通迟早会畅通。事实上,很多的大城市都有这样功能,但根本没用,他们真正需要的是一些更多的小路(CPU),涌向高速公路的速度真的降低了,那么交通会变的正常起来。