《Java平台体系》——第二章 JVM——JVM抽象架构

在学习该小节内容之前请大家先仔细理解下图所表达的内容:



如上图,JVM抽象架构的核心是运行时数据区(内存管理)的抽象架构(这里说抽象所表达的另外意思是不同的JVM实现可能有不同的实现策略)。当有了合理的内存管理策略,程序的执行仅仅是指令序列的推送。

其中方法区(Method Area)是对静态类文件结构的内存维护,不同的实现有不同的维护方法。

堆区(Heap)是对类实例的内存维护,说起堆,可以联想到“垃圾堆”,其不可避免的就是乱,在 JVM实现​部分我们谈到的垃圾回收主要就是针对堆区的维护。

Java堆栈(Java Stacks)是Java内存抽象架构的核心,有人把JVM说是 基于堆栈架构的原因就在于Java堆栈的概念。Java堆栈要求Java程序执行中的每个线程都有一个独立的堆栈,每个当前执行的方法是当前线程堆栈的一个片断(Frame)。

程序计数器(PC Registers),对于基于堆栈实现的JVM,这几乎是唯一寄存器了,它用来指示当前 Java 执行引擎执行到哪条Java字节码,指针指向方法区的字节码。

本地调用堆栈(Native Method Stacks)是本地库的调用堆栈,用来实现JNI(Java本地接口,在本章 JNI(Java本地接口)​小节会介绍该内容)。

为了对抽象架构的进一步理解,我从一个具体的实例出发,通过简化图表方式帮助大家理解:


① JVM通过引导类加载器(Bootstrap ClassLoader)加载类Main的字节码,通过校验之后为类在方法区建立静态内存结构。



② JVM启动主线程执行Main的静态方法main。将main以片断(Frame)的方式压入当前线程堆栈。


③ JVM通过类加载器加载类Class1,并为Class1建立方法区内存结构。然后调用Class1的构造函数,并将其压入当前线程堆栈。执行完毕构造函数之后把构造函数片断(Frame)从当前线程堆栈中移出。


④ JVM执行Class1的实例p的add方法,并且将其压入堆栈,当add方法返回,从当前线程堆栈中移出add片断(Frame)。

⑤ 主函数main执行完毕返回,当前线程堆栈清空,主线程执行完毕,进程直接退出。

从上面的粗略过程我们至少可以了解:
□ Java的方法区(Method Area)会随着类加载的数量不断地扩充。
□ 堆(Heap)区会随着新建类实例而不断地扩充。
□ 堆栈(Java Stacks)在程序执行中始终存在,不同的线程都有自己独立的执行堆栈。

下面我们再介绍一下JVM抽象架构中的另外一部分与我们日常编程息息相关的内容:多线程中的同步(解决并发带来数据不一致的一种方法,在JDK5.0之前唯一解决并发的原语就是同步)。

在前面我们已经看到堆(Heap)中的数据主要以“引用”的方式被堆栈使用,当多个线程对同一实例的数据进行操作时就可能发生数据不一致,我这里强调的主要是不一致,即超过一条指令对一个变量自身的连续引用操作(包括读和写)。

多线程中的数据同步在JVM规范中并没有强调具体的实现机制,从 Java字节码类文件格式​信息中我们只能看到对方法标注同步的地方和从指令集角度我们可以看到两条指令:194 (0xc2) monitorenter和195 (0xc3) monitorexit来标注同步块的开始和结束。也就是我们写程序的时候在方法前面加synchronized和同步块synchronized(object){}的具体体现。至于JVM如何实现方法同步和块同步都由JVM实现者来完成。

在介绍如何同步之前,我打个大家一定能理解的“俗”比方:上厕所。

有一天你很内急,不断地跑厕所,老发现没位置,但你仍然坚持不懈地跑试图看看有没有位置。同时可能还有其他人和你一样,也内急,老跑。这时候你可能有如下选择:

□ 告诉已占上位置的老兄们,那个完事了喊一下(通知Notify和等待Wait对应,但是广播性质的,人家通知了,你不一定就能抢到,因为可能等的人不止你一个)。
□ 回去干干其它的事情,然后接着跑,撞运气(非阻塞策略)。
□ 把老跑的人召集起来,排队等(队列阻塞等待策略Wait)。
□ 遇见领导了,让领导优先(优先级)。
□ 其它…

其实上面的选择我们都可以理解为解决并发问题的策略。但无论怎么看,占位的那些老兄就是占着位,我们形象的称作加“锁”(Lock)。大家都是为了等“锁”打开(Unlock)从而争得“锁”(Lock)而奋斗(注:解决并发带来数据不一致的策略有很多,例如假定不变先操作共享数据,回去修改的时候看看是否和操作前一样,如果一样直接修改,如果不一样,重新来过等乐观非阻塞策略,这些策略已经脱离我们说的“锁”的概念,而是解决并发带来数据不一致的另外途径,我们这里只谈JVM层面“锁”的抽象架构,关于其它并发解决策略,在后面 多线程编程​中会更进一步介绍,大家可以跳跃阅读)!

JVM抽象架构中要求对每一个对象在运行期加一个锁标识,也就是无论我们是用方法标注同步还是用同步块,其直接的体现就是给共享对象加锁或者等待对象解锁。这种加锁和等待解锁带来的问题就是线程的等待,甚至造成你等我,我等你的死锁。

从“锁”的基本工作原理和带来问题出发,建议大家在使用同步(synchronized)的时候注意:
□ 注意对需要同步的共享数据的读和写保持对应。不要出现在非同步调用中读取同步调用中写的对象数据,反之也要注意。
□ 注意尽量让需要同步的共享数据集中和少,并且不要开放直接对共享数据的非同步读或写的入口,把他们控制在同步的范围内。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值