Java·运行时数据区(Run-Time Data Areas)

可能是把Java内存区域讲的最清楚的一篇文章

运行时数据区(Run-Time Data Areas)

  • (线程私有)程序计数器
  • (线程私有)虚拟机栈
  • (线程私有)本地方法栈
  • (线程共享)堆
  • (线程共享)方法区

线程也共享直接内存,但不属于运行时数据区

1. 程序计数器(The pc Register)

字节码解释器工作时通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等功能都需要依赖这个计数器来完成。

2. 虚拟机栈(Java Virtual Machine Stacks)

Java虚拟机栈由一个个栈帧组成,每个栈帧中都拥有:局部变量表、操作数栈、动态链接、方法出口信息。

局部变量表主要存放了编译器可知的各种数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,它不同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)。

Java 虚拟机栈会出现两种异常:StackOverFlowError 和 OutOfMemoryError。

  • StackOverFlowError: 若Java虚拟机栈的内存大小不允许动态扩展,那么当线程请求栈的深度超过当前Java虚拟机栈的最大深度的时候,就抛出StackOverFlowError异常。
  • OutOfMemoryError: 若 Java 虚拟机栈的内存大小允许动态扩展,且当线程请求栈时内存用完了,无法再动态扩展了,此时抛出OutOfMemoryError异常。

3. 本地方法栈(Native Method Stacks)

和虚拟机栈所发挥的作用非常相似,区别是:虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务
在 HotSpot 虚拟机中,本地方法栈和 Java 虚拟机栈合二为一。

本地方法被执行的时候,在本地方法栈也会创建一个栈帧,用于存放该本地方法的局部变量表、操作数栈、动态链接、出口信息。

方法执行完毕后相应的栈帧也会出栈并释放内存空间,也会出现 StackOverFlowError 和 OutOfMemoryError 两种异常。

本地方法

在java源程序中,被native关键字修饰的方法叫做本地方法,不提供函数体。

public class HelloWorld{
    public native void h();//该方法和abstract修饰的方法一样,只有签名。
    static{
        System.loadLibrary("hello");//不写文件的后缀,程序会自动加上.dll的。
    }
    public static void main(String[] args){
        new HelloWorld().h();//调用
    }
}
  • 本地方法的实现,是用C/C++语言在另外的文件中编写的,编写的规则遵循Java本地接口的规范(简称JNI)。
  • native方法主要用于加载文件和动态链接库,由于Java语言无法访问操作系统底层信息(比如:底层硬件设备等),这时候就需要借助C语言来完成了。被native修饰的方法可以被C语言重写。
  • 一般本地方法都是很底层,跟平台结合紧密,或者用java实现性能很差。因此使用了native的程序可移植性都不太高。

知乎 · java的native方法,谁能简明地讲下?
详解native方法的使用
Java的native方法

4. 堆(Heap)

Java 虚拟机所管理的内存中最大的一块。堆的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。

Java 堆是垃圾收集器管理的主要区域,因此也被称作GC堆(Garbage Collected Heap)。从垃圾回收的角度,由于现在收集器基本都采用分代垃圾收集算法,所以Java堆还可以细分为:新生代和老年代。再细致一点有:Eden空间、From Survivor、To Survivor空间等。进一步划分的目的是更好地回收内存,或者更快地分配内存。
在 JDK 1.8中移除整个永久代,取而代之的是一个叫元空间(Metaspace)的区域(永久代使用的是JVM的堆内存空间,而元空间使用的是物理内存,直接受到本机的物理内存限制)。

Java8内存模型—永久代(PermGen)和元空间(Metaspace)

5. 方法区(Method Area)

存储per-class structures,例如:运行时常量池,已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。方法区在逻辑上来说是堆的一部分。

HotSpot 虚拟机中方法区也常被称为 “永久代”,本质上两者并不等价。仅仅是因为 HotSpot 虚拟机设计团队用永久代来实现方法区而已,这样 HotSpot 虚拟机的垃圾收集器就可以像管理 Java 堆一样管理这部分内存了。但是这并不是一个好主意,因为这样更容易遇到内存溢出问题。

相对而言,垃圾收集行为在这个区域是比较少出现的,但并非数据进入方法区后就“永久存在”了。

6. 运行时常量池(Run-Time Constant Pool)

  • JVM为每一个类/接口都维护一个各自的运行时常量池。
  • 每一个运行时常量池都从方法区分配。
  • Class文件中有一个constant_pool table,类似一个符号表(包含符号引用和静态常量),运行时常量池就是这个表的run-time版本。在JVM中创建相应类/接口时,依据constant_pool table,构建出运行时常量池。
  • Class 文件中除了有类的版本、字段、方法、接口等描述信息外,还有常量池信息(用于存放编译期生成的各种字面量和符号引用)。

既然运行时常量池是方法区的一部分,自然受到方法区内存的限制,当常量池无法再申请到内存时会抛出 OutOfMemoryError 异常。

7. 直接内存

直接内存并不是虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域,但是这部分内存也被频繁地使用。而且也可能导致OutOfMemoryError异常出现。

JDK1.4中新加入的 NIO(New Input/Output) 类,引入了一种基于通道(Channel) 与缓存区(Buffer) 的 I/O 方式,它可以直接使用Native函数库直接分配堆外内存,然后通过一个存储在 Java 堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样就能在一些场景中显著提高性能,因为避免了在 Java 堆和 Native 堆之间来回复制数据。

本机直接内存的分配不会收到 Java 堆的限制,但是,既然是内存就会受到本机总内存大小以及处理器寻址空间的限制。

你可能感兴趣的:(Java·运行时数据区(Run-Time Data Areas))