JVM的内存区域

本文详细解读了Java虚拟机内存区域,包括线程共享与私有的区别,以及虚拟机栈、本地方法栈、方法区、堆和直接内存的作用。通过示例和反汇编,展示了栈帧结构和内存分配过程,帮助读者理解Java程序执行时的数据管理机制。
摘要由CSDN通过智能技术生成

运行时数据区域

定义:Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域(内存虚拟化操作)。

类型:线程共享(被所有线程共享,且只有一份)与线程私有(一个线程拥有单独的一份内存区域)。

在JVM中,JVM内存主要分为方法区虚拟机栈本地方法栈程序计数器等。

这里还有一个直接内存,这个虽然不是运行时数据区的一部分,但是会被频繁使用。可以理解成没有被虚拟化的操作系统上的其他内存。

(那举个例子:比如操作系统上有8G内存,被JVM虚拟化了3G,那么还剩余5G,JVM是借助一些工具使用这5G内存的,这块内存部分称之为直接内存)

用思维导图按照与线程的关系划分整理为:

有图有真相

两个图片结合这样方便理解记忆。

下面通过Java一个示例方法的运行过程分块来解释各个概念。

其实在我们实际的代码中,一个线程是可以运行多个方法的,例如还是继续用上节的示例代码:

public class HelloWorld {
 
    public static void main(String[] args) {
        test1();
    }
 
    public static void test1(){
        test2();
    }
 
    public static void test2(){
        System.out.println("HelloWorld!");
    }
}

这段代码其实就是main方法调用其他方法,其他方法中运行另外的方法。

我们把代码跑起来,线程1来运行这段代码,就会有一个对应的虚拟机栈,同时在执行每个方法的时候都会打包成一个栈帧。

比如main开始运行时,打包一个栈帧送入虚拟机栈。运行到test1时,打包一个test1栈帧压入虚拟机栈,运行到test2时,打包一个test2栈帧压入虚拟机栈,等test2方法运行完了、test2方法出栈,接着test1方法运行完了、test1方法出栈,最后main方法运行完了,main方法这个栈帧也就出栈了。

这就是Java方法运行时对虚拟机栈的一个影响。虚拟机栈就是用来存储线程运行方法中的数据的。而每一个方法对应一个栈帧。

图形理解更容易(可以理解为手枪弹夹装入子弹,然后开枪打出子弹):

虚拟机栈

栈的数据结构先进后出的数据结构。

虚拟机栈的作用:在jvm运行过程中存储当前线程运行方法所需的数据,指令、返回地址

虚拟机栈是基于线程的:哪怕只有一个main()方法,也是以线程的方式运行的。在线程的生命周期中,参与计算的数据会频繁的入栈和出栈,栈的生命周期和线程是一样的。

虚拟机栈的大小为1M,可用参数-Xss调整大小,例如:-Xss256k。

参数官方文档(JDK1.8):https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html

栈帧:在每个Java方法被调用的时候,都会创建一个栈帧,并入栈。一旦方法完成相应的调用,则出栈。

栈帧大体包含四个区域:(局部变量表、操作数栈、动态连接、返回地址)

1、局部变量表:用于存放我们的局部变量的(方法中的变量)。

2、操作数栈:存放java执行时的操作数的,他就是一个栈,先进后出的栈结构,操作数栈,就是用来操作的。

3、动态连接:Java语言特性多态。

4、返回地址:正常返回(调用程序计数器中的地址作为返回)、异常的话(通过异常处理器表来确定)

下面通过一个简单的例子结合反汇编说明栈帧执行对内存区域有哪些影响:

public class TestDemo {
    public static void main(String[] args) {
        int res = work();
        System.out.println(res);
    }

    public static int work(){
        int x = 1;
        int y = 2;
        int z = (x + y) * 10;
        return z;
    }
}

 我们知道TestDemo.java运行编译后会在本地生成一个TestDemo.class的文件

对class文件进行反汇编 javap -c TestDemo.class 

 字节码解释地址:https://cloud.tencent.com/developer/article/1333540

结合字节码指令我们可以大致分析一下这个过程:先是通过aload进行加载,然后再invokespecial指令初始化main,然后再invokestatic指令调用类方法(static方法)work,work方法中会进行iconst_m1指令常量加载到操作数栈,然后再istore_<n>指令将一个数值从操作数栈存储到局部变量表,然后再iload指令将局部变量加载到操作数栈,过程中会执行一些算法指令比如:iadd..这样来来回回几次走完了,最终会执行ireturn指令将结果返回,然后 invokevirtual指令调用对象的示例方法println()将结果打印。

示例流程:线程运行Java方法->main方法执行->work方法执行(栈帧入栈、字节码的执行细节)->work方法执行完(栈帧出栈)

结合图示:

程序计数器

较小的内存空间,当前线程执行的字节码的行号指示器;每个线程之间独立存储,互不影响。

本地方法栈

本地方法栈跟Java虚拟机栈的功能类似,Java虚拟机栈用于管理Java函数的调用,而本地方法栈则用于管理本地方法的调用。但是本地方法并不是用Java实现的,而是由C语言实现的(比如Object.hashcode())。

它服务的对象是native方法。

方法区

是可供各条线程共享的运行时内存区域。它存储了每一个类的结构信息,例如运行时常量池字段和方法数据、构造函数和普通方法的字节码内容、还包括一些再类、实例、接口初始化时用到的特殊方法。

堆是JVM上最大的内存区域,我们申请的几乎所有的对象,都是在这里存储的。我们常说的垃圾回收,操作的对象就是堆。随着对象的频繁创建,堆空间占用的越来越多,就需要不定期的对不再使用的对象进行回收。这个在 Java 中,就叫作 GCGarbage Collection)。

堆大小参数:

-Xms:堆的最小值

-Xmx:堆的最大值

-Xmn:新生代的大小

-XX:NewSize: 新生代最小值
 
-XX:MaxNewSize :新生代最大值
 
 

 直接内存(堆外内存)

JVM在运行时,会从操作系统申请大块的堆内存,进行数据的存储;同时还有栈区。操作系统剩余的内存也就是堆外内存。这块内存不受java堆大小的限制,但受本机总内存的限制,可以通过-XX:MaxDirectMemorySize 来设置(默认与堆内存最大值一样),所以也会出现 OOM 异常。

直接内存主要是通过DirectByteBuffer申请的内存,可以使用参数"MaxDirectMemorySize"来限制他的大小。

其他堆外内存,主要是指使用了Unsafe(处于安全考虑,后续jdk1.9版本会去掉)或者其他JNI手段直接申请的内存。

 

我是娆疆_蚩梦,让坚持成为一种习惯,我们下期见!

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值