运行时数据区域
jvm在执行Java程序时会把他所管理的区域划分为若干个不同的数据区域,这些区域各有各自的用途。
如图:
1.程序计数器
程序计数器是一块较小的内存空间,可以看做是当前线程所执行的字节码的行号指示器。每条线程都有一个独立的程序计数器,各线程之间的计数器互不影响。如果线程在执行一个java方法,则计数器记录的是正在执行的虚拟机字节码指令的地址,在执行native方法时,计数器的值为空。并且在java虚拟机规范中没有在此区域规定任何的outOfMemoryError异常。
2.java虚拟机栈
java虚拟机栈也是线程私有的,且生命周期和线程相同,其描述的是java方法执行的内存模型。在方法执行时,创建一个栈帧,用于存储局部变量表,操作数栈,动态链接,方法出口等信息。此内存区域就是我们常说的栈内存。其中,局部变量表存放了编译器可知的各种基本数据类型。其中long和double占用两个局部变量空间,其他的占用一个。在进入方法时,此局部变量空间就被唯一确定。
java虚拟机规范规定了两种异常状况:线程请求的栈深度大于虚拟机所允许的深度时,抛出StackOverflowError异常。如果扩展时,无法申请到足够的内存,抛出OutOfMemoryError异常。当然我们也可以设置系统允许分配的栈深度。
3.本地方法栈
本地方法栈与java虚拟机栈类似,不过java虚拟机栈为虚拟机栈执行java方法服务,而本地方法栈则为虚拟机执行Native方法服务。Sun HotSpot虚拟机把本地方法栈和虚拟机栈合二为一。
4.java堆
java堆是java虚拟机管理的内存在中最大的一块。是线程共享的。java堆也是垃圾回收的主要区域,因此很多时候也会被称为“GC堆”,并且现在的收集器都是采用的分代收集算法(GC算法介绍请看:http://www.cnblogs.com/Booker808-java/p/9063677.html),所以java堆还可以细分为新生代和老年代,新生代再细致点还可以分为Eden区、From Survivor区、To Survivor区,并且为了防止在线程共享下产生的分配区域的安全问题,java堆也会划分出多个线程私有的分配缓冲区(TLAB),其实吧,无论怎么划分,堆都是用来存储对象实例的,,之所以划分这么多区域,主要就是为了更高效的回收内存,更高效的分配内存。
5.方法区
线程共享的区域,用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等。方法区也是属于堆的一部分,但是为了区分开来,也可以叫做非堆这个区域的回收主要针对常量池的回收和对类型的卸载。当方法区无法满足内存分配需求时,会抛出OutOfMemoryError异常。
6.运行时常量池
运行时常量池也是方法区的一部分,class文件除了含有类的版本、字段、方法、接口等描述信息外(这些信息都是通过十六进制的符号引用来代替的),还有一项就是常量池,专门用来存放编译期间生成的各种字面量(这里通常指final static声明的字段)和符号引用,这些符号引用一般都是描述什么字段类型信息啊、方法类型信息等,运行时常量对class文件常量池另一个重要的特征就是具备动态性,常量可以在运行时被放入常量池。
对象的创建
1.对象的创建
java是个面向对象语言,语言层面上,是通过new关键字来进行创建对象的,但是在虚拟机中,创建对象又是个怎么样的过程呢??当new指令出来时,会去常量池中检查是否能定位到该类的符号引用,当检查通过之后,接下来就是为新生对象进行内存分配,等同于在堆中划分出一块区域。这时,问题来了,如果java堆中的内存是绝对完整的,所有的用过的内存都放在一边,没用过的放在另一边,中间放着一个指针作为分界点的指示器,那么分配内存的动作其实就是把那个指针指向空闲空间那边挪动一小块,这一小块就是属于我们对象的,这种分配方式属于“指针碰撞”。
如果java堆内存是不完整的,是杂乱不堪的,这个时候指针碰撞根本不可能正常工作,,那么虚拟机就要维护一个列表,记录在堆上有哪些区域是可以被使用的,然后在分配内存时,在列表上找到一块足够大的空间分配给对象,同时更新列表上的记录。这种分配方式可以称为“空闲列表”,选取哪种方式完全取决于我们的堆的内存是否是规整的,而java堆是否规整又取决于垃圾收集器是否带有自动压缩整理功能。
2.并发下的内存分配
在划分可用空间之外,还有一个很重要的因素就是,在多线程环境下(因为堆是线程共享的,所以这种情况是存在的),指针碰撞方式是存在线程安全问题的。当指针还在为线程A分配时(还没分配完),这时线程B又使用了原来的指针来分配内存。两种解决方式:
(1)采用同步的方式;
(2)把内存划分的动作按照不同的线程划分在不同的区域进行,就是说每个线程在堆上会预先分配好一小块私有的内存区域,称之为本地线程缓冲分配(TLAB)
哪个线程要分配内存,就在哪个线程的TLAB上划分,如果TLAB区域划分完了,就再进行同步,这样也可以内存划分时提高效率。