『编程』12000字 史上最最最完整深入解析JVM,请先收藏再看!( 二 )


2、 主要目的是定义程序中各个变量的访问规则 。
3、 Java内存模型规定所有变量都存储在主内存中 , 每个线程还有自己的工作内存 。 (1) 线程的工作内存中保存了被该线程使用到的变量的拷贝(从主内存中拷贝过来) , 线程对变量的所有操作都必须在工作内存中执行 , 而不能直接访问主内存中的变量 。 (2) 不同线程之间无法直接访问对方工作内存的变量 , 线程间变量值的传递都要通过主内存来完成 。 (3) 主内存主要对应Java堆中实例数据部分 。 工作内存对应于虚拟机栈中部分区域 。
4、Java线程之间的通信由内存模型JMM(Java Memory Model)控制 。 (1)JMM决定一个线程对变量的写入何时对另一个线程可见 。 (2)线程之间共享变量存储在主内存中(3)每个线程有一个私有的本地内存 , 里面存储了读/写共享变量的副本 。 (4)JMM通过控制每个线程的本地内存之间的交互 , 来为程序员提供内存可见性保证 。
5、可见性、有序性:(1)当一个共享变量在多个本地内存中有副本时 , 如果一个本地内存修改了该变量的副本 , 其他变量应该能够看到修改后的值 , 此为可见性 。 (2)保证线程的有序执行 , 这个为有序性 。 (保证线程安全)
6、内存间交互操作:(1)lock(锁定):作用于主内存的变量 , 把一个变量标识为一条线程独占状态 。 (2)unlock(解锁):作用于主内存的变量 , 把一个处于锁定状态的变量释放出来 , 释放后的变量才可以被其他线程锁定 。 (3)read(读取):作用于主内存变量 , 把主内存的一个变量读取到工作内存中 。 (4)load(载入):作用于工作内存 , 把read操作读取到工作内存的变量载入到工作内存的变量副本中(5)use(使用):作用于工作内存的变量 , 把工作内存中的变量值传递给一个执行引擎 。 (6)assign(赋值):作用于工作内存的变量 。 把执行引擎接收到的值赋值给工作内存的变量 。 (7)store(存储):把工作内存的变量的值传递给主内存(8)write(写入):把store操作的值入到主内存的变量中
6.1、注意:(1)不允许read、load、store、write操作之一单独出现(2)不允许一个线程丢弃assgin操作(3)不允许一个线程不经过assgin操作 , 就把工作内存中的值同步到主内存中(4)一个新的变量只能在主内存中生成(5)一个变量同一时刻只允许一条线程对其进行lock操作 。 但lock操作可以被同一条线程执行多次 , 只有执行相同次数的unlock操作 , 变量才会解锁(6)如果对一个变量进行lock操作 , 将会清空工作内存中此变量的值 , 在执行引擎使用这个变量前 , 需要重新执行load或者assgin操作初始化变量的值 。 (7)如果一个变量没有被锁定 , 不允许对其执行unlock操作 , 也不允许unlock一个被其他线程锁定的变量(8)对一个变量执行unlock操作之前 , 需要将该变量同步回主内存中
堆的内存划分:
Java堆的内存划分如图所示 , 分别为年轻代、Old Memory(老年代)、Perm(永久代) 。 其中在Jdk1.8中 , 永久代被移除 , 使用MetaSpace代替 。
1、新生代:(1)使用复制清除算法(Copinng算法) , 原因是年轻代每次GC都要回收大部分对象 。 新生代里面分成一份较大的Eden空间和两份较小的Survivor空间 。 每次只使用Eden和其中一块Survivor空间 , 然后垃圾回收的时候 , 把存活对象放到未使用的Survivor(划分出from、to)空间中 , 清空Eden和刚才使用过的Survivor空间 。 (2)分为Eden、Survivor From、Survivor To , 比例默认为8:1:1(3)内存不足时发生Minor GC
2、老年代:(1)采用标记-整理算法(mark-compact) , 原因是老年代每次GC只会回收少部分对象 。
3、Perm:用来存储类的元数据 , 也就是方法区 。 (1)Perm的废除:在jdk1.8中 , Perm被替换成MetaSpace , MetaSpace存放在本地内存中 。 原因是永久代进场内存不够用 , 或者发生内存泄漏 。 (2)MetaSpace(元空间):元空间的本质和永久代类似 , 都是对JVM规范中方法区的实现 。 不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中 , 而是使用本地内存 。


推荐阅读