本文共 6301 字,大约阅读时间需要 21 分钟。
下图是JVM内存配置参数图:
1)HeapSize
-Xmx --最大Heap Size,即上面图中的最大堆大小(注意:不包括永久代即Perm),该参数限制了年轻代和年老代的可分配最大值。
-Xms --初始化分配的Heap Size
注意:生产环境中-Xms一般设置成跟-Xmx,因为若-Xms不等于-Xmx,那么在某些场景下JVM可能要对Heap Size进行频繁的扩展和收缩,增加处理时间。
2)New/YoungGeneration Size
-Xmn --最大年轻代大小。即上图中的Eden+Survior0+Survior1+Virtual
-XX:NewSize --初始化年轻代大小。即上图中的Eden+Survior0+Survior1。(注意:在只设置了-Xmn不设置-XX:NewSize的情况下,NewSize等于mn)
注意:生产环境中一般只需设置-Xmn或者设置-Xmn和-XX:NewSize相等。理由,同HeapSize的设置一样,避免容量震荡消耗资源。
3)OldGeneration Size(Tenured)
-XX:NewRatio --Old Size/New Size,通过年老代和年轻代的比例和HeapSize就可以算出老年代的大小。一般默认为8,若Heap Size为1024m,则NewSize=HeapSize/(NewRatio+1)=114m,OldSize=HeapSize-NewSize=910m;
注意:-Xmn的优先级比-XX:NewRatio高。若-Xmn已指定,则OldSize=HeapSize-NewSize,无需按比例计算。生产环境中一般只需指定-Xmn就足够。
4)Eden和Survivor0、Survivor1
-XX:SurvivorRatio --Eden/Survivor0,即Eden区和Survivor0的比例,默认为8,若NewSize为114m,则Survior0=NewSize/(SurvivorRatio+2)=11.4m;
注意:Survivor0 == Survivor1,即Survivor0、Survivor1的职能是一模一样,又叫做From space和To Space,在每一次minor gc后角色会交换。
5)PermanentGeneration Size
-XX:MaxPermSize --最大持久代大小,默认为64m;
-XX:PermSize --初始化持久代大小,默认为16m;
注意:生产环境中一般设置MaxPermSize和PermSize相等,理由和HeapSize的设置一样,避免容量震荡消耗资源。
实际应用中,当应用引用的类比较多或者使用了一些动态类生产技术时应该加大该区的值,一般256m对于服务器程序足够用了。
PS:永久代的概念在jdk 1.7中已经没有了,在jdk 1.8 中已经被废除了,而是改为Metaspace即元空间,参数的设置如下:
6)ThreadStack Size
-Xss --线程堆栈大小。一般用于存放方法入口参数和返回值,以及原子类型的本地变量(即方法内部变量),一般可设置为128k。
7)DirectMemory Size
-XX:MaxDirectMemorySize --direct bytebuffer用到的本地内存。
注意:默认跟-Xmx相等,所以生产环境中一般不设置-Xmx大于物理内存的一半。
8)GC过程的两个重要参数:
-XX:--Survivor Space最大使用率,若存放的对象的总大小超过该值,将引起对象向Old区迁移,默认值为50,即使用率为50%;
-XX:MaxTenuringThreadhold --Young区对象的最大任期阈值,即可经历minor gc的次数,超过该次数的对象直接迁移到Old区,默认值为15。
另外补充MinorGC过程中的重要知识点:
在发生第一次Minor GC(即eden区的容量满了)时,如果没有被引用的对象将被垃圾回收,而哪些仍被引用的对象,将可能可能放在新生代的survivor区,或者直接去了老年代。步骤如下:
(1)在代码中经过一段时间的不断的new 对象(占用Eden区),有些对象没有被引用,而有些对象仍然被引用着。当new出的所有对象(包括没有被引用的)占用的空间达到新生代的eden堆的大小,这时会产生一次Minor GC;
(2)这次GC(也称Minnor GC)过程中,那些没有被引用的对象的内存将被回收(占用的内存被释放),而仍然被引用的对象(Eden区)将被转移到Survior区。 这个转移过程中如果存在以下情况,将会被转移到老年代:1)设置-XX:MaxTenuringThreshold=1(如果对象在Eden出生并经过第一次MinorGC后仍然存活,并且能够被Survior容纳的话,被移动到Survivor空间中,并且对象年龄设置为1。对象在Survior区中每“熬过”一次MinorGC,年龄就增加1岁,当它的年龄增加到一定程度(默认是15),则建辉被晋升到老年代; 2)Survivor空间中的相同年龄所有对象大小总和大于Survovor空间的一半,年龄大于等于该年龄的对象就可以直接进入老年代。举个栗子:
为了更好的讲解GC 分代收集器如何工作,以及JVM中的配置参数该如何配置,我们还是根据上述那个内存溢出的例子进行讲解,如下:
JVM中的参数配置如下:
class User{ private int _1MB = 1024*1024; byte[] size = new byte[_1MB]; String name; String sex; @Override public String toString() { return "User [name=" + name + ", sex=" + sex + "]"; }}public class MemoryLeak1 { public static void main(String[] args) { Listlist = new ArrayList (); int i=0; while(true){ System.err.println("Eden 添加的次数:" + (++i)); list.add(new User()); } }}
代码运行后,控制台输出如下:
Eden 添加的次数:1
Eden 添加的次数:2
Eden 添加的次数:3
Eden 添加的次数:4
[GC (Allocation Failure) [PSYoungGen:4086K->504K(4608K)] 4086K->3664K(30208K), 0.0067050 secs] [Times:user=0.06 sys=0.00, real=0.01 secs]
Eden 添加的次数:5
Eden 添加的次数:6
Eden 添加的次数:7
[GC (Allocation Failure) [PSYoungGen:3654K->504K(4608K)] 6814K->6736K(30208K), 0.0017185 secs] [Times:user=0.00 sys=0.00, real=0.00 secs]
Eden 添加的次数:8
Eden 添加的次数:9
Eden 添加的次数:10
[GC (Allocation Failure) [PSYoungGen:3655K->128K(4608K)] 9887K->9888K(30208K), 0.0016471 secs] [Times:user=0.00 sys=0.00, real=0.00 secs]
Eden 添加的次数:11
Eden 添加的次数:12
Eden 添加的次数:13
[GC (Allocation Failure) [PSYoungGen:3279K->128K(4608K)] 13039K->12976K(30208K), 0.0017214 secs] [Times:user=0.00 sys=0.00, real=0.00 secs]
Eden 添加的次数:14
Eden 添加的次数:15
Eden 添加的次数:16
[GC (Allocation Failure) [PSYoungGen:3279K->96K(4608K)] 16128K->16040K(30208K), 0.0014599 secs] [Times:user=0.00 sys=0.00, real=0.00 secs]
Eden 添加的次数:17
Eden 添加的次数:18
Eden 添加的次数:19
[GC (Allocation Failure) [PSYoungGen:3247K->96K(2560K)] 19192K->19120K(28160K), 0.0021796 secs] [Times:user=0.00 sys=0.00, real=0.00 secs]
Eden 添加的次数:20
Eden 添加的次数:21
Eden 添加的次数:22
Eden 添加的次数:23
Eden 添加的次数:24
Eden 添加的次数:25
Eden 添加的次数:26
[Full GC (Ergonomics) [PSYoungGen:1159K->1024K(2560K)] [ParOldGen: 25168K->25091K(25600K)]26328K->26116K(28160K), [Metaspace: 2599K->2599K(1056768K)], 0.0078799secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[Full GC (Allocation Failure) [PSYoungGen:1024K->1024K(2560K)] [ParOldGen: 25091K->25080K(25600K)]26116K->26105K(28160K), [Metaspace: 2599K->2599K(1056768K)], 0.0068014secs] [Times: user=0.08 sys=0.00, real=0.01 secs]
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid7372.hprof ...
Heap dump file created [27414229 bytes in0.023 secs]
Exception in thread "main"java.lang.OutOfMemoryError: Java heap space
atcom.tim.User.<init>(MemoryLeak1.java:9)
atcom.tim.MemoryLeak1.main(MemoryLeak1.java:24)
Heap
PSYoungGen total 2560K, used 1106K [0x00000000ffb00000, 0x0000000100000000,0x0000000100000000)
edenspace 2048K, 54% used[0x00000000ffb00000,0x00000000ffc14848,0x00000000ffd00000)
fromspace 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
to space 1536K, 0% used [0x00000000ffd00000,0x00000000ffd00000,0x00000000ffe80000)
ParOldGen total 25600K, used 25080K [0x00000000fe200000, 0x00000000ffb00000,0x00000000ffb00000)
object space 25600K, 97% used[0x00000000fe200000,0x00000000ffa7e370,0x00000000ffb00000)
Metaspace used 2631K, capacity 4486K, committed 4864K, reserved 1056768K
classspace used 279K, capacity 386K,committed 512K, reserved 1048576K
分析:
首先,看下我们JVM 参数设置如下:
-verbose:gc -Xms30m -Xmx30m -Xmn5m-XX:NewSize=5m -XX:MaxTenuringThreshold=2 -XX:+PrintGCDetails-XX:+HeapDumpOutOfMemoryError
上述参数中,-Xms30m表示初始化堆的大小为30m, -Xmx30m表示堆的大小最大为30m,生产环境中一般设置这两个参数一样大,避免内存重新分配等。-XX:xmn5m表示初始新生代为5m,-XX:NewSize=5m表示新生代最大为5m,默认如果不写-XX:NewSize,则-XX:NewSize等于-XX:xmn5m,又由于没有写-XX:SurvivorRatio,该参数表示Eden区域survivor区(两份)的比例,默认是8,因此Eden区大小为5/(8+1*2) * 8 = 4m;同时,我们也可以得出老年代的大小为30m-5m = 25m。
因此,有:
Eden 添加的次数:1
Eden 添加的次数:2
Eden 添加的次数:3
Eden 添加的次数:4
[GC (Allocation Failure) [PSYoungGen:4086K->504K(4608K)] 4086K->3664K(30208K), 0.0067050 secs] [Times:user=0.06 sys=0.00, real=0.01 secs]
即,Eden区添加了4此1M达到了一次Minor GC。此时,Eden中4M空间有被引用,因此,要向Survior移动,但是发现Survior最大才0.5m,由上述原则,如果容纳不下,则直接向老年代转移,因此,[PSYoungGen:4086K->504K(4608K)]年轻代中的4086k变成504k大小,减小的空间没有被回收,而是被转移到老年代4086K->3664K(30208K),0.0067050 secs,即GC前使用的总的堆大小为4086k,而GC之后使用的空间大小为3664k,这部分空间大部分是老年代的。
上述的代码,我们不难知道,每次GC之后,代码都直接转移到老年代,当老年代的25m被占用满之后,即发生OOM。