深入了解JVM之内存区域(一)

mac2026-03-27  4

文章目录

一、概述二、各个区域1.程序计数器2.虚拟机栈3.本地方法栈4.直接内存5.方法区6.运行时常量池7.堆 三、版本更新四、相关问题1、什么是字面量和符号引用?2、方法区中除了运行时常量池,还有什么? 五、参考

一、概述

    了解JAVA的内存区域是学习JVM第一步,只有懂得了各个区域的工作原理、服务对象以及其中可能存在的问题,才能为日后的JVM调优、内存溢出排错等问题提供帮助。     根据《虚拟机规范》第二版规定,JVM管理的内存将包含以下几个运行时数据区域:

二、各个区域

    这一部分的阐述是基于JDK1.6,JDK1.7和1.8的改动会在下一部分进行讲解。

1.程序计数器

    当前线程字节码运行的行号指示器,通过程序计数器实现分支、循环、跳转、异常处理、线程恢复等基础功能。这是唯一不会发生内存溢出的区域。

2.虚拟机栈

    每一个方法的执行都会创建一个栈帧,用于存储局部变量表、方法出口、动态链接、操作栈等信息。每一个方法从执行到结束都对应了一个栈帧从虚拟机栈入栈到出栈的过程。     局部变量表:存放了编译器可知的各种基础数据类型(boolean、byte、char、short、int、float、long、double),对象应用(reference类型,代表对象的句柄或者指向对象起始地址的引用指针 ),和returnAddress(指向了一条字节码指令的地址)。局部变量表所需的空间是编译期间已经确定,在运行期间不会改变大小 。     动态链接:每一个栈帧都包含了当前方法指向运行时常量池中的符号引用以支持动态链接。这些符号引用在运行时转化为直接引用。     操作栈:每一个栈帧都包含了一个先进后出栈,称为操作数栈。操作数栈是方法调用和执行的空间,通过弹栈/压栈的形式来操作数据。JVM提供了将局部变量或常量加载到操作数栈的字节码指令,用这些指令将参与运算的值压入操作数栈,然后弹出这些值,进行运算,将结果压回操作数栈。     方法返回地址:方法执行后下一步指令的地址,方法正常退出时,程序计数器的值可以作为返回地址;异常退出时,返回地址要通过异常处理器决定。

3.本地方法栈

    为虚拟机使用到的native方法服务。

4.直接内存

    在JDK1.4后引入了NIO,一种基于通道channel和缓冲区buffer的I/O方式,它可以使用native函数库直接分配堆外内存,然后通过一个存储在Java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作,这样可以避免在java堆和native堆中来回复制数据,在某些场景显著提高性能

5.方法区

    存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据,也称为非堆(Non-Heap)。该区域很少出现垃圾回收,回收目标主要是针对常量池的回收和对类型的卸载。

6.运行时常量池

    运行时常量池是方法区的一部分,用于存放多种类型的常量,范围是从编译期已知的字面量到必须在编译期解析的方法和字段引用。     String.intern()方法可用来返回常量池中的某字符串,如果常量池中已经存在该字符串,则直接返回常量池中该对象的引用。如果不存在,则在该字符串放入常量池中。     对于直接做+运算的两个字符串(字面量)常量,并不会放入字符串常量池中,而是直接把运算后的结果放入字符串常量池中。(String s = “abc”+ “def”, 会直接生成“abcdef"字符串常量 而不把 “abc” "def"放进常量池)     对于先声明的字符串字面量常量,会放入字符串常量池,但是若使用字面量的引用进行运算就不会把运算后的结果放入字符串常量池中了(String s = new String(“abc”) + new String(“def”),在构造过程中不会生成“abcdef"字符串常量)     字符串常量池:在JDK1.7之前,还在运行时常量池;在JDK1.7之后,移到堆中,存储字符串常量和字符串引用

7.堆

    用于存放对象实例,是垃圾收集器的主要工作区域。Java堆分为新生代和老年代,其中新生代还分为Eden区、From Survior区和To Survior区。新生代和老年代的默认比例是1:2。为了更好地回收和分配内存,线程共享的堆还划分出了多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB)。

三、版本更新

    引用JDK1.7和JDK1.8的内存模型比较这篇博客的一个回答:

在JDK1.7之前,运行时常量池、字符串常量池存放在方法区, 此时hotspot虚拟机对方法区的实现为永久代。 在JDK1.7,字符串常量池被从方法区拿到了堆中, 这里没有提到运行时常量池,也就是说字符串常量池被单独拿到堆,运行时常量池剩下的东西还在方法区, 也就是hotspot中的永久代 在JDK1.8 hotspot移除了永久代用元空间(Metaspace)取而代之, 这时候字符串常量池还在堆, 运行时常量池还在方法区, 只不过方法区的实现从永久代变成了元空间(Metaspace)

    JDK1.8内存模型:

四、相关问题

1、什么是字面量和符号引用?

    字面量包括:1.文本字符串 2.八种基本类型的值 3.被声明为final的常量等;     符号引用包括:1.类和方法的全限定名 2.字段的名称和描述符 3.方法的名称和描述符。

2、方法区中除了运行时常量池,还有什么?

    class常量池:我们写的每一个Java类被编译后,就会形成一份class文件;class文件中除了包含类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池(constant pool table),用于存放编译器生成的各种字面量(Literal)和符号引用(Symbolic References);     字符串常量池:在HotSpot VM里实现的string pool功能的是一个StringTable类,它是一个Hash表,默认值大小长度是1009;这个StringTable在每个HotSpot VM的实例只有一份,被所有的类共享。字符串常量由一个一个字符组成,放在了StringTable上。     在JDK6.0中,StringTable的长度是固定的,长度就是1009,因此如果放入String Pool中的String非常多,就会造成hash冲突,导致链表过长,当调用String#intern()时会需要到链表上一个一个找,从而导致性能大幅度下降;可以存放字符串。     在JDK7.0中,StringTable的长度可以通过参数-XX:StringTableSize指定,可以存放字符串以及放于堆内的字符串对象的引用。

五、参考

Java虚拟机栈之操作数栈 JVM虚拟机规范官方文档 JDK1.7和JDK1.8的内存模型比较 Java中的常量池(字符串常量池、class常量池和运行时常量池) 《深入了解Java虚拟机:JVM高级特性与最佳实践》书籍

最新回复(0)