1.1类加载完以后JVM干了什么?
在类加载检查通过后,接下来虚拟机将为新生对象分配内存。
1.1.1JVM的内存模型
首先我们来了解一下JVM的内存模型的怎么样的:
- 基于jdk1.8画的JVM的内存模型
再来看看每个区域究竟存储的是什么(干的是什么):
- 堆:存放对象实例,几乎所有的对象实例都在这里分配内存,同时包含一个常量池(final),是由1.7以前版本的方法区转移过来的。所以1.8之后的string常量都是存在堆里面。
- 虚拟机栈:虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息,帧数超过限制(-Xss),就会出现StackOverFlow(=SOF)错误。另外超过线程分配的内存大小,也会报OOM错误。
- 本地方法栈:本地方法栈则是为虚拟机使用到的Native方法服务。
- 方法区:所有线程共享。存放class加载相关信息。(元空间)
- 程序计数器:当前线程所执行的字节码的行号指示器
回到开始的代码看过程:
public class TestMain {
public static void main(String[] args){
JavaTestBean javaTestBean = new JavaTestBean();
javaTestBean.setName("linhua");
System.out.println(javaTestBean);
}
- 1、通过
java.exe
运行JavaMain.class
,随后被加载到JVM中,元空间存储着类的信息(包括类的名称、方法信息、字段信息..)。 - 2、然后JVM找到JavaTest的主函数入口(main),为main函数创建栈帧,开始执行main函数
- 3、main函数的第一条命令是
JavaTestBean javaTestBean = new JavaTestBean();
就是让JVM创建一个JavaTestBean
对象,但是这时候方法区中没有JavaTestBean
类的信息,所以JVM马上加载JavaTestBean
类,把JavaTestBean
类的类型信息放到方法区中(元空间) - 4、加载完
JavaTestBean
类之后,Java虚拟机做的第一件事情就是在堆区中为一个新的JavaTestBean
实例分配内存, 然后调用构造函数初始化JavaTestBean
实例,这个JavaTestBean
实例持有着指向方法区的JavaTestBean
类的类型信息(其中包含有方法表,java动态绑定的底层实现)的引用 - 5、当使用j
avaTestBean.setName("Java3y");
的时候,JVM根据引用找到JavaTestBean
对象,然后根据JavaTestBean
对象持有的引用定位到方法区中JavaTestBean
类的类型信息的方法表,获得setName()
函数的字节码的地址 - 6、为
setName()
函数创建栈帧,开始运行setName()
函数
1.1.2 内存可能溢出的模块
- 虚拟机栈溢出:
public class TestMain {
private static int index = 1;
public void call(){
index++;
call();
}
public static void main(String[] args) {
TestMain mock = new TestMain();
try {
mock.call();
} catch (Throwable e) {
System.out.println("Stack deep : " + index);
e.printStackTrace();
}
}
}
运行两次结果:可以看出栈的深度是不一样的
2、堆内存是 JVM 所有线程共享的部分,在虚拟机启动的时候就已经创建。所有的对象和数组都在堆上进行分配。这部分空间可通过 GC 进行回收。当申请不到空间时会抛出 OutOfMemoryError。下面我们简单的模拟一个堆内存溢出的情况:
public class HeapOomMock {
public static void main(String[] args) {
List<byte[]> list = new ArrayList<byte[]>();
int i = 0;
boolean flag = true;
while (flag){
try {
i++;
list.add(new byte[1024 * 1024]);//每次增加一个1M大小的数组对象
}catch (Throwable e){
e.printStackTrace();
flag = false;
System.out.println("count="+i);//记录运行的次数
}
}
}
}
1.2、PermGen(永久代)
绝大部分 Java 程序员应该都见过 "java.lang.OutOfMemoryError: PermGen space "这个异常。这里的 “PermGen space”其实指的就是方法区。不过方法区和“PermGen space”又有着本质的区别。前者是 JVM 的规范,而后者则是 JVM 规范的一种实现,并且只有 HotSpot 才有 “PermGen space”,而对于其他类型的虚拟机,如 JRockit(Oracle)、J9(IBM) 并没有“PermGen space”。由于方法区主要存储类的相关信息,所以对于动态生成类的情况比较容易出现永久代的内存溢出。最典型的场景就是,在 jsp 页面比较多的情况,容易出现永久代内存溢出。我们现在通过动态生成类来模拟 “PermGen space”的内存溢出
1.3、Metaspace(元空间)
其实,移除永久代的工作从JDK1.7就开始了。JDK1.7中,存储在永久代的部分数据就已经转移到了Java Heap或者是 Native Heap。但永久代仍存在于JDK1.7中,并没完全移除,譬如符号引用(Symbols)转移到了native heap;字面量(interned strings)转移到了java heap;类的静态变量(class statics)转移到了java heap。
不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制,但可以通过以下参数来指定元空间的大小:
参考链接:http://www.cnblogs.com/paddix/p/5309550.html
1.4 碰到内存分配免不了要处理类似这些问题了:
String aa = new String("aa");
aa.intern();
String bb = "bb";
这个问题可以参考这个链接:https://blog.csdn.net/u013366617/article/details/83618361