面试必问:说一下 Java 虚拟机的内存布局

科技资讯 投稿 8400 0 评论

面试必问:说一下 Java 虚拟机的内存布局

官方定义

《Java虚拟机规范》中将 JVM 运行时数据区域划分为以下 5 部分:

    程序计数器(Program Counter Register)
  1. Java虚拟机栈(Java Virtual Machine Stacks)
  2. 本地方法栈(Native Method Stack)
  3. Java 堆(Java Heap)
  4. 方法区(Methed Area)

接下来,我们分别来看每个模块的作用及详细介绍。

1.程序计数器

The Java Virtual Machine can support many threads of execution at once (JLS §17. Each Java Virtual Machine thread has its own pc (program counter register. At any point, each Java Virtual Machine thread is executing the code of a single method, namely the current method (§2.6 for that thread. If that method is not native, the pc register contains the address of the Java Virtual Machine instruction currently being executed. If the method currently being executed by the thread is native, the value of the Java Virtual Machine's pc register is undefined. The Java Virtual Machine's pc register is wide enough to hold a returnAddress or a native pointer on the specific platform。

也就是说,程序计数器(Program Counter Register)是线程独有一块很小的内存区域,保存当前线程所执行字节码的位置,包括正在执行的指令、跳转、分支、循环、异常处理等。

1.1 作用

CPU 核数是比较少的,而任务(线程)是比较多的,所以真实的情况是,CPU 会不停的切换线程以执行所有的程序,当然因为(CPU)切换的速度比较快,所以我们是感知不到的,我们感觉好像所有的程序都是一直在执行,其实从微观的层面来看,所有的程序都是切换执行的。

这就是程序计数器的作用,程序计数器里面保存了当前线程执行的行号,这样当 CPU 切换到当前线程时,才能接着上次执行的位置,继续执行

1.2 线程共享

程序计数器记录的是每个线程的执行行号,所以每个线程都拥有自己的程序计数器,所以此区域不是线程共享的,而是线程私有的

1.3 GC

此区域不存在 GC。

1.4 OOM

此区域不存在 OOM 的问题。

2.Java 虚拟机栈

Each Java Virtual Machine thread has a private Java Virtual Machine stack, created at the same time as the thread. A Java Virtual Machine stack stores frames (§2.6. A Java Virtual Machine stack is analogous to the stack of a conventional language such as C: it holds local variables and partial results, and plays a part in method invocation and return. Because the Java Virtual Machine stack is never manipulated directly except to push and pop frames, frames may be heap allocated. The memory for a Java Virtual Machine stack does not need to be contiguous.
In the First Edition of The Java® Virtual Machine Specification, the Java Virtual Machine stack was known as the Java stack.
This specification permits Java Virtual Machine stacks either to be of a fixed size or to dynamically expand and contract as required by the computation. If the Java Virtual Machine stacks are of a fixed size, the size of each Java Virtual Machine stack may be chosen independently when that stack is created.
A Java Virtual Machine implementation may provide the programmer or the user control over the initial size of Java Virtual Machine stacks, as well as, in the case of dynamically expanding or contracting Java Virtual Machine stacks, control over the maximum and minimum sizes.
The following exceptional conditions are associated with Java Virtual Machine stacks:

    If the computation in a thread requires a larger Java Virtual Machine stack than is permitted, the Java Virtual Machine throws a ***Error.
  • If Java Virtual Machine stacks can be dynamically expanded, and expansion is attempted but insufficient memory can be made available to effect the expansion, or if insufficient memory can be made available to create the initial Java Virtual Machine stack for a new thread, the Java Virtual Machine throws an OutOfMemoryError.

Java 虚拟机栈是线程私有的区域,它随着线程的创建而创建。它里面保存的是局部变量表(基础数据类型和对象引用地址)和计算过程中的中间结果。Java 虚拟机的内存不需要连续,它只有两个操作:入栈和出栈。

Java 虚拟机栈出现的异常有两种:

  • 如果 Java 虚拟机栈是动态扩展的,那么当内存不足时,就会引发 OutOfMemoryError 的异常。

    2.1 作用

    Java 虚拟机栈主要是管 Java 程序运行的,它保存的是方法的局部变量、方法执行中的部分结果,并参与方法的调用和返回。

2.2 线程共享

Java 虚拟机栈是线程私有的,它的生命周期和线程的生命周期一致。

2.3 GC

不涉及垃圾回收的。

2.4 OOM

    当 Java 虚拟机栈大小固定时,如果程序中的栈分配超过了最大虚拟机栈就会出现 ***Error 异常。

也就是,Java 虚拟机栈是可能存在 OOM 的

2.5 常见参数设置


如设置:-Xss128k,表示设置每个线程的栈大小为 128k,此设置等价于 -XX:ThreadStackSize=128k。

2.6 常见问题演示

3.本地方法栈

An implementation of the Java Virtual Machine may use conventional stacks, colloquially called "C stacks," to support native methods (methods written in a language other than the Java programming language. Native method stacks may also be used by the implementation of an interpreter for the Java Virtual Machine's instruction set in a language such as C. Java Virtual Machine implementations that cannot load native methods and that do not themselves rely on conventional stacks need not supply native method stacks. If supplied, native method stacks are typically allocated per thread when each thread is created.
This specification permits native method stacks either to be of a fixed size or to dynamically expand and contract as required by the computation. If the native method stacks are of a fixed size, the size of each native method stack may be chosen independently when that stack is created.
A Java Virtual Machine implementation may provide the programmer or the user control over the initial size of the native method stacks, as well as, in the case of varying-size native method stacks, control over the maximum and minimum method stack sizes.
The following exceptional conditions are associated with native method stacks:

    If the computation in a thread requires a larger native method stack than is permitted, the Java Virtual Machine throws a ***Error.
  • If native method stacks can be dynamically expanded and native method stack expansion is attempted but insufficient memory can be made available, or if insufficient memory can be made available to create the initial native method stack for a new thread, the Java Virtual Machine throws an OutOfMemoryError.

本地方法栈俗称“C栈”,它是 Native(本地)方法(用 Java 编程语言以外的语言编写的方法),此区域和 Java 虚拟机栈类似,这不过诸如 C 语言等使用的栈空间。它也是存在两种异常:***Error 和 OutOfMemoryError。

此区域和 Java 虚拟机栈类似,不过是给 C/C++ 语言使用的。

4.堆

The Java Virtual Machine has a heap that is shared among all Java Virtual Machine threads. The heap is the run-time data area from which memory for all class instances and arrays is allocated.
The heap is created on virtual machine start-up. Heap storage for objects is reclaimed by an automatic storage management system (known as a garbage collector; objects are never explicitly deallocated. The Java Virtual Machine assumes no particular type of automatic storage management system, and the storage management technique may be chosen according to the implementor's system requirements. The heap may be of a fixed size or may be expanded as required by the computation and may be contracted if a larger heap becomes unnecessary. The memory for the heap does not need to be contiguous.
A Java Virtual Machine implementation may provide the programmer or the user control over the initial size of the heap, as well as, if the heap can be dynamically expanded or contracted, control over the maximum and minimum heap size.
The following exceptional condition is associated with the heap:

    If a computation requires more heap than can be made available by the automatic storage management system, the Java Virtual Machine throws an OutOfMemoryError.

堆是线程共享的,程序中所有类实例和数组的内存都存储在此区域,它在 Java 虚拟机启动时就会创建。对象不会被显式释放,只会在垃圾收集时释放。堆的大小可以是固定的,也可以动态扩展或收缩。堆的内存在物理层面不需要是连续的。

堆可能会出现 OutOfMemoryError 异常。

4.1 作用

4.2 线程共享

堆是线程共享的,堆上的对象可能被多个线程同时访问。

4.3 GC

4.4 OOM

当堆空间不足时,会发生 OutOfMemoryError 异常。

4.5 常见参数设置

    :设置初始 Java 堆大小,比如:-Xms10m,表示设置堆的初始大小为 10MB。

  • :设置最大 Java 堆大小,比如:-Xmx10m,表示设置堆的最大空间为 10MB。

    4.6 常见问题演示

    设置后的最终效果这样的:

配置完 Idea 之后,接下来我们来实现一下业务代码。在代码中我们会创建一个大对象,这个对象中会有一个 10m 大的数组,然后我们将这个大对象存储在 ThreadLocal 中,再使用线程池执行大于 5 次添加任务,因为设置了最大运行内存是 50m,所以理想的情况是执行 5 次添加操作之后,就会出现内存溢出的问题,实现代码如下:

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ThreadLocalOOMExample {

    /**
     * 定义一个 10m 大的类
     */
    static class MyTask {
        // 创建一个 10m 的数组(单位转换是 1M -> 1024KB -> 1024*1024B)
        private byte[] bytes = new byte[10 * 1024 * 1024];
    }

    // 定义 ThreadLocal
    private static ThreadLocal<MyTask> taskThreadLocal = new ThreadLocal<>(;

    // 主测试代码
    public static void main(String[] args throws InterruptedException {
        // 创建线程池
        ThreadPoolExecutor threadPoolExecutor =
                new ThreadPoolExecutor(5, 5, 60,
                        TimeUnit.SECONDS, new LinkedBlockingQueue<>(100;
        // 执行 10 次调用
        for (int i = 0; i < 10; i++ {
            // 执行任务
            executeTask(threadPoolExecutor;
            Thread.sleep(1000;
        }
    }

    /**
     * 线程池执行任务
     * @param threadPoolExecutor 线程池
     */
    private static void executeTask(ThreadPoolExecutor threadPoolExecutor {
        // 执行任务
        threadPoolExecutor.execute(new Runnable( {
            @Override
            public void run( {
                System.out.println("创建对象";
                // 创建对象(10M)
                MyTask myTask = new MyTask(;
                // 存储 ThreadLocal
                taskThreadLocal.set(myTask;
                // 将对象设置为 null,表示此对象不在使用了
                myTask = null;
            }
        };
    }
}

以上程序的执行结果如下:
从上述图片可看出,当程序执行到第 5 次添加对象时就出现内存溢出的问题了,这是因为设置了最大的运行内存是 50m,每次循环会占用 10m 的内存,加上程序启动会占用一定的内存,因此在执行到第 5 次添加任务时,就会出现内存溢出的问题。

5.方法区

The Java Virtual Machine has a method area that is shared among all Java Virtual Machine threads. The method area is analogous to the storage area for compiled code of a conventional language or analogous to the "text" segment in an operating system process. It stores per-class structures such as the run-time constant pool, field and method data, and the code for methods and constructors, including the special methods (§2.9 used in class and instance initialization and interface initialization.
The method area is created on virtual machine start-up. Although the method area is logically part of the heap, simple implementations may choose not to either garbage collect or compact it. This specification does not mandate the location of the method area or the policies used to manage compiled code. The method area may be of a fixed size or may be expanded as required by the computation and may be contracted if a larger method area becomes unnecessary. The memory for the method area does not need to be contiguous.
A Java Virtual Machine implementation may provide the programmer or the user control over the initial size of the method area, as well as, in the case of a varying-size method area, control over the maximum and minimum method area size.
The following exceptional condition is associated with the method area:

    If memory in the method area cannot be made available to satisfy an allocation request, the Java Virtual Machine throws an OutOfMemoryError.

方法区是线程共享的,方法区类似于传统语言的编译代码的存储区,或者类似于操作系统进程中的“文本”段。它存储每个类的结构,如运行时常量池、字段和方法数据,以及方法和构造函数的代码。

Java 虚拟机实现可以为程序员或用户提供对方法区域初始大小的控制,以及在可变大小的方法区域的情况下,对最大和最小方法区域大小的控制。

5.1 作用

用于存储每个类的结构,包括运行时常量池、静态变量、字段和方法数据

5.2 HotSpot 方法区实现

对于 HotSpot 虚拟机来说,不同的 JDK 方法区的实现是不同的,在 JDK 1.7 之前,HotSpot 技术团队使用的是永久代来实现方法区的,但这种实现有一个致命的问题,这样设计更容易造成内存溢出。因为永久代有 -XX:MaxPermSize(方法区分配的最大内存)的上限,即使不设置也会有默认的大小。例如,32 位操作系统中的 4GB 内存限制等,并且这样设计导致了部分的方法在不同类型的 Java 虚拟机下的表现也不同,比如 String::intern( 方法。所以在 JDK 1.7 时 HotSpot 虚拟机已经把原本放在永久代的字符串常量池和静态变量等移出了方法区,并且在 JDK 1.8 中完全废弃了永久代的概念。

JDK 1.8 之后,HotSpot 虚拟机开始使用元空间(Meta Space)来实现方法区了。

5.3 线程共享

5.4 GC

《Java 虚拟机规范》中规定方法区可以没有 GC(垃圾回收),但 HotSpot 中对此区域实现了 GC 操作。

5.5 OOM

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

/**
 * 方法区 OOM 演示(JDK 1.8+)
 * 设置 -XX:MaxMetaspaceSize=10m 元空间最大内存为 10MB。
 */
public class MethodAreaOOMExample {
    public static void main(String[] args {
        while (true {
            Enhancer enhancer = new Enhancer(;
            enhancer.setSuperclass(MethodAreaOOMExample.class;
            enhancer.setUseCache(false;
            enhancer.setCallback(new MethodInterceptor( {
                @Override
                public Object intercept(Object object, Method method,
                                        Object[] args,
                                        MethodProxy methodProxy throws Throwable {
                    return methodProxy.invokeSuper(object, args;
                }
            };
            enhancer.create(;
        }
    }
}

以上程序的执行结果如下图所示:
以上代码是通过 CGLIB 不断的产生动态代理类将方法区填满,从而就导致 OOM 的问题。

5.6 常用参数设置

永久代(HotSpot 虚拟机,JDK 1.7 之前设置有效):

    -XX:PermSize=100m:设置永久代初始值为 100MB。
  • -XX:MaxPermSize=100m:设置永久代最大值为 100MB。

元空间(HotSpot 虚拟机,JDK 1.8 之后设置有效):

    -XX:MetaspaceSize=100m:设置元空间初始大小为 100MB。
  • -XX:MaxMetaspaceSize=100m:设置元空间最大容量为 100MB,默认不设置,则没有限制。
  • -XX:CompressedClassSpaceSize=100m:设置 Class Metaspace 的大小为 100MB,默认值为 1G。

直接内存(HotSpot 虚拟机,JDK 1.8 之后设置有效):
-XX:MaxDirectMemorySize=100m:指定直接内存的最大容量为 100MB。

总结

    程序计数器(Program Counter Register)
  1. Java 虚拟机栈(Java Virtual Machine Stacks)
  2. 本地方法栈(Native Method Stack)
  3. Java 堆(Java Heap)
  4. 方法区(Methed Area)

其中线程私有的区域是:程序计数器、Java 虚拟机栈、本地方法栈;而线程共享的是:Java 堆和方法区。

参考 & 鸣谢

docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.5.1
zhuanlan.zhihu.com/p/518151056

本文已收录到 Gitee 开源仓库《Java 面试指南》,其中包含的内容有:Redis、JVM、并发、并发、MySQL、Spring、Spring MVC、Spring Boot、Spring Cloud、MyBatis、设计模式、消息队列等模块。Java 面试有它就够了:超全 Java 常见面试题,持续更新..。

编程笔记 » 面试必问:说一下 Java 虚拟机的内存布局

赞同 (46) or 分享 (0)
游客 发表我的评论   换个身份
取消评论

表情
(0)个小伙伴在吐槽