JVM-jvm的内存结构和内存模型

Posted it江湖之旅

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JVM-jvm的内存结构和内存模型相关的知识,希望对你有一定的参考价值。


前言

上一篇咱们介绍了,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的内存结构和内存模型

JVM的内存模型

为什么会有jvm的内存模型?

我们知道,计算机CPU和内存的交互是最频繁的,内存是我们的高速缓存区,用户磁盘和CPU的交互,而CPU运转速度越来越快,磁盘远远跟不上CPU的读写速度,才设计了内存,用户缓冲用户IO等待导致CPU的等待成本,但是随着CPU的发展,内存的读写速度也远远跟不上CPU的读写速度,因此,为了解决这一纠纷,CPU厂商在每颗CPU上加入了高速缓存,用来缓解这种症状。其实就是一句话为了提高执行的效率

java为了提高程序的执行效率,所以也采用了这种类似的方式,这就是咱们熟悉了JVM内存模型了。

那么这种方式会带来什么问题呢?

如果有两个线程共享同一个变量,由于jvm的内存模型的存在,他们其实操作的都是各自工作内存的值,那么最后他们是如何通信的呢,就是数据最后如何达成一致呢?

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的内存结构和内存模型的主要内容,如果未能解决你的问题,请参考以下文章

jvm基础--JVM内存模型

详解Jvm内存结构

详解Jvm内存结构

详解Jvm内存结构

14.VisualVM使用详解15.VisualVM堆查看器使用的内存不足19.class文件--文件结构--魔数20.文件结构--常量池21.文件结构访问标志(2个字节)22.类加载机制概(代码片段

浅谈JVM内存结构,Java内存模型和Java对象模型