Java底层知识之JVM详细研究一(Java内存区域)
Object obj = new Object();
主要参考资料:《深入理解Java虚拟机》,未经本人及原书作者同意禁止转载。
JVM中的数据区域
JVM中给数据分了这么几个区域:
其中:
1.程序计数器:
程序计数器是一块较小的内存空间,为当前线程所执行的字节码的行号指示器。也就是说,程序再翻译成为字节码了之后,分支、循环、跳转、异常处理、线程恢复等功能都需要程序计数器来完成。
(注意!)此区域存储的东西是执行的虚拟机的字节码指令的地址,如果为natvie方法(natvie:一个调用非Java语言的代码时的关键字),则存储为空。
(特别的)此区域是JVM中唯一一个没有规定任何OutOfMemoryError(内存溢出错误)的区域
2.Java虚拟机栈
此区域描述的是方法执行的内存模型,每个方法在执行的时候都会创建一个栈帧,用于存储局部变量表、操作栈、动态链接方法出口等信息。
每一个方法调用的过程就是一个栈帧在虚拟机栈中入栈和出栈的过程。
局部变量表:存放编译器可知的各种基本数据类型(八种:Boolean\byte\char\short\int\long\float\double)、对象引用类型和returnAddress类型(指明一条字节码指令的地址)。
这个区域中有两种异常情况: StackOverflowError异常(栈深度大于虚拟机所允许的深度时触发)
OutOfMemoryError异常(这当然,除了程序计数器区都会有)
3.本地方法栈
注意名字:native method stack,跟Java中的关键字native是一样的,意思就是这个区跟虚拟机栈差不多,不过它是为本地方法(也就是native修饰的部分)工作的。
异常抛出和虚拟机站一样。
4.Java堆
虽然图中看起来很小,但是它却是JVM管理的内存中最大的一块,被所有线程所共享;
这个区唯一的作用是存放对象实例,几乎所有的对象实例都在堆上分配(可以类比基本数据类型,他们就是在虚拟机栈中分配的)。Java虚拟机规范中规定:所有的对象实例以及数组坐标都要在堆上分配。理解这一部分对我们在程序中的内存分配有一个大体的印象。
Java堆还是垃圾收集器管理的主要区域,一次有一个别名叫“GC堆”,这一部分内容会在以后的文章中讨论。
此外,Java堆是可以再物理上不连续的,当此区域内存不够时,会抛出OutOfMemoryError异常。
5.方法区
作用是存储已经被虚拟机加载的类信息、常量、静态变量等数据。
6.运行时常量池
根据名字理解,就是存放常量的地方,位于方法区。一般在编译时候就会确定程序的常量
这个部分对于理解String很有帮助:
一般情况下,我们使用一个双引号“”就可以创建一个常量并存储在常量池中,因此:
String s1 = "Java"; String s2 = "Java"; //s1 == s2为TRUE
当是创建一个String对象的时候,这个对象并不位于堆中,而是位于常量池中;s2继续同样的语句的时候,其回先在常量池中寻找一样Unicode编码的字符串,如果有就不再进行创建。以上代码共创建了1个对象。
此区域同样会出现OutOfMemoryError异常。
但是,如果使用new的方法创建了一个字符串,也就是在堆中创建了两个相同内容的字符串对象的时候,就会输出不一样的结果:
String s1 = new String("Java"); String s2 = new String("Java"); //s1 == s2为FALSE
因为使用new操作符进行对象创建的时候,因为括号中的内容(即构造方法的参数完全可能不确定),因此以上代码创建出来了两个内容相同,但是引用地址却不同的对象。
对象访问:
一个简单的Java语句:
Object obj = new Object();
它牵扯到了JVM中的多少地方呢?
首先程序计数器的使用肯定是少不了的,如果没有程序计数器,这个语句在哪里JVM都不会知道;其次,Object obj这部分将会反应刀虚拟机栈中的本地变量表里面,作为一个reference的类型数据出现;而new Object() 这部分则是在堆中进行了内存的分配;在对象实例中,又会包含许多对象类型数据的地址信息,这些存储在方法区中,其他线程可以访问。
在这里,我们讨论的对象访问在JVM中有两种实现方式,一种是通过句柄访问对象,如图:
一种是通过直接指针访问对象。在我们使用的虚拟机(Sun HotSpot而言),是通过直接地址访问的:
(内存区域介绍完)
Github:
https://github.com/RainFool