JVM内存结构、Java内存模型(JMM)、Java对象模型
JVM内存结构、Java内存模型(JMM)、Java对象模型
JVM内存结构
堆内存:存放Java对象,线程共享的区域,实在虚拟机启动时创建的,堆里面存放的都是对象的实例(new 出来的对象都存在堆中)
- 垃圾回收,主要回收的就是堆区
- 垃圾回收性能
- 新生代(young)
- Eden区, 新创建的对象
- 2个Survivor区(From Survivor和To Survivor) , 保存GC后还存活的对象 , 采用 复制算法
- 年老代(old)
- 对象存活时间长(经过多次新生代的垃圾收集,默认15次)的对象则进去老年代
- 堆中分配的对象实例过多,而且大部分对象都还在被使用,就会报内存溢出异常(OutOfMemoneyError)
- 新生代(young)
栈:线程私有,生命周期与线程相同,创建线程的时候就会创建一个Java虚拟机栈
本地方法栈:线程私有,是非Java语言实现的方法,例如,Java调用C语言,来操作某些硬件信息
程序计数器:为了线程切换能恢复到正确的位置,每条线程都需要一个独立的程序计数器,所以它是线程私有的
方法区:线程共享,用于存放已被虚拟机加载的类信息,常量,静态变量等数据,也称 永久代
- 垃圾回收,针对常量池回收、类型卸载(发射生成大量的临时使用的Class等信息)
- 存放编译期生成的各种字节码和符号引用,运行期间的常量也可以添加进入常量池中,比如String的intern()方法
- Java8已经没有方法区,取而代之的是 元空间(Metaspace)
Java内存模型(JMM)
通信步骤
- 线程A把本地内存A中更新过的共享变量刷新到主内存中;
- 线程B到主内存中读取线程A已更新过的共享变量;
重排序
- 编译器优化重排序
- 指令级并行的重排序
- 内存系统的重排序
- 通过内存屏障禁止重排序
顺序一致性
一个线程中的所有操作必须按照程序的顺序来执行;
(不管程序是否同步)所有线程都只能看到一个单一的操作执行顺序,在顺序一致性内存模型中,每个操作都必须原子执行且立刻对所有线程可见。
同步多线程并发执行
未同步多线程并发执行
未同步程序在JMM中不但整体的执行顺序是无序的,而且所有线程看到的操作执行顺序也可能不一致的。
锁(假设有1线程、2线程)
- happens before
1
2
3
4
5
6
7
8
9
10
11class Test{
int a = 0;
public synchronized void write(){//1 1 happens before 2
a++; //2 2 happens before 3 ,happens before传递性->2 happens before 5
} //3 监视器锁规则->3 happens before 4
public synchronized void read() {//4 4 happens before 5
int i = a; //5 5 happens before 6
} //6
}并发编程中最重要的同步机制
- 临界区互斥执行
- 释放锁的线程想获取同一个锁的线程发送消息
锁释放
线程1释放一个锁,实质上是线程1向接下来将要获取这个锁的某个(假设1线程)线程发出了(线程1对共享变量所做修改的)消息,JMM会把该线程对应的本地内存中的共享变量刷新到主内存中。
锁获取
线程2获取一个锁,实质上是线程2接收了之前某线程(假设1线程)发出的(在释放这个锁之前对共享变量所做的修改的)消息,JMM会把该线程对应的本地内存置为无效,从而使得被监听器保护的临界区代码必须冲主内存中读取共享变量。
线程1释放锁,随后线程2获取这个锁,这个过程中实质上是线程1通过主内存向线程2发送消息
final
- final变量一经初始化,就不能改变其值
- final关键字可以修饰变量、方法和类
- 如果值为对象或者数组,那么值指的就是引用地址,虽然引用地址无法改变,但是可以改变对象域或者数组中的元素
- 线程对这个对象的变量的域或者数组得到元素的改变不具有线程可见性
volatile
- 可见性,对volatile变量的读,任意线程总是能看到对这个volatile变量最后的写入
- 原子性,对任意单个volatile变量的读/写具有原子性
- volatile变量保证的是一个线程对它的写会立即刷新到主内存中,并置其他线程的副本为无效
- volatile变量的写被保证是可以被之后其他线程的读看到的,因此可以利用它进行线程之间的通信
1
2
3
4
5
6
7
8volatile int a;
public void set(int b){
a = b;
}
public void get() {
int i = a;
}线程1执行set(b)后,线程2执行get(),相当于线程1向线程2发送了消息。
内存屏障
- 两个能力
- 阻止栅栏前后的没有数据依赖性的代码进行指令重排序,保证程序在一定程度上的有序性
- 强制把写缓冲区/高速缓存中的脏数据等写回主内存,让缓存中相应的数据失效,保证数据的可见性
- 三种类型和一种伪类型
- 读屏障(Load Barrier),在读指令前插入读屏障,可以让高速缓存中的数据失效,重新从主内存加载数据,以保证读取的是最新的数据
- 写屏障(Store Barrier),在写指令之后插入写屏障,让写入缓存的最新数据写回到主内存,以保证写入数据立即对其他线程可见
- 全能屏障,具读屏障、写屏障的能力
- Lock前缀(伪),Lock不是一种内存屏障,但它能完成类似全能型内存屏障的功能
- 实现技术
- volatile(Lock前缀方式的内存屏障来实现)
- 两个能力
多处理器
指南
Java对象模型
注意:图片部分来自互联网。