JVM(一)——运行时数据区

436 阅读5分钟

今天我们来聊一聊JVM内存结构,首先我们先区别JVM和JMM:
Java Virtual Machine(JVM)Java虚拟机:包含在jre中,内部体系结构分为三部分,分别是:类装载器(ClassLoader)子系统,运行时数据区,和执行引擎。今天图中表示的是运行时数据区 (运行时数据区和JMM并没有本质上的联系)。
Java Memory Model(JMM)java内存模型:主要定义了程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和内存中取出变量这样的细节。

程序计数器

是一块较小的内存空间,它可以看做是当前线程所执行的字节码的行号指示,记录正在执行的虚拟机字节码指令的地址(如果正在执行的是本地方法【其它语言编写的,像Native】则为空)。

Java 虚拟机栈

每个 Java 方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、返回地址等信息。从方法调用直至执行完成的过程,对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程。

可以通过 -Xss 这个虚拟机参数来指定每个线程的 Java 虚拟机栈内存大小,在 JDK 1.4 中默认为 256K,而在 JDK 1.5+ 默认为 1M:

java -Xss2M HackTheJava

该区域可能抛出异常

  • 如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常
  • 如果虚拟机栈可以动态扩展,如果扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常

本地方法栈

本地方法栈与 Java 虚拟机栈类似,它们之间的区别只不过是本地方法栈为本地方法服务。
本地方法一般是用其它语言(C、C++ 或汇编语言等)编写的,并且被编译为基于本机硬件和操作系统的程序,对待这些方法需要特别处理。

所有对象都在这里分配内存,是垃圾收集的主要区域("GC 堆")。
现代的垃圾收集器基本都是采用分代收集算法,其主要的思想是针对不同类型的对象采取不同的垃圾回收算法。可以将堆分成两块:

  • 新生代(Young Generation)
  • 老年代(Old Generation)

堆不需要连续内存,并且可以动态增加其内存,增加失败会抛出 OutOfMemoryError 异常。

可以通过 -Xms 和 -Xmx 这两个虚拟机参数来指定一个程序的堆内存大小,第一个参数设置初始值,第二个参数设置最大值。

java -Xms1M -Xmx2M HackTheJava

方法区(元空间)

用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

和堆一样不需要连续的内存,并且可以动态扩展,动态扩展失败一样会抛出 OutOfMemoryError 异常。

对这块区域进行垃圾回收的主要目标是对常量池的回收和对类的卸载,但是一般比较难实现。

HotSpot 虚拟机把它当成永久代来进行垃圾回收。但很难确定永久代的大小,因为它受到很多因素影响,并且每次 Full GC 之后永久代的大小都会改变,所以经常会抛出 OutOfMemoryError 异常。为了更容易管理方法区,从 JDK 1.8 开始,移除永久代,并把方法区移至元空间,它位于本地内存中,而不是虚拟机内存中。

方法区是一个 JVM 规范,永久代与元空间都是其一种实现方式。在 JDK 1.8 之后,原来永久代的数据被分到了堆和元空间中。元空间存储类的元信息,静态变量和常量池等放入堆中。

运行时常量池

运行时常量池是方法区的一部分。
用于存放Class文件中的常量池(编译器生成的字面量和符号引用),其会在类加载后被放入这个区域。 除了在编译期生成的常量,还允许运行期间也可以将新的常量放入池中,例如 String 类的 intern()。

直接内存

直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域。但是这部分内存也被频繁的使用。 而且也可能导致OutOfMemoryError异常。
在 JDK 1.4 中新引入了 NIO 类,它可以使用 Native 函数库直接分配堆外内存,然后通过 Java 堆里的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在堆内存和堆外内存来回拷贝数据。

采用直接内存的优点:
1:对于频繁的io操作,我们需要不断把内存中的对象复制到直接内存。然后由操作系统直接写入磁盘或者读出磁盘。这时候用到直接内存就减少了堆的内外内存来回复制的操作。
2:我们在运行程序的过程中可能需要新建大量对象,对于一些声明周期比较短的对象,可以采用对象池的方式。但是对于一些生命周期较长的对象来说,不需要频繁调用GC,为了节省GC的开销,直接内存是必备之选。(JVM来自动管理内存回收,JVM主要是管理堆内内存,也就是当对堆内对象回收的时候,才有可能回收直接内存)
3:扩大程序运行的内存,由于JVM申请的内存有限,这时候可以通过堆外内存来扩大内存。

小坑:本机的直接内存的分配不会受到Java堆大小的限制,但是会受到本机总内存的限制。可能导致各个内存区域总和大于物理内存的限制,从而导致动态扩展时出现OutOfMemoryError

以上就是我的学习成果,如果有什么我没有思考到的地方或是文章内存在错误,欢迎与我分享。