JVM内存模型

JVM内存区域

线程私有区域:程序计数器、Java虚拟机栈、本地方法栈
线程共享区域:Java堆、方法区、运行时常量池

程序计数器(线程私有) ​​​​​​​

程序计数器是一块比较小的内存空间,保存下一条执行的指令的地址。

如果当前线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;
如果正在执行的是一个Native方法,这个计数器值为空。

程序计数器内存区域是唯一一个在JVM规范中没有规定任何OOM情况的区域!

程序计数器有两个作用:
①、字节码解释器通过改变程序计数器来一次读取指令,从而实现代码的流程控制,比如我们常见的顺序、循环、选择、异常处理等。
②、在多线程的情况下,程序计数器用来记录当前线程执行的位置,当线程切换回来的时候仍然可以知道该线程上次执行到了哪里。

Java虚拟机栈(线程私有)

虚拟机栈描述的是Java方法执行的内存模型:
每个方法执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
每一个方法从调用直至执行完成的过程,就对应一个栈帧在虚拟机栈中入栈和出栈的过程。声明周期与线程相同。

局部变量表:存放了编译器可知的各种基本数据类型(8大基本数据类型)、对象引用。局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在执行期间不会改变局部变量表大小。

此区域一共会产生以下两种异常:

  1. 如果线程请求的栈深度大于虚拟机所允许的深度,将会抛出StackOverFlowError异常。
  2. 虚拟机在动态扩展时无法申请到足够的内存,会抛出OOM异常。
本地方法栈(线程私有)

本地方法栈与虚拟机栈的作用完全一样,区别在于本地方法栈为虚拟机使用的Native方法服务,而虚拟机栈为JVM执行的Java方法服务。

Java堆(线程共享)

Java堆(Java Heap)是JVM所管理的最大内存区域。

Java堆是所有线程共享的一块区域,在JVM启动时创建。存放的都是对象实例。

Java堆是垃圾回收器管理的主要区域,因此很多时候可以称之为"GC堆"。
根据JVM规范规定的内容,Java堆可以处于物理上不连续的内存空间中。
如果在堆中没有足够的内存完成实例分配并且堆也无法再拓展时,将会抛出OOM。

方法区(线程共享)

方法区是各个线程共享的内存区域。它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
此区域的内存回收主要是针对常量池的回收以及对类型的卸载。
JVM规范规定:当方法区无法满足内存分配需求时,将抛出OOM异常。

运行时常量池(方法区的一部分)

运行时常量池是方法区的一部分,存放字符串常量与符号引用。
字面量:字符串 、常量、基本数据类型的值。
符号引用:类和结构的完全限定名、字段的名称和描述符、方法的名称和描述符。

JVM内存溢出异常

Java堆溢出

Java堆用于存储对象实例,只要不断的创建对象,并且保证GC Roots到对象之间有可达路径来避免来GC清除这些对象,那么在对象数量达到最大堆容量后就会产生内存溢出异常。

class Test {
    static class OOMObject { }
    public static void main(String[] args) { 
        List<OOMObject> list = new ArrayList<>(); 
        while(true) { 
            list.add(new OOMObject()); 
        } 
    } 
}

当出现Java堆内存溢出时,异常堆栈信息"java.lang.OutOfMemoryError"会进一步提示"Java heap space"。当出现"Java heap space"则表明OOM发生在堆上。

内存泄漏:泄漏对象无法被GC
内存溢出:内存对象确实还应该存活。此时要根据JVM堆参数与物理内存相比较检查是否还应该把JVM堆内存调大;或者检查对象的生命周期是否过长。

虚拟机栈和本地方法栈溢出

关于虚拟机栈会产生的两种异常:
如果线程请求的栈深度大于虚拟机所允许的最大深度,会抛出StackOverFlow异常;
如果虚拟机在拓展栈时无法申请到足够的内存空间,则会抛出OOM异常。

StackOverFlow异常(单线程):

public class Test { 
    private int stackLength = 1;
    public void stackLeak() { 
        stackLength++; 
        stackLeak(); 
    }
    public static void main(String[] args) { 
        Test test = new Test(); 
        try {
            test.stackLeak(); 
        } catch (Throwable e) { 
            System.out.println("Stack Length: "+test.stackLength); 
            throw e; 
        } 
    } 
}

如果是因为多线程导致的内存溢出问题,在不能减少线程数的情况下,只能减少最大堆和减少栈容量的方式来换取更多线程。

内存溢出异常(多线程):

public class Test { 
    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) { 
        Test test = new Test(); 
        test.stackLeakByThread(); 
    } 
}

Java 内存模型

主内存与工作内存

Java内存模型的主要目标是定义程序中各个变量的访问规则,即在JVM中将变量存储到内存和从内存中取出变量这样的底层细节。此处的变量包括实例字段、静态字段和构成数组对象的元素,但不包括局部变量和方法参数,因为后两者是线程私有的,不会被线程共享。

Java内存模型规定了所有的变量都存储在主内存中。每条线程还有自己的工作内存,线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝,线程对变量的所有操作(读取、赋值等)都必须在工作内存进行,而不能直接读写主内存中的变量。不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成。

线程、主内存、工作内存三者的交互关系如下所示:
在这里插入图片描述

内存间交互操作

主内存与工作内存之间的具体交互协议:
一个变量如何从主内存中拷贝到工作内存、如何从工作内存同步回主内存之类的实现细节,Java内存模型中定义了如下8种操作来完成。

  • lock(锁定):作用于主内存的变量,它把一个变量标识为一条线程独占的状态;
  • unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定;
  • read(读取):作用于主内存的变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用;
    load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中;
    use(使用):作用于工作内存的变量,它把工作内存中一个变量的值传递给执行引擎;
    assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋给工作内存的变量;
    store(存储):作用于工作内存的变量,它把工作内存中一个变量的值传送到主内存中,以便后续的write操作使用;
    write(写入):作用于主内存的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中。

Java内存模型的三大特性:
原子性: 由Java内存模型来直接保证的原子性变量操作包括read、load、assign、use、store和read。大致可以认为,基本数据类型的访问读写是具备原子性的。如若需要更大范围的原子性,需要synchronized关键字约束。(即一个操作或者多个操作要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行)

可见性: 可见性是指当一个线程修改了共享变量的值,其他线程能够立即得知这个修改。volatile、synchronized、final三个关键字可以实现可见性。

有序性: 如果在本线程内观察,所有的操作都是有序的;如果在线程中观察另外一个线程,所有的操作都是无序的。前半句是指"线程内表现为串行",后半句是指"指令重排序"和"工作内存与主内存同步延迟"现象。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值