JVM内存结构

虚拟机运行时数据区

Java虚拟机定义了在程序执行期间使用的各种运行时数据区域

**线程共享数据区:**所有线程都能访问这块内存;随虚拟机启动而创建,虚拟机退出而销毁。

**线程隔离数据区:**每个线程有独立的空间;随线程的创建而创建,随线程退出而销毁。

程序计数器

Java的线程为了能从休眠状态唤醒时继续执行,需要保存当前执行的位置。JVM为每个线程都划分了一个独立的较小的内存空间,称之为
程序计数器(Program Counter Register,PC),用于存储正在执行的虚拟机字节码指令的地址(执行Native方法时为空)。

虚拟机栈

每个方法被执行的时候,虚拟机会创建一个**栈帧(StackFrame)用于存放局部变量、方法出口等信息。并入栈到Java虚拟机栈(Java Virtual Machine Stacks)**中,
方法执行完返回调用者的时候,将该栈帧出栈。因此每一个方法被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

异常状况:

  1. 如果Java虚拟机栈容量可以动态扩展,当栈扩展时无法申请到足够的内存会抛出 OutOfMemoryError

  2. 如果线程请求的栈深度大于虚拟机所允许的深度,将抛出 StackOverflowError

    HotSpot不支持扩容,因此在单线程中,只会因为栈容量无法容纳新的栈帧而导致 StackOverflowError

    /**
     * VM Args: -Xss128k
     */
    public class JavaVMStackOF {
        private int stackLength = 1;
        
        public void stackLeak() {
            stackLength++;
            stackLeak();
        }
    
        public static void main(String[] args) {
            JavaVMStackOF oom = new JavaVMStackOF();
            try {
                oom.stackLeak();
            } catch (Throwable e) {
                System.out.printl("stack length:" + oom.stackLength);
                throw e;
            }
        }
    }
    

    -Xss128k :限制栈容量为128k

    无限递归,直至无法再创建新的栈帧,观测结果:

    stack length:2402
    Exception in thread "main" java.lang.StackOverflowError
      at ...
    
  3. 没有足够的内存创建新线程时,会抛出 OutOfMemoryError

    /**
     * VM Args: -Xss2M
     */
    public class JavaVMStackOOM {
        private void dontStop() {
            while (true) {}
        }
        
        public void stackLeakByThread() {
            while (true) {
                Thread thread = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        dontStop();
                    }
                });
                thread.start();
            }
        }
    
        public static void main(String[] args) {
            JavaVMStackOOM oom = new JavaVMStackOOM();
            oom.stackLeakByThread();
        }
    }
    

    -Xss2M 每个线程的虚拟机栈大小设置为2M;

    无限创建线程,观测结果:

    Exception in thread "main" java.lang.OutOfMemoryError: unable to create native thread
    

Java堆(Java Heap) 是被所有线程共享的一块内存区域,用于存放对象实例,在虚拟机启动时创建。
通过参数XmxXms,可以扩展堆的大小。

异常状况:
如果在Java堆的容量已满,无法为新对象分配内存空间,并且堆也无法再扩展时,Java虚拟机将会抛出 OutOfMemoryError

/**
 * VM args: -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
 */
public class HeapOOM {
    static class OOMObject {}
    public static void main(String[] args) {
        List<OOMObject> list = new ArrayList<>();
        
        while(true) {
            list.add(new OOMObject());
        }
    }
}

-Xms20m -Xmx20m :限制堆大小为20M;
-XX:+HeapDumpOnOutOfMemoryError :出现 OutOfMemoryError 时,Dump内存堆存储快照。

运行结果:

java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid3404.hprof ...
Heap dump file created [22055981 bytes in 0.663 secs]

方法区

方法区(Method Area)用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。
这区域的内存回收目标主要是针对常量池的回收和对类型的卸载。

异常状况:

如果方法区无法满足新的内存分配需求时,将抛出 OutOfMemoryError

本地方法栈

本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别只是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,
而本地方法栈则是为虚拟机使用到的本地(Native)方法服务。

异常状况:

与虚拟机栈一样,本地方法栈也会在栈深度溢出或者栈扩展失败时分别抛出 StackOverflowErrorOutOfMemoryError