【JAVA优化编程】内存管理之——(9)JVM内存参数调优
9? JVM内存参数调优
我们前面所提到的堆内存(heap)是由Java虚拟机控制管理的,因此,这些参数对JVM而言都有一个默认值,但在某些情况下这些参数的默认值并不是最优的,这就需要我们通过调整这些参数的值来提高JVM的性能,最终提高应用的性能指标。在实际的应用开发中,如果应用所使用的系统内存较大,经常会引发内存溢出的错误:…java.lang.OutOfMemoryError <<no stack trace available>>java.lang.OutOfMemoryError <<no stack trace available>> Exception in thread "main"…这可能是因为应用要使用的堆内存(heap)超过了JVM所管理内存范围,如果我们适当追加内存值有时就可以避免这种致命错误的出现。在WINDOWS系统上你可以通过参数-verbosegc查看JVM回收内存的信息,在HP UNIX系统上你可以通过-Xverbosegc:file=/tmp/gc$$.out参数将信息重定向到一个文件中。然后查看相应的信息,例如下面的这个类。
public class A { public static void main(String args[]) { for (int i =0 ;i < 100000;++i) { A a = new A(); } System.out.println("this is a GC test"); }}在类A的main方法中创建了100 000个A对象,然后我们看一下JVM回收内存的情况,编译并执行这个类:>java -verbosegc A[GC 512K->91K(1984K), 0.0027537 secs]this is a ?GC test从输出信息中可以看出总共有1984KB的内存被回收,耗时0.002 753 7秒。现在我们将类A添加一行清除对象引用的代码:public class A { public static void main(String args[]) { for (int i =0 ;i < 100000;++i) { A a = new A(); a = null; } System.out.println("this is a GC test"); }}编译并执行这个类:>java -verbosegc A[GC 512K->91K(1 984K), 0.0 027 450 secs]this is a ?GC test我们看到被回收内存的数量并没有变化,但是回收所需要的时间却变成了0.002 745 0秒,后者比前者节省了0.000 008 7秒,千万不要小看这0.000 0087秒,当你的应用足够复杂时这个时间就会成指数级增长,看来我们主动清除对象引用的方法,确实可以加速JVM对垃圾内存的回收。如果再在类A中加入一行强制系统内存回收的代码,结果又会怎样呢?如下所示:public class A { public static void main(String args[]) { for (int i =0 ;i < 100000;++i) { A a = new A(); a = null; } System.gc(); System.out.println("this is a GC test"); }}编译并执行这个类:>java -verbosegc A[GC 512K->91K(1984K), 0.0 027 272 secs][Full GC 487K->91K(1984K), 0.0 070 730 secs]this is a ?GC test系统这次做了两次内存回收,第一次是程序中强制系统内存回收的代码System.gc()导致的内存回收,而后者是系统最终的内存回收操作,我们看到强制内存回收耗时不长,可是却导致了系统最终垃圾回收的时间加长了很多,因此我们在采用强制系统垃圾回收(通过显式调用方法System.gc())的办法来回收系统垃圾内存的办法,还是存在一些弊端的,应尽量少用,或者说只在必要的时候应用。上面我们提到的内存回收操作就是回收JVM所管理的堆内存(heap)。当系统连续申请内存并且超过JVM所管理的堆内存(heap)的最大值时,就会产生系统内存溢出的致命异常,下面我们来看一下怎样通过设置JVM的内存参数来优化JVM对内存的管理,避免内存溢出异常的发生。下表所示的就是与JVM内存相关的参数及其说明。与JVM内存相关的参数及其说明JVM堆内存(heap)设置选项参数格式说明public class A { public static void main(String args[]) { for (int i =0 ;i < 100000;++i) { A a = new A(); } }}通过上面这三种方式编译后的类文件的大小分别为:默认编译方式:291字节。调试编译方式:422字节。代码编译方式:207字节。采用三种不同的方式,编译产生的类文件的大小差异非常大,这是什么原因导致的呢?原来在于.class文件中包含多个不同的部分或属性。代码(Code)属性包含实际的方法字节码。源文件信息(SourceFileInformation)包含用于生成.class的源文件名称。代码行序号表(LineNumberTable)用来映射源文件中的代码行序号与字节码文件中的序号偏移。本地变量表(LocalVariableTable)用来映射本地变量与栈桢的偏移。