《深入理解Java虚拟机》读后总结(二)JVM内存分配

《深入理解Java虚拟机》读后总结

(一)Sun HotSpot JVM内存模型

(二)Sun HotSpot JVM内存分配

(三)Sun HotSpot JVM内存监控


基于Sun HotSpot JVM

请先了解JVM内存模型在来看此篇文章

使用对JVM不同内存区域灌入数据,导致相关区域内存溢出,来验证JVM内存分配


先看一个经典问题:



1
2
3
4
5
6
7
8
9
10
11
12
13
String s1 = "小金子(aub)";
String s2 = "小金子(aub)";
String s3 = "小金子"+ "(aub)";
String s4 = newString("小金子(aub)");
String s5 = "小金子"+ newString("(aub)");
String s6 = s4.intern();
System.out.println("s1 == s2: "+ (s1 == s2));//true;
System.out.println("s1 == s3: "+ (s1 == s3));//true;
System.out.println("s2 == s3: "+ (s2 == s3));//true;
System.out.println("s1 == s4: "+ (s1 == s4));//false;
System.out.println("s1 == s5: "+ (s1 == s5));//false;
System.out.println("s4 == s5: "+ (s4 == s5));//false;
System.out.println("s1 == s6: "+ (s1 == s6));//true;

原因就在与String对象特殊的内存分配方式:(Strings pool是JVM内存中运行时常量池的一部分)

1.String s1 = new String("小金子(aub)");

2.String s2 = "小金子(aub)";
3.String s3 = "小金子" + "(aub)";

  虽然两个语句都是返回一个String对象的引用,但是jvm对两者的处理方式是不一样的。

对于第一种,jvm会马上在heap中创建一个String对象,然后将该对象的引用返回给用户。

对于第二种,jvm首先会在内部维护的strings pool中通过String的 equels 方法查找是对象池中是否存放有该String对象,如果有,则返回已有的String对象给用户,而不会在heap中重新创建一个新的String对象;如果对象池中没有该String对象,jvm则在heap中创建新的String对象,将其引用返回给用户,同时将该引用添加至strings pool中。

注意:使用第一种方法创建对象时,jvm是不会主动把该对象放到strings pool里面的,除非程序调用 String的intern方法

对于第三种,jvm会进行“+”运算符号的优化,两遍都是字符串常量会做类似于第二种的处理,如果“+”任意一边是一个变量,就会做类似第一种的处理。


JVM栈和Native Method栈内存分配:

JAVA中八个基本类型数据,在运行时都是分配在栈中的。

测试代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
publicclassJvmStackOOM {
privateintstackLength = 1;
publicvoidexecute() {
try{
stackLeak();
} catch(Throwable e) {
System.out.println("stackLength : "+ stackLength);
e.printStackTrace();
}
}
privatevoidstackLeak() {
stackLength++;
stackLeak();
}
}

用一个递归不断地对实例变量stackLength进行自增操作,当JVM在扩展栈时无法申请到足够的空间,将产生StackOverflowError

可以使用Jvm 参数-Xss配置栈大小,例如:-Xss2M


方法区内存分配:

类信息和运行时常量将会分配到此区域。

测试代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
publicclassJvmRuntimeConstantPoolOOM {
privateintruntimeConstantCount = 1;
publicvoidexecute() {
try{
runtimeConstantLeak();
} catch(Throwable e) {
System.out.println("runtimeConstantCount : "+ runtimeConstantCount);
e.printStackTrace();
}
}
privatevoidruntimeConstantLeak() {
List<String> list = newArrayList<String>();
while(true) {
list.add(String.valueOf(runtimeConstantCount++).intern());
}
}
}

使用String的intern()方法向方法区中灌入数据,当方法区内存不足时,抛出OutOfMemoryError: PermGen space,

也可以加载过多的类的方式,测试是否有OutOfMemoryError: PermGen space异常,如果有说明类信息也是存放在方法区中的可以

使用Jvm 参数-XX:PermSize和-XX:MaxPermSize配置栈大小,例如:-XX:PermSize=10M -XX:MaxPermSize=10M


堆内存分配:

所有对象实例及数组都会在堆上分配。

测试代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
publicclassJvmHeapOOM {
privateintbojectCount = 1;
publicvoidexecute() {
try{
heapLeak();
} catch(Throwable e) {
System.out.println("bojectCount : "+ bojectCount);
e.printStackTrace();
}
}
privatevoidheapLeak() {
List<OOMObject> list = newArrayList<OOMObject>();
while(true) {
list.add(newOOMObject());
bojectCount++;
}
}
privateclassOOMObject {
}
}

创建多个OOMObject对象放到List中,当堆内存不足时,产生OutOfMemoryError:Java Heap space

使用Jvm 参数-Xm -Xmx -Xmn -XX:SurvivorRatio配置堆,例如:-Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8


本地直接内存分配:

堆外内存,NIO相关操作将在此分配内存

使用Jvm 参数-XX:MaxDirectMemorySize配置,例如:-XX:MaxDirectMemorySize=10M


所有用到的JVM启动参数:
-Xss2M       设置JVM栈内存大小
-Xms20M    设置堆内存初始值
-Xmx20M    设置堆内存最大值
-Xmn10M    设置堆内存中新生代大小
-XX:SurvivorRatio=8  设置堆内存中新生代Eden 和 Survivor 比例
-XX:PermSize=10M  设置方法区内存初始值
-XX:MaxPermSize=10M  设置方法区内存最大值
-XX:MaxDirectMemorySize=10M 设置堆内存中新生代大小



本文出自 “AUB” 博客,请务必保留此出处http://aubdiy.blog.51cto.com/2978849/1207518


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章