上一篇文章写了JAVA的垃圾回收机制,提到垃圾回收机制主要是发生在堆内存中(极少可能发生在方法区中),这里便有同学会联想到我堆栈方法区都没搞清楚,你直接来堆内存的垃圾回收机制了,那这篇文章就直接来讲讲JVM内存结构以及它另外两个傻傻分不清名字的兄弟:JAVA内存模型与JAVA对象模型。
记录目前主要为自己加深理解,后期会加入自己画的配图供大家理解。
JVM内存结构
首先,五大重要组成部分。
堆
所有线程共享
主要存放各种对象和数组
方法区
所有线程共享
主要存放类的定义、常量、静态变量
相当于永久代
java虚拟机栈
线程独享
存放java方法的栈针
基本数据类型和对象引用
参与方法的调用与返回
本地方法栈
线程独享
存放native方法的栈针
程序计数器
线程独享
记录指令的位置
其实还有一个直接内存的部分,它不属于运行时数据区,也不是java虚拟机规范中定义的内存区域。
java内存模型(JAVA MEMORY MODEL)
众所周知java是一门跨平台的语言,不同系统上的虚拟机对于java语言中一些处理理解不一样,导致最终的运行结果不一样,于是就有JMM的这种规范。
JMM规定程序所有的变量都存储在主内存中,每个线程都拥有一份主内存的副本,线程对变量的操作必须在工作内存中进行,线程与线程之间不能直接进行数据交互,而只能通过工作内存和主内存之间的数据同步,JMM主要是规定数据同步的方式和时间。
JMM包括三种特性:
排序性
不同的系统对程序指令顺序会有自己独特的优化,在其带来程序速度的同时也会因为重排序产生一系列不可预知的问题,而JMM中volatile关键字所修饰的属性会禁止指令重排,synchronized关键字会将程序转化为单线程形式,从而避免重排序带来的影响。其中volatile关键字使用了内存屏障,即Load屏障和Store屏障,其规定:
在写之前加StoreStore屏障,在写之后加StoreLoad屏障
在读之前加LoadLoad屏障,在读之后加LoadStore屏障
可见性
因为线程对变量的操作完全在从主内存拷贝过来的副本上的,这样就有可能导致不同线程对同一个变量的修改无法统一,volatile关键字所修饰的属性一旦被修改则马上同步到内存,一旦要读取则必须从主存中读取。
happens-before规则很好地保证了可见性
- 单线程规则
- 锁操作(synchronized和lock)
- volatile修饰
- 线程启动 start()方法
- 传递性
- finalize()一定是最后执行的
- 线程join方法
- 中断:一个线程被interrupt时,检测中断isInterrupted一定能检测到。
原子性
JMM要求操作必须是原子性的,这样也能避免线程在一些关键的操作上互相穿插而导致的错误结果,synchronized关键字则将其保护的操作限定为一种单线程的操作,主要是得益于monitorenter(从主存中刷新变量值到工作内存中)和monitorexit(将最新值从工作内存中刷新到主存中)两个高级指令。
在讲到内存同步问题的时候不得不提的就是两个方法:
CountDownLatch:计数器递减使得某线程在等待其他线程执行完成后执行,不可复用
CyclIcBarrier:计数器递增使得线程之间相互等待直至达到阈值后继续执行,可复用
Java对象模型
即java对象的存储结构:主要分为三个区域
栈:保存对象的引用和基本数据类型
堆:保存对象的实例(对象头(markworld代表hashcode、锁状态、gc年龄等,元数据表示目标类的类型信息)、实例数据。对齐填充)
方法区:类的模型 instanceKlass(被元数据指向)