一。JVM内存结构
JVM运行时的数据区
分为五部分
堆: 运行区域最大的一块,占用内存最多。new的实例对象,数组。优势:运行时动态分配
JAVA栈:保存各个基本类型,对象引用。编译时确定大小
方法区:已经加载的static静态变量或类信息或常量信息,永久引用例如static People p = new People();的p引用
本地方法栈:native方法相关栈
程序计数器: 当前线程执行的字节码的行号数,上下文切换时会被保存下来还包括下一条需要执行的指令
参考JVM虚拟机知识: http://liuwangshu.cn/java/jvm/1-runtime-data-area.html
二。JAVA对象模型
JAVA对象本身的存储模型
三部分
方法区:JVM会给这个类创建个类信息放在这
堆: new创建实例对象时,会在堆放每个实例包括对象头和实例数据
栈: 方法中的参数变量,当某个对象被方法调用了,就会在栈中保存对象的引用,
三。JAVA内存模型----JMM
1.什么是JMM(JAVA内存模型)
2.为什么需要JMM JAVA内存模型
- 最开始我们编写的是*.java 文件
- 执行javac 编译成java字节码文件(*.class文件)
- JVM会执行字节码文件,并把字节码文件转化成机器指令
- 机器指令会在CPU上运行,也就是最终的程序执行
但是不同处理器的机器指令不相同,执行顺序不一样。所以不同处理器执行结果不一致,需要统一的标准即JMM保证多线程运行结果可期。
2.1 重排序:线程内部执行顺序与JAVA代码中的书写顺序不一致,可能被颠倒
重排序的三个场景
(1)编译器优化:提高执行效率,例如将对数据a的操作放在一起执行,避免反复读取的消耗
(2)CPU优化: 和编译器优化类似
(3)内存的重排序: 实际上是因为可见性问题造成的类似重排序的现象,线程1操作了a的值但没有写到主内存,所以线程2拿到还是原来的值
2.2 可见性:Cpu有多级缓存,每个线程都有本地内存,读写会将修改数据先保存在本地内存里,再放入主内存。
主内存是多线程共享的,线程间通信需要通过主内存来中转
线程1修改了a,b, 线程2可可能看不到a的修改甚至看到部分修改例如x =a, y = b这里x可能看到,y看不到
三.Happens-before原则
产生原因:因为JVM会对代码编译优化,重排序,所以需要Happens-before原则规定一些禁止编译优化的场景,保证并发的正确性
解释:如果操作1 happens-before操作2,那么操作1的操作结果对于操作2是可见的
a. 单线程原则
a=3;
b=a;
在单线程中,a=3在b=a之前,那么a=3将在b=a之前执行,且a对b可见,结果是b=3。
因为同一线程都使用本地内存,是可见的。但是它不影响重排序,只要后面的操作能看见前面的操作就行
b. 锁操作(synchronized和lock)
同一个锁上的两个线程,后执行的线程能看到先执行的线程所有操作。
c.volatile变量
volatile变量只要写入了,后面的操作都能看到,禁止重排序
d。 线程join
thread1.join(); 执行后面的主线程语句都能看到thread1的执行结果
c. 传递性
操作1 先于操作2, 操作2先于操作3,那么操作1 先于操作3
d. 中断
一个线程被其他线程interrupt,那么检测中断(isInterrupted)一定能看到
e. 构造方法
finallize方法一定能看到构造方法的操作结果
f. 工具类的HB原则
(1)线程安全的容器例如ConcurrentHashMap 的get方法一定能看到在他之前执行的put方法,即使是不同线程
(2)CountDownLatch 的await一定能看到其他线程执行的countDown结果
(3)Semphore的aquire已经能看到其他线程释放
(4)Future 的get方法能拿到完整结果
(5)线程池提交很多任务,每个任务都能看到之前提交的任务结果
(6)CyclicBarrier
重点关注锁操作和volatile,这是需要我们写代码的
public class VolatileTest { public static void main(String[] args) { while (true){ Vis vis = new Vis(); Thread t1 = new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } vis.setVal(); } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } vis.print(); } }); t1.start(); t2.start(); } } } class Vis{ int a=0; int b=0; public void setVal(){ a=3; b=a; } public void print(){ System.out.println("a:"+a+",b:"+b); } }
这里的结果:
a:3,b:3
a:0,b:0
a:0,b:3 ------t2只看到t1部分结果,可见性
a:3,b:0 ------