JVM-jvm的内存结构和内存模型
Posted it江湖之旅
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JVM-jvm的内存结构和内存模型相关的知识,希望对你有一定的参考价值。
前言
上一篇咱们介绍了,jvm的类加载机制和流程,这篇咱们就说一下,jvm把类加载到以后在内存里面是如何分配的(jvm内存结构),以及在运行过程中为了提升程序性能而做的内存模型。
JVM的内存结构
1.程序计数器:用来记录每一个线程中下一条要执行的指令,线程之间互不影响相互独立。(线程私有)
2.Java虚拟机栈:它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧,存储局部变量表、操作数栈、动态链接、方法出口等信息。可能抛出的异常StackOverflowError和OutOfMemoryError(线程私有)
3.本地方法栈:发挥的作用于虚拟机栈类似,管理的是本地方法。可能抛出的异常StackOverflowError和OutOfMemoryError(线程私有)
4.Java堆:内存中最重要的部分,几乎所有的对象和数组都是在堆内存分配空间的。(线程共享)堆内存可以简单分为新生代(eden区、{S0、S1经过一次GC})和老年代,新生代指的是新建的对象,而老年代指的是经过垃圾回收次数较多的对象。
5.方法区:保存类的元数据(类的类型信息、常量池、域信息、方法信息),GC主要回收常量池和元数据。(线程共享)可以通过参数分配内存的大小,也可以打印内存回收信息。[jdk1.8永久代背取消换成了元数据区,常量池移到了堆内存]
再看一个上一篇文章的图,体会一下各个区的大致关系
JVM的内存模型
为什么会有jvm的内存模型?
我们知道,计算机CPU和内存的交互是最频繁的,内存是我们的高速缓存区,用户磁盘和CPU的交互,而CPU运转速度越来越快,磁盘远远跟不上CPU的读写速度,才设计了内存,用户缓冲用户IO等待导致CPU的等待成本,但是随着CPU的发展,内存的读写速度也远远跟不上CPU的读写速度,因此,为了解决这一纠纷,CPU厂商在每颗CPU上加入了高速缓存,用来缓解这种症状。其实就是一句话为了提高执行的效率。
java为了提高程序的执行效率,所以也采用了这种类似的方式,这就是咱们熟悉了JVM内存模型了。
那么这种方式会带来什么问题呢?
如果有两个线程共享同一个变量,由于jvm的内存模型的存在,他们其实操作的都是各自工作内存的值,那么最后他们是如何通信的呢,就是数据最后如何达成一致呢?
1、 线程1将自己工作内存中的X更新为1并刷新到主内存中;
2、 线程2从主内存读取变量X=1,更新到自己的工作内存中,从而线程2读取的X就是线程1更新后的值。
从上面的流程看出线程之间的通信都需要经过主内存,而主内存与工作内存的交互,则需要Java内存模型(JMM)来管理器。下图演示了JMM如何管理主内存和工作内存:
当线程1需要将一个更新后的变量值刷新到主内存中时,需要经过两个步骤:
1、 工作内存执行store操作;
2、 主内存执行write操作;
完成这两步即可将工作内存中的变量值刷新到主内存,即线程1工作内存和主内存的变量值保持一致;
当线程2需要从主内存中读取变量的最新值时,同样需要经过两个步骤:
1、主内存执行read操作,将变量值从主内存中读取出来;
2、工作内存执行load操作,将读取出来的变量值更新到本地内存的副本;
完成这两步,线程2的变量和主内存的变量值就保持一致了。
内存的可见性问题?
由于工作内存这个中间层的出现,线程1和线程2必然存在延迟的问题,例如线程1在工作内存中更新了变量,但还没刷新到主内存,而此时线程2获取到的变量值就是未更新的变量值,又或者线程1成功将变量更新到主内存,但线程2依然使用自己工作内存中的变量值,同样会出问题。不管出现哪种情况都可能导致线程间的通信不能达到预期的目的。
咱们来看一个代码:
public class VolitileTest implements Runnable{
private boolean flag = true;
public void stop(){ flag=false; } @Override
public void run() { System.out.println(Thread.currentThread().getName()+"线程开始"); while (flag) { } System.out.println(Thread.currentThread().getName()+"线程结束"); }
public static void main(String[] args) { System.out.println("进入主线程"); VolitileTest volitileTest = new VolitileTest(); Thread thread = new Thread(volitileTest,"t1"); thread.start(); try { thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } volitileTest.stop(); System.out.println("flag="+volitileTest.flag); System.out.println("主线程结束"); } }
结果发现程序是一直在运行的,咱们自己设置的false没有起作用,这主要就是jmm导致的问题,因为t1线程进行了一个休眠,此时调用stop值进行了一个flag修改成了false,但是并没有同步到t1线程,t1线程一直使用的还是自己的工作线程中的值true.所以程序不会停止。
那么如何实现内存的可见性呢?
这时变量可以使用volatile关键字进行修饰,就可以帮我们实现内存的可见性了。加上关键字工作内存的值发生修改后,就可以强制将值刷新到主内存中,而且使参与操作共享资源的其他工作内存的值实效,这样其他线程就会强制去,主内存中去读取数据,从而保证了内存的可见性。 当然volatile除了解决了内存的可见性问题,他也可以禁止JVM的指令重排序。但是他不具备原子性。
未完待续。。。。。下一篇预告:JVM-jvm的垃圾回收算法
以上是关于JVM-jvm的内存结构和内存模型的主要内容,如果未能解决你的问题,请参考以下文章
14.VisualVM使用详解15.VisualVM堆查看器使用的内存不足19.class文件--文件结构--魔数20.文件结构--常量池21.文件结构访问标志(2个字节)22.类加载机制概(代码片段