深入理解jvm-java内存区域、内存溢出异常、对象访问

运行时数据区域

在运行java程序时把它所管理的内存分为若干的数据区域,包括方法区、虚拟机栈、本地方法栈、堆、程序计数器。
在这里插入图片描述

程序计数器
  • 相当于当前线程所执行的字节码的行号指示器,通过改变计数器的值来执行对应的字节码指令。
  • 如果正在执行一个java方法,计数器记录的是虚拟机字节码指令的地址,如果是native方法,计数器为空。
  • 在多线程中,任何一个确定的时刻,一个处理器只执行一条线程中的指令,因此为了线程切换后能恢复到正确的位置,每个线程都有一个独立的计数器。
  • 该区域是唯一一个在java虚拟机规范中没有规定任何OutOfMermoryError情况的区域。
java虚拟机栈
  • 生命周期和线程相同,也属于线程私有。
  • 每个方法执行过程中同事创建一个栈帧用于存储局部变量表、操作栈、动态链接、方法出口等信息。每个方法被调用到执行完成,就对应了一个栈帧在虚拟机栈从入栈到出栈的过程。
  • 64位长度的long和double类型的数据在局部变量表中占2个局部变量空间,其余的数据类型占1个。局部变量表所需的内存空间在编译期间完成分配,进入方法时,这个方法需要在帧中分配多大的局部变量空间是确定的,运行期间不可更改大小。
  • java虚拟机规范中,规定了两种异常:线程请求的栈深度大于虚拟机栈所允许的深度,抛出StackOverflowError异常;如果虚拟机栈深度可以动态扩展,扩展无法申请到足够的内存时抛出OutOfMemoryError异常。
本地方法栈

与虚拟机栈相同,针对的是native方法服务。

  • 线程共享,在虚拟机启动时创建,此区域的唯一目的就是存储对象实例。也称GC堆。java堆可以是物理上不连续的内存空间。一般分为新生代、老年代和永久代。
    新生代:主要是用来存放新生的对象。
    老年代:主要存放应用程序中生命周期长的内存对象。
    永久代:内存的永久保存区域,主要存放Class和Meta(元数据)的信息。(在Java8中,永久代已经被移除,被一个称为“元数据区”(元空间)的区域所取代。)
    如果在细致分的话,有Eden空间,From Survivor空间,To Survivor空间等。
  • 如果在堆中没有内存供实例分配或者堆无法扩展时抛出OutOfMemoryError异常。
方法区
  • 线程共享。用于存储被虚拟机加载的类信息、常量、静态变量、编译后的代码等数据。相对而言,这个区域的垃圾收集行为是比较少出现的,这个区域的内存回收目标主要针对常量池的回收和类型的卸载。
  • 方法区无法满足内存分配需求时,抛出OutOfMemoryError异常。
运行时常量池
  • 属于方法区的一部分。Class文件除了有类的版本、字段、方法等描述信息外,还有一项信息是常量池,用于存放编译期间生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。
  • 运行时常量池具备动态性,运行期间也可能将新的常量放入池中,比如String的intern()方法(如果常量池中存在该字符串,直接返回该字符串,如果不存在,会将该字符串放入池中,在返回)
  • 当常量池无法申请到内存时,抛出OutOfMemoryError异常。
直接内存
  • 不属于虚拟机运行时数据区的一部分。
  • JDK1.4新加入NIO类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,他可以使用Native函数库直接分配堆外内存,然后通过一个存储在java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。在一些场景中显著的提高了性能,因为避免了java堆和native堆来回复制数据。
  • 配置虚拟机参数时忽略掉直接内存的话,使得各个内存区域总和大于物理内存限制,导致动态扩展时出现OutOfMemoryError异常。

对象的访问

一般来说引用类型的访问方式有两种:使用句柄和直接指针

  • 句柄访问方式,堆中会划出一块句柄池,reference中存储的就是对象的句柄地址,而句柄中包含对象的实例数据和类型数据:
    在这里插入图片描述
  • 直接指针的话,堆中存储的就是对象类型(包括对象的类型、父类、实现接口、方法等信息)的指针和对象的实例信息
    在这里插入图片描述
    参考《深入理解java虚拟机》

猜你喜欢

转载自blog.csdn.net/administratorJWT/article/details/87706641