走进java_内存分配

来源:互联网 发布:windows原版壁纸 编辑:程序博客网 时间:2024/05/16 15:37

        Java虚拟机在执行的过程中会把它所管理的内存划分成为若干个不同的数据区域。分别是:虚拟机栈、本地方法栈、程序计数器、方法区、堆。这些内存区域都有各自的用途,以及创建和销毁的时间。学习的时候虽然没必要知道java的对象,变量放在那里,java虚拟机会帮助好我们处理好这些东西,但是了解了这些东西对我们自己会有很大的提升,下面我们来看看jvm是怎么划分这些内存区域的:

 

 

 

   程序计数器(program counter register:

它是一块较小的内存,但执行速度是最快的区域,它的作用可以看成当前线程所执行的字节码的行号指示器,(pc在每次指令执行后都会自增,来准备下一个将要执行指令的地址)在虚拟机的概念模型里,字节码解释器就是通过改变程序计数器的值来选择下一条需要执行的字节码指令,循环,跳转,异常处理等一些基本的功能都需要依靠程序计数器来完成。

  如果线程正在执行的是java方法,则这个计数器记录的是正在执行的虚拟机字节码指令的地址。 如果正在执行的是Native方法,这个计数器值则为空(Undefined)此内存区域是唯一一个在java虚拟机规范中没有规定任何OutOfMemoryErroe情况的区域。

 线程中,为了让线程切换后能恢复到正确的执行位置每个线程都需要一个独立的程序计数器,各条线程之间相互不影响,独立存储,因此这块内存是线程私有的。

 

   Java虚拟机栈(java Virtual Machine Stacks

   它也是线程私有的,它的生命周期也与线程相同。它描述的是java方法执行的内存模型:每一个方法被执行的时候都会创建一个栈帧(stack frame)用来存储局部变量表,操作栈,动态链接,方法出口等信息。每一个方法在执行完的过程,就对应着一个栈帧在虚拟机栈中对应出栈的过程。对于执行引擎来讲活动线程中只有栈顶是有效的,成为当前栈帧,这个栈帧索关联的方法称为当前方法,执行引擎所运行的所有字节码指令都只对当前栈帧进行操作。一个栈帧在需要分配多少内存,不会受到程序运行期变量的影响,而是在编译程序的时候,栈帧中需要多大的局部变量表,多深的操作数都已近完全确定了(也就是说java程序被编译成Class文件时,就确定了所需要分配的最大局部变量表的容量。在运行期间局部变量的大小是不会改变的)。

   局部变量表存放了编译期可知的所有基本数据类型(int ,float ,char ,short , long ,boolean ,byte, double,还有对象引用, 它不是对象本身,它有可能是指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄。局部变量表的容量是以变量槽(Slot

为最小单位。在虚拟机规范当中没有明确指明一个slot占用内存空间的大小(允许虚拟机或者操作系统的不同而发生变化)像int ,float和对象的引用类型这些32位以内的数据类型,用一个slot存放。对于64位的数据类型(doublelong)虚拟机会为其分配两个连续的slot空间。

另外在java虚拟机规范中,对于这个内存区域规定了两种异常情况:

1:如果线程请求的栈的深度大于虚拟机所允许的深度,将会抛出Stackoverflowerror异常。

2:如果虚拟机可以动态扩展,当扩展时无法申请到足够的内存是会抛出outofmemoryerror异常。

 

   

本地方法栈(Native Method Stacks

   该区域与虚拟机所发挥的作用非常相似,区别是java虚拟机为虚拟机执行java方法服务(字节码),但是本地方法栈则是为使用到的Native方法服务。在虚拟机规范当中没有对本地方法做出出明确的规定。

 

Java堆(Java Heap

    对于java堆来说,java堆是java虚拟机所管理的内存中最大的一块,但同样也是执行效率很低的一块空区域。Java堆是被线程共享的一块内存区域,在此区域中几乎存放所有对象的实例和数组,java堆也是垃圾收集器管理的主要区域,所以也被称为“GC堆”。

在细一点java 堆还可以分成新生代和老年代再细一点新生代还可以分为三个区域:Eden, From Survivor, To Surviethvor

java虚拟机规范里面规定,java堆可以是物理上不连续的内存空间,只要逻辑上连续只要逻辑上连续就行。如果堆中没有内存完成实例分配,并且堆无法再扩展时,将会抛出OutOfMemoryError异常。

堆的优点在于编译器不必要知道要从堆中分配多少内存空间,也不必要知道存储的数据要在堆当中停留多长时间,因此堆在保存数据时会得到很大的灵活性,缺点在于在运行的时候,由于从操作系统管理内存分配,所以在分配和销毁时都要占用时间,因此堆的效率非常低。

 

方法区(Method Area

   方法区和堆一样也是各个线程共享的一块内存区域,它用于存储已经被虚拟机加载的类信息,常量,静态变量,还有编译后的代码的数据。Java虚拟机规范把方法区描述为堆的一个逻辑部分,但它也被称为非堆,实际是为了区分java堆。

java虚拟机规范当中规定,当方法区无法满足内存分配要求时,也会抛出OutOfMemoryError异常。

 

运行时常量池(Runtime Constant Pool

   运行时常量池是方法区的一部分。Class文件中出除已有类的字段,方法,接口等信息外,还有一项就是常量池(Class文件常量池),用于存放编译器生成的各种字面量和符号引用,这些内容在类加载后会存放到方法区的运行常量池中,java并不要求常量一定只能在编译的时候产生,运行的期间也可以将新的常量放入常量池。这种特性要依赖于String类的intern()方法。

 

对象实例的分析:

   下面我们来看这段代码  Student stu = new Student();  这段代码会涉及到java栈,java堆,和方法区,三个内存区域。 分析完这段代码,我们应该知道,引用(stu)会在栈当中保存,(相当于C语言当中的指针,里面存放指向对象的地址)。而java堆当中存放的是该引用的实例化对象。而方法区则保存的是对象类型,父类,实现的接口,方法等。

由于引用类型在java虚拟机里面只规定了一个指向对象的引用,并没有规定这个引用应该通过哪种方式去定位,以及访问到堆当中的具体位置,因此不同的虚拟机实现对象的访问方式会有些不同,主要的访问方式有两种:

 

1: 使用句柄访问方式;

 

 

 

   使用这种方式后,java堆会划分出一块内存来作为句柄池,引用中储存的是就是对象的句柄地址,而句柄中包含了对象实例数据和类型数据各自的具体地址信息,它的最大好处就是引用中存放的是句柄地址,在对象印度是只会改变句柄中实例数据指针,而引用本身不会修改。

 

2:使用直接指针方式:

 

 

 

  这种方式引用里面直接装的就是对象的地址,它的好处就是速度快,省内存,目前大多数采用第二种方式对对象进行访问。



0 0
原创粉丝点击