堆配置
堆大小设置
当Java进程启动时,虚拟机就会分配一块初始堆空间,可以使用-Xms
指定这块空间的初始大小。如果初始堆耗尽,虚拟机就会对堆进行扩展(如果可能的话),最大堆空间可以使用参数-Xmx
指定。
public class HeapAlloc{ public static void main(String []args){ System.out.println("maxMemory="+Runtime.getRuntime().maxMemory()+"bytes"); //最大可用内存 System.out.println("fee mem="+Runtime.getRuntime().freeMemory()+"bytes");//当前空闲内存 System.out.println("total mem="+Runtime.getRuntime().totalMemory()+"bytes");//当前总内存 byte[] b=new byte[1*1024*1024]; System.out.println("分配了1M空间给数组"); System.out.println("maxMemory="+Runtime.getRuntime().maxMemory()+"bytes"); System.out.println("fee mem="+Runtime.getRuntime().freeMemory()+"bytes"); System.out.println("total mem="+Runtime.getRuntime().totalMemory()+"bytes"); b=new byte[4*1024*1024]; System.out.println("分配了4M空间给数组"); System.out.println("maxMemory="+Runtime.getRuntime().maxMemory()+"bytes"); System.out.println("fee mem="+Runtime.getRuntime().freeMemory()+"bytes"); System.out.println("total mem="+Runtime.getRuntime().totalMemory()+"bytes"); }}
现在使用java -Xmx20m -Xms5m -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseSerialGC HeapAlloc
来运行,看看日志输出。
其中-Xmx20
设置最大堆空间为20M,-Xms5m
设置初始堆为5M,-XX:+PrintCommandLineFlags
表示输出Java虚拟机启动的参数,+PrintGCDetails
,打印详细的日志,-XX:+UseSerialGC
表示使用串行垃圾回收器。
output:
-XX:InitialHeapSize=5242880 -XX:MaxHeapSize=20971520 -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseSerialGC maxMemory=20316160bytes //20971520(20M)-655360fee mem=5550672bytes //5.29Mtotal mem=6094848bytes //5.8M分配了1M空间给数组maxMemory=20316160bytesfee mem=4502080bytes //4.29Mtotal mem=6094848bytes //5.8M[GC (Allocation Failure) [DefNew: 1555K->192K(1856K), 0.0015332 secs][Tenured: 1107K->1298K(4096K), 0.0014706 secs] 1555K->1298K(5952K), [Metaspace: 2598K->2598K(1056768K)], 0.0030620 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 分配了4M空间给数组maxMemory=20316160bytesfee mem=4800984bytes //4.5Mtotal mem=10358784bytes //9.8MHeap def new generation total 1920K, used 51K [0x00000007bec00000, 0x00000007bee10000, 0x00000007bf2a0000) eden space 1728K, 2% used [0x00000007bec00000, 0x00000007bec0cc60, 0x00000007bedb0000) from space 192K, 0% used [0x00000007bedb0000, 0x00000007bedb0000, 0x00000007bede0000) to space 192K, 0% used [0x00000007bede0000, 0x00000007bede0000, 0x00000007bee10000) tenured generation total 8196K, used 5394K [0x00000007bf2a0000, 0x00000007bfaa1000, 0x00000007c0000000) the space 8196K, 65% used [0x00000007bf2a0000, 0x00000007bf7e4858, 0x00000007bf7e4a00, 0x00000007bfaa1000) Metaspace used 2605K, capacity 4486K, committed 4864K, reserved 1056768K class space used 284K, capacity 386K, committed 512K, reserved 1048576K
首先,第一行是-XX:+PrintCommandLineFlags
命令的效果。
其次,根据程序的输出可以看出,运行时的JVM堆的最大可用
内存并不完全等于启动时设置的最大值20M,而是比20M少那么一点,原因之一是在新生代中,存在from/to
两个分区,这两个分区事实上只有一个是属于可用
的;还有一个原因是虚拟机内部并没有直接使用新生代的from/to
的大小,而是对它们做了一个对齐操作,需要占用一些堆空间。在这里,对齐操作的空间大小为655360字节。而对于当前空闲内存
和当前总内存
也是一样,都和启动时设置的参数有点差距。
在执行完分配1M数组(分配在新生代)后,可以发现,当前空闲内存
减少了1M。
[GC (Allocation Failure) [DefNew: 1555K->192K(1856K), 0.0015332 secs][Tenured: 1107K->1298K(4096K), 0.0014706 secs] 1555K->1298K(5952K), [Metaspace: 2598K->2598K(1056768K)], 0.0030620 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
当虚拟机准备分配一个4M的空间给数组时,堆的最大可用内存只有4.29M,划分下eden,to,from区,自然就无法存放一个4M的数组了。因此堆空间进行扩展。随后触发Full GC,新生代内存1555K->192K
减少,之前的1M数组对象被移到老年代1107K->1298K
。1555K->1298K(5952K)
显示的是当前整个堆大小从1555K变成1298K,其中的1555K
也就是分配完1M数组后total mem-fee mem=(6094848-4502080)/1024=1555K
可以从最后的Heap信息看出,新生代可用内存为1920K,使用51K。老年代可用内存为8196K,使用5394K(1M,4M数组都存放在这里)。新生代和老年代可用内存总和正好为total mem=10358784bytes
。
新生代设置
Java虚拟机提供了参数-Xmn
来设置新生代的大小。而参数-XX:SurvivorRatio
则可用来设置新生代中eden/from(to)
的比例关系。下面看个例子
public class NewSizeDemo{ public static void main(String[] args) { byte b[]=null; for (int i=0; i<10; i++) { b=new byte[1*1024*1024]; } }}
使用 java -Xmx20m -Xms20m -Xmn2m -XX:SurvivorRatio=2 -XX:+PrintGCDetails NewSizeDemo
来执行,结果如下
Heap PSYoungGen total 1536K, used 497K [0x00000007bfe00000, 0x00000007c0000000, 0x00000007c0000000) eden space 1024K, 48% used [0x00000007bfe00000,0x00000007bfe7c440,0x00000007bff00000) from space 512K, 0% used [0x00000007bff80000,0x00000007bff80000,0x00000007c0000000) to space 512K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007bff80000) ParOldGen total 18432K, used 10240K [0x00000007bec00000, 0x00000007bfe00000, 0x00000007bfe00000) object space 18432K, 55% used [0x00000007bec00000,0x00000007bf6000a0,0x00000007bfe00000) Metaspace used 2599K, capacity 4486K, committed 4864K, reserved 1056768K class space used 284K, capacity 386K, committed 512K, reserved 1048576K
尽管新生代的eden
区正好可以存放一个1M的数组,但是从打印的日志可以看出,数组全部存放在了老年代。 使用java -Xmx20m -Xms20m -Xmn7m -XX:SurvivorRatio=2 -XX:+PrintGCDetails -XX:+UseSerialGC NewSizeDemo
命令,区别在于扩大了新生代的可用内存大小。 结果如下:
[GC (Allocation Failure) [DefNew: 2582K->1298K(5376K), 0.0015075 secs] 2582K->1298K(18688K), 0.0015396 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [DefNew: 4439K->1024K(5376K), 0.0011856 secs] 4439K->1296K(18688K), 0.0012097 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [DefNew: 4158K->1024K(5376K), 0.0007845 secs] 4430K->1296K(18688K), 0.0008061 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] Heap def new generation total 5376K, used 3209K [0x00000007bec00000, 0x00000007bf300000, 0x00000007bf300000) eden space 3584K, 60% used [0x00000007bec00000, 0x00000007bee22420, 0x00000007bef80000) from space 1792K, 57% used [0x00000007bf140000, 0x00000007bf240010, 0x00000007bf300000) to space 1792K, 0% used [0x00000007bef80000, 0x00000007bef80000, 0x00000007bf140000) tenured generation total 13312K, used 272K [0x00000007bf300000, 0x00000007c0000000, 0x00000007c0000000) the space 13312K, 2% used [0x00000007bf300000, 0x00000007bf344178, 0x00000007bf344200, 0x00000007c0000000) Metaspace used 2600K, capacity 4486K, committed 4864K, reserved 1056768K class space used 284K, capacity 386K, committed 512K, reserved 1048576K
刚开始数组都分配在eden区,分配到第3次,eden装不下,触发GC,留下一个数组,DefNew: 2582K->1298K(5376K)
,分配到第6个,eden装不下,触发GC,留下一个数组,DefNew: 4439K->1024K(5376K)
,分配到第9个,eden装不下,触发GC,留下一个数组,DefNew: 4158K->1024K(5376K)
,加上第9个数组和第10个数组,最后剩下三个数组在新生代。
使用java -Xmx20m -Xms20m -Xmn15m -XX:SurvivorRatio=8 -XX:+PrintGCDetails -XX:+UseSerialGC NewSizeDemo
执行时,结果如下
def new generation total 13824K, used 11224K [0x00000007bec00000, 0x00000007bfb00000, 0x00000007bfb00000) eden space 12288K, 91% used [0x00000007bec00000, 0x00000007bf6f6038, 0x00000007bf800000) from space 1536K, 0% used [0x00000007bf800000, 0x00000007bf800000, 0x00000007bf980000) to space 1536K, 0% used [0x00000007bf980000, 0x00000007bf980000, 0x00000007bfb00000) tenured generation total 5120K, used 0K [0x00000007bfb00000, 0x00000007c0000000, 0x00000007c0000000) the space 5120K, 0% used [0x00000007bfb00000, 0x00000007bfb00000, 0x00000007bfb00200, 0x00000007c0000000) Metaspace used 2599K, capacity 4486K, committed 4864K, reserved 1056768K class space used 284K, capacity 386K, committed 512K, reserved 1048576K
可以看出,新生代内存足够存放10M的数组,于是所有的数组都分配在新生代,没有GC。
基本策略
尽可能将对象预留在新生代,减少老年代的GC次数,比如像上面例子中数组都分配在老年代就不好了
扩展
Java虚拟机还提供了一个参数-XX:NewRatio
,用来设置老年代/新生代的比例。
导出堆信息
当发生堆溢出时,为了及时获取堆信息,可以使用-XX:+HeapDumpOnOutOfMemoryError
,在发生堆溢出时导出堆信息,并且,可以使用-XX:+HeapDumpPath
指定导出路径
例如:
public class DumpOOM{ public static void main(String[] args) { java.util.Vector vector=new java.util.Vector(); for(int i=0;i<25;i++){ vector.add(new byte[1*1024*1024]); } }}
使用 java -Xmx20m -Xms5m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/Users/ruochenxing/Desktop/java/out.dump DumpOOM
执行,设置最大堆为20m,所以一定会OutOfMemoryError。
除此之外,还可以在溢出后执行某个脚本 ` java -Xmx20m -Xms5m "-XX:OnOutOfMemoryError=脚本的路径 %p" -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/Users/ruochenxing/Desktop/java/out.dump DumpOOM
获取GC信息
简要: -verbose:gc -XX:+PrintGC
详细: -XX:+PrintGCDetails
更详细: -XX:+PrintHeapAtGC
GC与应用程序相互执行耗时:-XX:+PrintGCApplicationStoppedTime - XX:+PrintGCApplicationConcurrentTime
打印GC发生的时间: -XX:+PrintGCTimeStamps
打印新生对象晋升为老年代的实际阈值:-XX:+PrintTenuringDistribution
指定最大阈值: -XX:MaxTenuringThreshold=18
输出到文件: -Xloggc:c:\gc.log `
非堆配置
方法区配置
-
JDK6,7中,可以使用-XX:PermSize(初始大小) 和 -XX:MaxPermSize(最大值)配置永久区大小
-
JDK8中,永久区被彻底移除,使用了新的元数据区存放类的元数据。默认情况下,元数据区只受系统可用内存的限制,但仍然可以使用参数-XX:MaxMetaspaceSize指定元数据区的最大可用值。
栈配置
-Xss指定线程的栈大小
直接内存配置
在Java NIO被广泛使用的直接内存(非Java堆内)也可以通过使用参数 -XX:MaxDirectMemorySize设置,如不设置,默认为最大堆空间,即-Xmx。 如果直接内存达到设定值,就会触发GC,如果不能有效释放足够的空间,直接内存溢出同样会抛OOM。 关于直接内存的访问和申请测试。
import java.nio.*;public class AccessDirectBuffer{ //直接内存访问 public void directAccess(){ long startTime=System.currentTimeMillis(); ByteBuffer buffer=ByteBuffer.allocateDirect(500); for(int i=0;i<100000;i++){ for(int j=0;j<99;j++){ buffer.putInt(j); } buffer.flip(); for(int j=0;j<99;j++){ buffer.getInt(); } buffer.clear(); } long endTime=System.currentTimeMillis(); System.out.println("testDirectWrite:"+(endTime-startTime)); } //堆内存访问 public void bufferAccess(){ long startTime=System.currentTimeMillis(); ByteBuffer buffer=ByteBuffer.allocate(500); for(int i=0;i<100000;i++){ for(int j=0;j<99;j++){ buffer.putInt(j); } buffer.flip(); for(int j=0;j<99;j++){ buffer.getInt(); } buffer.clear(); } long endTime=System.currentTimeMillis(); System.out.println("testBufferWrite:"+(endTime-startTime)); } public static void main(String[] args) { AccessDirectBuffer all=new AccessDirectBuffer(); //热身代码,忽略输出 all.bufferAccess(); all.directAccess(); all.bufferAccess(); all.directAccess(); }}/*java -client AccessDirectBuffertestBufferWrite:36testDirectWrite:22testBufferWrite:16testDirectWrite:12直接内存访问比堆内存访问快java -server AccessDirectBuffertestBufferWrite:36testDirectWrite:25testBufferWrite:18testDirectWrite:31直接内存访问比堆内存访问慢 */
import java.nio.*;public class AllocDirectBuffer{ public void directAlloc(){ long startTime=System.currentTimeMillis(); for(int i=0;i<200000;i++){ ByteBuffer buffer=ByteBuffer.allocateDirect(1000); } long endTime=System.currentTimeMillis(); System.out.println("directAllo:"+(endTime-startTime)); } public void bufferAlloc(){ long startTime=System.currentTimeMillis(); for(int i=0;i<200000;i++){ ByteBuffer buffer=ByteBuffer.allocate(1000); } long endTime=System.currentTimeMillis(); System.out.println("bufferAllo:"+(endTime-startTime)); } public static void main(String[] args) { AllocDirectBuffer all=new AllocDirectBuffer(); all.directAlloc(); all.bufferAlloc(); all.directAlloc(); all.bufferAlloc(); }}/*java -server AllocDirectBuffer directAllo:166bufferAllo:126directAllo:137bufferAllo:103直接内存空间申请比堆内存慢java -client AllocDirectBufferdirectAllo:168bufferAllo:125directAllo:133bufferAllo:101直接内存空间申请比堆内存慢 */
直接内存适合申请次数较少,访问频繁的场合;内存空间如果需要频繁申请,不适合用直接内存
工作模式:
使用java -version
查看。默认情况下是server
如果想要使用client模式,则使用命令java -client ...
即可。
- 与client模式相比,server模式启动较慢,此模式会尝试收集更多的系统性能信息,使用更复杂的优化算法对程序进行优化。
- 当系统进入稳定期后,server模式的执行速度会远远快于client模式,对于后台长期运行的系统,可使用server模式,对于用户界面程序,可使用client模式。
- 在64位机器上,更倾向于server模式