本篇仅记录个人经验。这方面的详细资料可参考:http://www.oracle.com/technetwork/java/javase/tech/vmoptions-jsp-140102.html
以Oracle(Sun) HotSpot虚拟机为例,JVM内存调优涉及三大块,其中线程堆栈大小大多数情况下不需要调整,PermGen对于大多数应用来说只要启动起来,后面基本不会增涨,也比较好调。所以主要还在于Heap内存,也即堆内存。
Heap区域分成三块:Eden, Survivor, Old Gen。可以形象地比喻为少年、成人、老人。不过这个比喻带来一个严重的后果,就是在JVM的世界里,往往只有少数人能活到成年,更少的人可以活到老年。
一个Java程序需要多少堆内存?也许通过并发量、每任务的资源占用等可以估出一个数值,但最后发现这个数值不够用,甚至有可能差很多。这是怎么回事呢?我们来具体分析一下。
在阴森恐怖的JVM世界里,住着一位江湖大佬,它的名字叫GC。它常常关注这个世界里的青少年人群,一但它们人满为患,或者数量超过了一个阀值,这位大佬就会血洗Eden区域,几乎将所有青少年杀光。
少数幸存的青少年逃到Survivor区域,它们经历过这特殊的成人礼,就慢慢变成成人了。然而江湖大佬GC并不会放过这片区域,它会时不时的来向成人发动攻击,有些成人会葬身“存活区”,这个区并不像它的名字那样美好。
在GC的时不时的洗劫之下,饱经沧桑的成人,慢慢迈向老年,他们开始向老年人居住区迁移。江湖大佬GC对老年人兴趣并不大,但如果那里人满为患,或是越过了指定的阀值,它一样会毫不留情地血洗老年人居住区。
我们看到,这些人是需要搬来搬去来躲避江湖大佬GC的,所以如果你需要10个人来干活,通常你需要不止10个人的土地面积。
对于大吞吐量的程序(频繁创建临时变量),其对应的JVM世界往往是荒蛮型的,出生率很高,年轻化很严重,大多数对象甚至没机会成人。对于此情形,往往要将年轻人居住区调大些,以降低GC大佬血洗的频率,提高数据吞吐能力。这时此区域往往从近乎于零开始增涨,满了这后又变成接近零,一直这样重复下去。当然这样会有一较多空间浪费,不过很多时候不得不这样。-XX:NewRatio=n参数用于指定Eden+Survivor : Old Gen = 1 : n,默认情况下Old Gen比较大,对于吞吐型的,可以将这一比例调为1:1,甚至可以让新生代更大一些。但这个比值不支持小数,如果需要,可以用Xmn来指定Eden+Survivor的总大小。
同时在Eden与Survivor之间也可以通过一个比值来调整其大小分配。在Survivor不够用时,成人会变成啃老族,向老人区借地方用。当然,尽量互不干扰比较好些。
老年人居住区住的都是老寿星,比如应用程序中的生命周期较长的对象。如果应用程序是某种缓存服务器,那这将与典型大吞吐应用有所不同,因为这种应用可能会老龄化。这时要怎么调呢?用默认值?或进一步加大Old Gen?
其实无论什么应用,都要看具体情况来调这些参数。前面的介绍,只是想说明这些内存区域的特点和工作流程。应用程序中的临时变量、会话级变量、长生命周期的变量,这些变量的数量及它们之间的比例,是决定如何调节堆内存的重要因素。当然,实际调优往往是通过实际观查来决定,而不是看代码。除了这些,熟悉内存回收的发生机制也是必要的,知道这一点,就不会再问为什么理论上给够了内存却还可能出问题。