jvm - 运行时内存结构

jvm - 运行时内存结构

注意 : 本系列文章为学习系列,部份内容会取自相关书籍或者网络资源,在文章末尾处会有标注java

内存模型示意图

Jvm运行时内存结构

每一个区域的做用简述

pc寄存器 (program counter)

每一条java虚拟机线程都有本身的pc寄存器git

在任意时刻,一条java虚拟机线程只会执行一个方法的代码,正在被线程执行的方法称为该线程的当前方法程序员

(若是这个方法不是native的,那pc寄存器就保存java虚拟机正在执行的字节码指令的地址)数组

(若是这个方法是natice的,那pc寄存器的值是undefined)网络

pc寄存器的容量至少应当能保存一个returnAddress类型的数据或者一个与平台相关的本地指针的值数据结构

虚拟机栈 (virtual machine stack)

每条java虚拟机线程都有本身私有的java虚拟机栈,这个栈与线程同时建立,用于存储栈帧 (用于存储局部变量与一些还没有算好的结果)框架

除了栈帧的入栈和出栈以外,不会再受其余因素影响,因此栈帧能够在堆中分配,java虚拟机栈所使用的内存不须要保证是连续的jvm

java虚拟机规范容许java虚拟机栈被实现成固定大小的,也容许根据计算来动态扩展和收缩学习

(若是是固定大小的,那每个线程的java虚拟机栈的容量能够在线程建立时独立选定)this

若是线程请求分配的栈容量超过java虚拟机栈容许的最大容量,java虚拟机将抛出一个StackOverflowError异常

若是java虚拟机栈能够动态扩展,而且在尝试扩展时没法申请到足够内存,或者在建立新线程的时候没有足够的内存去建立对应的虚拟机栈,那么java虚拟机将会抛出一个OutOfMemoryError异常

堆 (heap)

堆是可供各个线程共享的运行时区域,也是提供全部类实例和数组对象分配内存的区域

堆在java虚拟机启动的时候被建立,它存储了被自动内存管理系统(垃圾收集器)所管理的各类对象,这些受管理的对象无需也没法显示地销毁

堆的用量能够是固定的,也能够是随程序执行的需求动态扩展,并在不须要过多空间的时候自动收缩

堆所使用的内存空间不须要保证是连续的

若是实际所需的堆超过了自动内存管理系统能提供的最大容量,那java虚拟机将会抛出一个OutOfMemoryError异常

方法区 (method area)

的逻辑组成部分

可供各个线程共享的运行时区域

存储了每一个类的结构信息

在虚拟机启动的时候建立

能够选择不实现垃圾收集和压缩

用量能够是固定的,也能够是随程序执行的需求动态扩展,并在不须要过多空间的时候自动收缩

所使用的内存空间不须要保证是连续的

若是方法区的内存空间不能知足内存分配请求,那java虚拟机将会抛出一个OutOfMemoryError异常

运行时常量 (runtime constant pool)

它是class文件中每个类或者接口的的常量池表的运行时表示形式,包括了多种不一样的常量,从编译期可知的数值字面量到必须在运行期解析后才能得到的方法和引用

每个运行时常量都在java虚拟机的方法区中分配,在加载类和接口到虚拟机后,就建立对应的运行时常量池

建立类或者接口时,若是构造运行时常量池所须要的内存空间超过了方法区所能提供的最大值,那java虚拟机将会抛出一个OutOfMemoryError异常

本地方法栈 (native method stack)

Java虚拟机可能会用到传统的栈(称为C stack)来支持native方法的执行,这个栈就是本地方法栈

若是java虚拟机支持本地方法栈,那么每个线程的本地方法栈容量能够在建立栈的时候独立选定

若是线程请求分配的栈容量超过本地方法栈的最大容量,java虚拟机将抛出一个StackOverflowError异常

若是本地方法栈能够动态扩展,而且在尝试扩展时没法申请到足够内存,或者建立新线程时没有足够的内存区建立对应的本地方法栈,那java虚拟机将会抛出一个OutOfMemoryError异常

栈帧 (frame)

栈帧的存储空间由建立它的线程分配在java虚拟机栈中

栈帧是用来存储数据和部分过程结果的数据结构,同时也用来处理动态连接,方法返回值,异常分派

栈帧随着方法的调用而建立,随着方法的结束而销毁,不管方法是正常完成仍是异常完成,都算做方法结束

每一个栈帧都有本身的本地变量表,操做数栈,指向当前方法所属的类的运行时常量池的引用

在某条线程执行过程当中的某个时间点上,只有目前正在执行的那个方法的栈帧是活动的

调用新的方法时,新的栈帧会随之而建立,而且会随着程序控制权移交到新方法,而称为新的当前栈帧

方法返回之际,当前栈帧会传回此方法的执行结果给前一个栈帧,而后虚拟机会丢弃掉当前栈帧,使得前一个栈帧从新称为当前栈帧

注意 : 栈帧是线程本地私有的数据,不可能在一个栈帧之中引用另一个线程的栈帧

栈帧 - 局部变量表

每一个栈帧内部都包含一组称为局部变量表的变量列表

栈帧中的局部变量表的长度由编译期决定

存储于类或接口的二进制表示之中(class),即经过方法的code属性保存以及提供给栈帧使用

一个局部变量能够保存一个类型为以下的数据,boolean,byte,char,short,int,float,reference,returnAddress

两个连续的局部变量能够保存一个类型为long或double的数据

局部变量表使用索引来进行定位访问

java虚拟机使用局部变量表来完成方法调用时的参数传递

栈帧 - 操做数栈

每一个栈帧内部都包含一个称谓操做数栈的后进先出栈

栈帧中的操做数栈的最大深度由编译期决定,而且经过方法的code属性保存以及提供给栈帧使用

栈帧在刚刚建立时,操做数栈是空的

java虚拟机提供一些字节码指令来从局部变量表或者对象实例的字段中复制常量或者变量的值到操做数栈中,也提供了一些指令用于操做数栈,取走数据,操做数据,以及把操做结果从新入栈

在调用方法时,操做数栈也用来准备调用方法的参数以及接收方法的返回结果

操做数栈的每个位置上能够保存一个java虚拟机定义的任意数据类型的值,包括long和double

任意时刻,操做数栈都会有一个肯定的深度,一个long或者double类型的数据会占用两个单位的栈深度,其余数据类型则会占用一个单位的栈深度

栈帧 - 动态连接

每一个栈帧内部都包含一个指向当前方法所在类型的运行时常量池的引用,以便对当前方法实现动态连接

在class文件里,一个方法若要调用其余方法,或者访问成员变量,则须要经过符号引用来表示,动态连接的做用就是将这些符号引用所表示的方法转换为对实际方法的直接引用

溢出的实例代码

栈溢出

public class StackOom {

    public int num = 1;

    public void stack() {
        num++;
        this.stack();
    }
    public static void main(String[] arge) {
        StackOom stackOom = new StackOom();
        stackOom.stack();
    }

}
Exception in thread "main" java.lang.StackOverflowError
    at org.itkk.learn.StackOom.stack(StackOom.java:15)
    at org.itkk.learn.StackOom.stack(StackOom.java:15)
    at org.itkk.learn.StackOom.stack(StackOom.java:15)
    at org.itkk.learn.StackOom.stack(StackOom.java:15)

以上这段代码,stack方法不断的递归调用,最终达到java虚拟机栈的最大容量,就抛出了StackOverflowError异常

堆溢出

public class HeapOom {

    private List<byte[]> data = new ArrayList<>();

    public void heap() {
        while (true) {
            data.add(new byte[1024 * 1024]);
        }
    }

    public static void main(String[] arge) {
        HeapOom heapOom = new HeapOom();
        heapOom.heap();
    }
}
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at org.itkk.learn.HeapOom.heap(HeapOom.java:21)
    at org.itkk.learn.HeapOom.main(HeapOom.java:32)

以上这段代码,heap方法,以死循环的方式不断的往data中添加1M大小的数组对象,最终堆中的内存空间不能知足data存放的要求,而抛出了OutOfMemoryError异常

元空间溢出

public class MetaspaceOom {

    static String str = "string";

    public static void main(String[] arge) {
        List<String> list = new ArrayList<>();
        while (true) {
            str += str;
            list.add(str.intern());
        }
    }
}
java.lang.OutOfMemoryError: Metaspace
        at sun.misc.Launcher.<init>(Unknown Source)
        at sun.misc.Launcher.<clinit>(Unknown Source)
        at java.lang.ClassLoader.initSystemClassLoader(Unknown Source)
        at java.lang.ClassLoader.getSystemClassLoader(Unknown Source)

以上这段代码使用以下命令执行 :

java -XX:MetaspaceSize=2m -XX:MaxMetaspaceSize=2m org.itkk.learn.MetaspaceOom

在运行MetaspaceOom类时,限定了元空间的大小,而main方法中,则以死循环的方式不停的拷贝生成新的字符串常量(intern方法),而字符串常量存储于元空间中,最终字符串常量超出了元空间的容量,从而抛出OutOfMemoryError异常(Metaspace)

java8中永久代的变化

在java8中,永久代已经由元空间替代了,在上面章节中的元空间溢出的实例代码中,在jdk6中,会出现"PermGen Space"溢出,在jdk7和jdk8中,则是出现"Java heap space"溢出

并且在java8中,-XX:PermSize和-XX:MaxPermGen已经提示无效了,以下:

使用以下命令运行
java -XX:PermSize=8m -XX:MaxPermSize=8m -Xmx16m  org.itkk.learn.MetaspaceOom

D:\develop\JetBrains\IdeaProjects\learn\leanmain\target\classes>java -XX:PermSize=8m -XX:MaxPermSize=8m -Xmx16m  org.itkk.learn.MetaspaceOom
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option PermSize=8m; support was removed in 8.0
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=8m; support was removed in 8.0

会得出以下两个警告,能够得知,Perm在jdk8中已经不存在了

元空间和永久代最大的区别在于,元空间再也不java虚拟机内存中,而是直接使用的本地内存,空间大小仅受本地内存的限制,而且可使用jvm参数来指定元空间的大小

上面元空间溢出的实例代码中就使用了-XX:MetaspaceSize和-XX:MaxMetaspaceSize来指定元空间的最大和最容量

参考文献

文章中部份内容取自<<Java虚拟机规范.Java SE 8版>>

结束

俗话说,不了解jvm的java程序员,等于耍流氓

深刻的了解底层的能有助于咱们更好的理解java程序运行的机制,从而帮助咱们写出更好的代码.

关于本文内容 , 欢迎你们的意见跟建议

代码仓库 (博客配套代码)


想得到最快更新,请关注公众号

想得到最快更新,请关注公众号

相关文章
相关标签/搜索