细说 JVM 本地方法栈和堆内存

 2023-01-13
原文作者:OpenCoder 原文地址:https://juejin.cn/post/6995015588997234724

一.本地方法栈

202301011557247481.png

Nativemethodstack(本地方法栈):保存native方法进入区域的地址

对于一个运行中的Java程序而言,它还可能会用到一些跟本地方法相关的数据区。当某个线程调用一个本地方法时,它就进入了一个全新的并且不再受虚拟机限制的世界。本地方法可以通过本地方法接口来访问虚拟机的运行时数据区,但不止如此,它还可以做任何它想做的事情。

任何本地方法接口都会使用某种本地方法栈。当线程调用Java方法时,虚拟机会创建一个新的栈帧并压入Java栈。然而当它调用的是本地方法时,虚拟机会保持Java栈不变,不再在线程的Java栈中压入新的帧,虚拟机只是简单地动态连接并直接调用指定的本地方法。

如果某个虚拟机实现的本地方法接口是使用C连接模型的话,那么它的本地方法栈就是C栈。

这幅图展示了JAVA虚拟机内部线程运行的全景图。一个线程可能在整个生命周期中都执行Java方法,操作它的Java栈;或者它可能毫无障碍地在Java栈和本地方法栈之间跳转。

202301011557252332.png

该线程首先调用了两个Java方法,而第二个Java方法又调用了一个本地方法,这样导致虚拟机使用了一个本地方法栈。假设这是一个C语言栈,其间有两个C函数,第一个C函数被第二个Java方法当做本地方法调用,而这个C函数又调用了第二个C函数。之后第二个C函数又通过本地方法接口回调了一个Java方法(第三个Java方法),最终这个Java方法又调用了一个Java方法(它成为图中的当前方法)。

二.堆

202301011557257643.png

2.1概念

Heap 堆

  • 通过 new 关键字创建对象都会使用堆内存
  • 一个JVM实例只存在一个堆内存,堆也是Java内存管理的核心区域。Java堆区在JVM启动的时候即被创建,其空间大小也就确定了。它是 JVM 管理的最大一块内存空间。

特点

  • 它是线程共享的,堆中对象都需要考虑线程安全的问题

    《Java虚拟机规范》规定,堆可以处于物理上不连续的内存空间中,但在逻辑上它应该被视为连续的。所有的线程共享Java堆,在堆中还可以划分线程私有的缓冲区(Thread Local Allocation Buffer,TLAB)。

  • 有垃圾回收机制(后续在第二个系列:垃圾回收机制深入讲解)

说明:

当栈帧被执行的时候,里面有对象的创建,那么栈帧里面仅仅是保存对象名以及对应的地址值,真正的对象存储是分配在了堆内存:(全流程图)

202301011557263204.png

2.2内存分配关系

《Java虚拟机规范》中对Java堆的描述是:所有的对象实例以及数组都应当在运行时分配在堆上。(The heap is the run-time data area from which memory for all class instances and arrays is allocated)

  要注意的是:“几乎”所有的对象实例都在这里分配内存——是从实际使用角度看的。因为还有一些对象是在栈上分配的。

  数组和对象可能永远不会存储在栈上,因为栈帧中保存引用,这个引用指向对象或者数组在堆中的位置。

比如下面一段很简单的代码:

    public class Demo2 {
        public static void main(String[] args) {
            Hello h1 = new Hello();
            Hello h2 = new Hello();
        }
    }
    class Hello{
    }

在内存中的存放位置如下:

202301011557268325.png

在方法结束后,堆中的对象不会马上被移除,仅仅在垃圾收集的时候才会被移除,也就是触发了GC的时候,才会进行回收。如果堆中对象马上被回收,那么用户线程就会受到影响。

  堆,是GC(Garbage Collection,垃圾收集器)执行垃圾回收的重点区域。

2.3设置堆内存大小与OOM

Java堆区用于存储Java对象实例,那么堆的大小在JVM启动时就已经设定好了,大家可以通过选项"-Xmx"和"-Xms"来进行设置。

    “-Xms"用于表示堆区的起始内存,等价于-XX:InitialHeapSize。

    “-Xmx"用于表示堆区的最大内存,等价于-XX:MaxHeapSize。

  一旦堆区中的内存大小超过“-Xmx"所指定的最大内存时,将会抛出OutOfMemoryError异常。

  通常会将-Xms和-Xmx两个参数配置相同的值,其目的是 为了能够在java垃圾回收机制清理完堆区后不需要重新分隔计算堆区的大小,从而提高性能

默认情况下:

    初始内存大小:电脑物理内存大小/64。

    最大内存大小:电脑物理内存大小/4。

可以通过如下代码进行查看:

    /**
     * -Xms 用来设置堆空间(年轻代+老年代)的初始内存大小
     *  -X:是jvm运行参数
     *  ms:memory start
     * -Xmx:用来设置堆空间(年轻代+老年代)的最大内存大小
     */
    public class Demo3 {
        public static void main(String[] args) {
            // 返回Java虚拟机中的堆内存总量
            long initialMemory = Runtime.getRuntime().totalMemory() / 1024 / 1024;
            // 返回Java虚拟机试图使用的最大堆内存
            long maxMemory = Runtime.getRuntime().maxMemory() / 1024 / 1024;
            System.out.println("-Xms:" + initialMemory + "M");
            System.out.println("-Xmx:" + maxMemory + "M");
        }
    }

运行结果:

-Xms:245M

-Xmx:3625M