Java进阶之内存模型

 2019-12-10 11:23  阅读(808)
文章分类:Java Core

一张常见的图,java虚拟机运行时数据区:

20191210001139\_1.png

20191210001139\_2.png

一、虚拟机栈和本地方法栈

虚拟机栈: 虚拟机为每个方法所创建的栈帧,用于存储每个方法的局部变量表、操作数栈等信息。每一个方法从调用到执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。

20191210001139\_3.png

本地方法栈:native方法 => 本地方法不是用 Java 实现,对待这些方法需要特别处理。与 Java 虚拟机栈类似,它们之间的区别只不过是本地方法栈为本地方法服务。

20191210001139\_4.png

二、堆

是java对象的主要存储区域。该区域要实现自动内存管理,并被所有线程所共享。

所有的对象都在堆里分配内存。

java栈与堆怎样联系起来的?

20191210001139\_5.png

可以看见当执行Object obj = new Object()时,在栈的本地变量中一定要有一个引用存在一个obj的引用,new在堆上创建对象,并分配内存,并将该对象的引用复制给本地变量的obj,obj是一个引用类型。

java中对象的访问机制,一般有使用句柄和直接指针两种方法。

三、方法区和运行时常量池

被所有线程共享,主要存储Java的类结构信息,常量、静态变量。

四、对象判断和回收算法

java中有一个垃圾回收器,用来监视所有new 出来的对象,将那些不会被引用到的对象的内存空间释放掉,以供其他新的对象使用。 那么,怎么实现这种机制的呢?怎么辨别出那些不会被引用的类? 方法有两种:
引用计数和可达性分析
引用计数:问题:循环引用 => 很难解决对象循环引用的问题
可达性分析:是否可达GC Root Java语言中的GC Root : 1)在虚拟机栈(即本地变量表)中的引用对象 2)在方法区中的类静态属性引用对象 2)在方法区中常量引用的对象 3)在本地方法栈中国年JNI(即一般说的Native方法)的引用对象 但是一般的引用只描述了该对象是被引用还是没有被引用,那么我们的收集器每次只能判断该对象应该被回收还是不应该被回收; 但是我们希望要有一种更灵活的机制:也就是如果当前内存空间还足够,则将对象暂时保存在内存中;如果内存空间在垃圾收集后还很紧张,则应该回收该对象。比如一些缓存机制,也是这个道理。那么我们应该定义引用的“程度”: 来看看
Java中的引用: 1)强引用:类似于Object obj = new Object() => 被强引用关联的对象不会被垃圾收集器回收 2)软引用:描述一些还有用但并非必要的对象;可以活到进行第二次回收判定 3)弱引用:比软引用更弱一些,描述非必要的对象,不会活到第二次回收,即第一次回收就被收集了; 4)虚引用:唯一目的是能在这个对象被收集器回收时收到一个系统通知

回收过程:

真正宣布一个对象死亡,需要经过两次回收标记:

1)可达性分析:如不可达,则标记;

2)gc对f-queue中的对象进行第二次标记,如果此时该对象与任何引用链上的对象建立起了关系,则对象活了过来;反之,被第二次标记了,则对象会被真正回收

如下代码很好的演示了这一过程:

垃圾收集算法

**1)标记-清除算法:**问题:效率问题:效率低,空间问题:大量内存碎片

2)复制算法:回收新生代=>将内存区域划分为几块

**3)标记-整理算法:**回收老年代=>让所有存活的对象都向一边移动,然后直接清理掉边界以外的内存

3)分代收集算法: 将内存区域划分为几块,一般现在都将java堆划分为新生代和老生代,可以根据各个年代采用合适的算法: 新生代(对象存活时间短,大量对象死去)=>复制;老生代(对象存活时间长)=>标记-整理

垃圾收集器:(分代收集的思想来管理内存)

垃圾收集器有很多种,现在B/S系统上最常用的是CMS(Concurrent Mark Sweep) 和 G1(Garbage-First),它真正实现了垃圾回收线程和用户线程的并发执行,效率很高。

虚拟机把 Java 堆分成以下三块:

  • 新生代(Young Generation)
  • 老年代(Old Generation)
  • 永久代(Permanent Generation)

当一个对象被创建时,它首先进入新生代,之后有可能被转移到老年代中。

新生代存放着大量的生命很短的对象,因此新生代在三个区域中垃圾回收的频率最高。为了更高效地进行垃圾回收,把新生代继续划分成以下三个空间:

  • Eden(伊甸园)
  • From Survivor(幸存者)
  • To Survivor

20191210001139\_6.png

五、java内存模型

1. cpu的多级缓存

在看java内存模型之前,让我们先看看cpu的多级缓存与cpu的缓存一致性(MESI)

20191210001139\_7.png

20191210001139\_8.png

2. java内存模型

java将内存分为主内存和工作内存。这样的区分和堆、栈、工作区是不同层次的区分,二者并没有可比性更无所谓交集。

20191210001139\_9.png

当不同栈上的方法访问同一个对象引用时,它们就分别获得了这个变量的局部拷贝,存储在各自线程所在cpu的缓存中,然后由缓存刷到主内存中;

java的内存模型主要是为了定义程序中各个变量的访问规则。即弄清楚变量怎么将变量存储到内存和将变量从内存中取出。注意这里所说的变量是指实例字段、静态字段和构成数组对象的元素,但并不包括局部变量和方法参数(因为这些是线程私有的,存储在虚拟机栈中)。

硬件效率:将运算需要用的数据复制到缓存中,让运算能快速进行,运算结束再复制回主存中,这样处理器就不用等待缓慢的内存读写了。所以其实java的内存模型就是缓存和主存交互的过程抽象。

这两张图对比,其实就可以看出主内存就相当于内存,而各线程各自的工作内存就相当于cache。主内存中存的是堆中的实例对象数据部分,工作内存则对应虚拟机栈部分。

这张图的结构其实有点类似于分布式,那么分布式一个很重要的问题就是**“一致性”的问题。**

=> 那么怎么保证各缓存(工作内存)和主存(主内存)之间的一致性呢?

那么必须要有一种“一致性协议”来规定一些读写操作规则。

20191210001139\_10.png

六、java内存模型与线程

java的内存模型中,主存怎么与各线程的工作内存进行交互。这也是多线程并发的基础。只有主存与工作内存遵循了统一交互原则。“一致性协议”

java内存模型中的工作内存是cpu的寄存器和高速缓存的一个抽象描述,存储着主存中数据的临时拷贝;

20191210001139\_11.png

如上图所示,如果两个线程对主内存不可见,则可能存在问题;所以应当由jmm在刷新到主内存时设置一个内存屏障;控制可见性

七、同步的八种操作

20191210001139\_12.png

点赞(0)
版权归原创作者所有,任何形式转载请联系作者; Java 技术驿站 >> Java进阶之内存模型

相关推荐