介绍
对基本的JVM内存模型的理解非常重要,尤其在开发多线程程序的时候。程序员只有在大脑中建立一个简单清晰的JVM内存概念模型,才能为正确开发多线程程序打下一个良好的基础。
基本的JVM内存模型
上图是一个最简JVM内存概念模型,JVM把它管理的内存大致分为栈内存和堆内存:
栈内存:JVM会为每个线程分配线程栈,线程运行时会使用。随着线程运行时调用方法的不同,线程的运行栈会不断变化。每次一个线程进入一个方法,JVM会为该线程创建新的调用栈(call stack)或称栈帧(stack frame),调用栈上保存线程在该方法上运行要使用的局部变量,这些变量是该线程独有的,其它线程不可见。随着调用方法的嵌套,调用栈也会依次增长,每次执行完一个方法,线程会退出相应的调用栈,相应的内存被JVM释放。
堆内存:是多线程共享的内存区,程序线程运行时创建的引用对象会被分配在堆内存上,包括:
对象类型
原子封装类型(Integer, Double, Long等等)
静态变量类型
注意,当线程在运行时创建对象,引用本身是存在线程自己的调用栈上的,而对象本身则是创建在堆上的。另外,线程之间可以复制对象引用,这样多个线程可以共享堆上的对象数据,但是这个时候可能存在多线程更新数据的可见性(visibility)和一致性(consistence)问题。
案例分析
对于上面的代码,我们假设启两个SampleThread线程,当两个线程运行到 方法的中间时,所展示出来的内存图如下图所示:
分析:
栈帧中的 是原子整型局部变量,存在线程栈上。
栈帧中的 是对象引用类型变量,变量本身存在线程栈上,它引用的 实例被分配在堆上。
两个线程同时通过各自的 变量引用共享的 实例。
是静态初始化好(通过 和 关键字)并且被分配在堆上, 静态引用变量也分配在堆上, 实例内部的原子类型字段 和 ,以及原子封装类型对象 和 都是被分配在堆上。
栈帧中的 是原子封装类型,引用本身存在线程栈上,封装对象被分配在堆上,两个线程各一个。
当线程退出 ,其栈帧上的 局部存储被释放,它引用的对象 也成为垃圾待回收。
当线程退出 ,其栈帧上的 和 局部变量存储被释放, 实例因为有静态引用,在程序运行结束前不会被垃圾回收。
结论
JVM大致把内存分为栈内存和堆内存,栈内存是线程的局部存储,而堆内存是线程的共享存储,JVM通过线程栈的方式虚拟多线程并发运行。
对栈内存变量操作不会有多线程问题,但对共享堆上的数据进行并发修改可能会存在可见性和一致性问题。
建立简单的JVM内存概念模型是多线程并发编程的基础,后续我会进一步介绍现代处理器架构和Java内存模型不匹配问题,以及由于这种不匹配而造成的并发编程的坑。
领取专属 10元无门槛券
私享最新 技术干货