读《深入理解java虚拟机》(一)java内存区域

java内存区域

一、运行时数据区域

Java虚拟机内存分为:

方法区、堆区、虚拟机栈区、本地方法栈、程序计数器

其中方法区堆区所有线程共享虚拟机栈、本地方法栈、程序计数器线程私有

1.程序计数器:

程序计数器是一块较小的内存空间,它的作用可以看作是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。- - 摘自《深入理解Java虚拟机》

  1. 如果线程正在执行的是Java 方法,则这个计数器记录的是正在执行的虚拟机字节码指令地址
  2. 如果正在执行的是Native 方法,则这个技术器值为空(Undefined)
  3. 此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域
  • Java 虚拟机的多线程是通过切换线程并分配处理器执行时间的方式来实现的,在任何一个确定的时间,一个处理器(对于多核处理器来说是一个内核)都只会执行一条线程中的指令

例子:当A 线程先向处理器发出指令,但当执行到中途一半时,B线程过来执行,且优先级高,此时处理器将A 挂起,B 执行,当B 执行结束需要唤醒A 同时得知道A 的执行位置,就可以查看线程A 中的计数器指令。

(不会抛出OutOfMemoryError)

2.java虚拟机栈:

线程私有,生命周期与线程相同,描述的是Java方法执行的内存模型:每一个方法执行的同时都会创建一个栈帧,由于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法的执行就对应着栈帧在虚拟机栈中的入栈,出栈过程

  1. 局部变量表:存放了编译时可以知的基本数据类型(byte,char,int等等)和引用类型
  2. 操作数栈:栈帧刚建立使,操作数栈为空,执行方法操作时,操作数栈用于存放JVM从局部变量表复制的常量或者变量,提供提取,及结果入栈,也用于存放调用方法需要的参数及接受方法返回的结果
  3. 动态连接:在Class文件中的常量持中存有大量的符号引用。字节码中的方法调用指令就以常量池中指向方法的符号引用作为参数。这些符号引用一部分在类的加载阶段(解析)或第一次使用的时候就转化为了直接引用(指向数据所存地址的指针或句柄等),这种转化称为静态链接。而相反的,另一部分在运行期间转化为直接引用,就称为动态链接
  4. 方法返回地址:当一个方法被执行后,有两种方式退出该方法:执行引擎遇到了任意一个方法返回的字节码指令或遇到了异常,并且该异常没有在方法体内得到处理。无论采用何种退出方式,在方法退出之后,都需要返回到方法被调用的位置,程序才能继续执行。方法返回时可能需要在栈帧中保存一些信息,用来帮助恢复它的上层方法的执行状态

(会抛出OutOfMemoryError或者StackOverflowError)

3.本地方法栈:

本地方法栈主要为本地native方法服务,native方法就是指不是java语言实现的方法,可能是C语言或者其他语言。

(会抛出OutOfMemoryError或者StackOverflowError)

4.JAVA堆

堆内存所有线程共享的一块内存区域,在虚拟机启动时创建。次内存区域唯一的目的就是存放实例,当我们创建了一个对象(也就是 new一个对象),在堆内存就会为对象分配一个空间,Java垃圾回收器(GC)运行在堆内存里,一旦发现没有任何地方引用到这个对象,就释放这个对象所占的内存。 

Java堆是垃圾收集器管理的主要区域。由于现在的收集器基本上采用的都是分代收集算法,所有Java堆可以细分为:新生代和老年代。在细致分就是把新生代分为:Eden空间、From Survivor空间、To Survivor空间。当堆无法再扩展时,会抛出OutOfMemoryError异常。
(会抛出OutOfMemoryError)

5.方法区

 线程共享,存储已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等等。

6、运行时常量池
      运行时常量池(Runtime Constant Pool)是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用。这部分内容将在类加载后存放到方法区的运行时常量池中。
      运行时常量池相对于Class文件常量池的另外一个重要特征是具备动态性,不仅Class文件中的常量池的内容可以进入方法去运行时常量池,运行期间也可能将新的常量放入池中。

7、直接内存
      直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范汇总定义的内存区域,例如, JDK1.4引入的NIO类,引入了基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。
      注意:直接内存虽然不包含在堆内存中,但是它也是一块需要的内存区域,因此,在计算各个内存区域总和的时候要计算在内。

对象

对于Object obj = new Object();

       "Object obj"这部分的语义将会反映到Java栈的本地变量表中,作为一个reference类型数据出现。

       “new Object()”这部分语义将会反映到Java堆中,形成一块存储了Object类型所有实例数据值的结构化内存。

        由于reference类型在Java虚拟机规范里面只规定了一个指向对象的引用,因此不同的虚拟机实现会有不同的实现对象访问的方式,主流的访问方式有两种:使用句柄 和直接指针。

         使用句柄

        使用该方式,Java堆中将会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中存对象实例数据和类型数据的具体地址信息。如下图:

如果把对象比作人,这里句柄就好像人名,这个人名下计入了这个人具体的地址

       使用直接指针

       reference中直接存储的就是对象地址。如下图:

     各自的优点

     ** 句柄访问方式最大的好处是reference中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而reference本身不需要被修改。

     ** 直接指针访问方式的最大好处就是速度更快,它节省了一次指针定位的时间开销。Sun HotSpot虚拟机是使用第二种方式的

 

 

 

猜你喜欢

转载自blog.csdn.net/baiyan3212/article/details/81330402