点击下方关注我,然后右上角点击...“设为星标”,就能第一时间收到更新推送啦~~~
类文件被类装载器加载之后,类中的内容(比如:变量、常量、方法、对象等)这些数据需要存储起来,存储的位置就是在 JVM 中的运行时数据区(Run-Time Data Areas),包括 5 个区域:方法区,堆,虚拟机栈,本地方法栈,程序计数器。
1、Method Area(方法区)
方法区是各个线程共享的内存区域,在虚拟机启动时创建,用于存储已被虚拟机加载的常量、静态变量、类信息、即时编译器编译后的代码等数据,所有定义方法的信息都保存在该区域。Java 虚拟机规范把方法区描述为堆的一个逻辑部分,为了与 Java 堆区分开,它有一个别名叫做Non-Heap(非堆)。当方法区无法满足内存分配需求时,将抛出 OutOfMemoryError 异常。
很多开发者习惯将方法区称之为“永久代“(Permanent Generation space),方法区在 JDK 8中就是元空间(Metaspace),已经没有了永久代的概念。如果出现 java.lang.OutOfMemoryError: PermGen space,说明是 Java 虚拟机对永久代内存设置不够。一般出现这种情况,都是程序启动需要加载大量的第三方 jar 包。例如:在一个Tomcat 下部署了太多的应用,或者大量动态反射生成的类不断被加载,最终导致 Perm 区被占满。解决方案是手动设置 MaxPermSize 大小。
2、Heap(堆)
Java 堆是 Java 虚拟机所管理内存中最大的一块,在虚拟机启动时创建,被所有线程共享,Java对象实例以及数组都在堆上分配。一个 JVM 实例只存在一个堆内存,堆内存逻辑上分为三部分:新生代+老年代+方法区/元空间。详细介绍参见下文的 Java 内存模型。
3、Java Virtual Machine Stacks(虚拟机栈)
虚拟机栈是一个线程执行的区域,保存着一个线程中方法的调用状态。每一个被线程执行的方法称为该栈中的栈帧,即每个方法对应一个栈帧,栈帧可以理解为一个方法的运行空间。每调用一个方法,就会向栈中压入一个栈帧;一个方法调用完成,就会把该栈帧从栈中弹出。每个栈帧中包括局部变量表、操作数栈、动态链接、方法返回地址。
局部变量表:方法中定义的局部变量以及方法的参数存放在局部变量表表中,局部变量表中的变量不可直接使用,如需要使用的话,必须通过相关指令将其加载至操作数栈中作为操作数使用。
操作数栈:以压栈和出栈的方式存储操作数。
动态链接:每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接。
方法返回地址:当一个方法开始执行后,只有两种方式可以退出,一种是遇到方法返回的字节码指令,另外一种是遇见异常,并且这个异常没有在方法体内得到处理。
4、The pc Register(程序计数器)
程序计数器占用的内存空间很小,由于 Java 虚拟机的多线程是通过线程轮流切换,并分配处理器执行时间的方式来实现的,在任意时刻,一个处理器只会执行一条线程中的指令。因此,为了线程切换后能够恢复到正确的执行位置,每条线程需要有一个独立的程序计数器(线程私有)。如果线程正在执行Java方法,则计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是 Native方法,则这个计数器为空。
2、Native Method Stacks(本地方法栈)
如果当前线程执行的方法是 Native 类型的,这些方法就会在本地方法栈中执行。
后面将为大家介绍 Java 内存模型。