Java第四篇:jvm,jmm相关知识

1 jvm虚拟机由3部分组成,类加载子系统、运行时数据区(内存模型)、字节码执行引擎

2 运行时数据区即内存模型包括堆、方法区(元空间)、栈(线程)、本地方法栈、程序计数器

3 栈组成部分为:

  1. 局部变量表-存储方法参数,内部使用的变量
  2. 操作数栈-在变量进行存储时,需要进行入栈和出栈
  3. 动态连接-引用类型的指针
  4. 方法出口-方法的返回

4 栈帧是一个方法在栈空间内申请的空间,每块栈帧中存放该方法中的局部变量等数据

5 javap -c 反汇编字节码文件,让字节码文件更加可读。例如反汇编之后的文件中,有些指令码iconst_1便是将int型常量1压入操作数栈,istore_1将int类型值存入局部变量1中,这个操作会在局部变量表中生成一个变量名的空间,并将int值放在这里面去。iload_1将局部变量1中装载int类型值,这里又会把存在局部变量表中对应变量名中的数据重新加载到操作数栈中。iadd执行int类型的加法,会将之前压入操作数栈的int值出栈,并将结果值压入栈。bipush 10意思就是将10 这个int型常量压入操作数栈中,ireturn,返回操作数栈中第一个值。

6 字节码执行引擎控制每个线程内部区域的程序计数器记录当前运行代码的行号。

7 程序计数器的作用是当线程被切换的时候,回来运行时从记录的位置开始运行。

8 方法出口记录调用该方法时主方法当前的行号

9 方法中如果new一个对象,对象存在堆中,但是该方法的栈帧的局部变量表中存放一个指针,指向堆中对象的地址。

10 方法区存放常量、静态变量、类信息(.class文件的信息)

11 本地方法栈是栈调用本地方法时分配的空间,本地方法可能是C程序。

12 能作为GC ROOT跟结点的由线程的本地变量、静态变量、本地方法栈变量

13 每次年轻代的minor gc都会给对象的分代年龄+1,分代年龄达到15会被移到老年代

14 jdk自带的工具jvisualvm能够查看程序自运行时内存的使用情况 需要安装一个插件visual Gc

15 GC时会出发stop-to-world事件,造成线程阻塞。

16 jvm调优的目的时为了减少stw事件的次数和持续时间,由于minor gc只会清空年轻代的垃圾,占用内存空间较小,而full gc会清空整个堆的垃圾,所以优先调优full gc,减少full gc的次数,一次full gc的时间越短,stw的时间就越短。

17 多核并发缓存架构

18 jmm(java内存模型)与CPU模型相似。

19 多个线程读取同一个共享变量,是在自己线程的工作内存中拷贝一份主内存的备份,线程是读取工作内存中的变量。而不同的线程对变量的改变也只是在工作内存中的共享变量,其他线程无法感知改变。如果要想变量在线程之间可见,必须用volatile关键字修饰。

20 java内存中线程的工作内存和主内存的交互是由java虚拟机定义了如下的8种操作来完成的

  • lock(锁定):作用于主内存的变量,一个变量在同一时间只能一个线程锁定,该操作表示这条线成独占这个变量
  • unlock(解锁):作用于主内存的变量,表示这个变量的状态由处于锁定状态被释放,这样其他线程才能对该变量进行锁定
  • read(读取):作用于主内存变量,表示把一个主内存变量的值传输到线程的工作内存,以便随后的load操作使用
  • load(载入):作用于线程的工作内存的变量,表示把read操作从主内存中读取的变量的值放到工作内存的变量副本中(副本是相对于主内存的变量而言的)
  • use(使用):作用于线程的工作内存中的变量,表示把工作内存中的一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时就会执行该操作
  • assign(赋值):作用于线程的工作内存的变量,表示把执行引擎返回的结果赋值给工作内存中的变量,每当虚拟机遇到一个给变量赋值的字节码指令时就会执行该操作
  • store(存储):作用于线程的工作内存中的变量,把工作内存中的一个变量的值传递给主内存,以便随后的write操作使用
  • write(写入):作用于主内存的变量,把store操作从工作内存中得到的变量的值放入主内存的变量中

sPS:read和load,store和write必须成对出现。 线程在工作内存中修改了变量的值必须同步回主内存,也就是要执行assign操作,但是如果变量没有修改,不能同步回主内存。变量只能在主内存中产生。一个变量只能被一个线程加锁,但是能被同一个线程加锁多次,但是释放的是否必须同样释放多次。

例如:一个加了volatile关键字修饰的静态变量flag,被线程一个用来当作执行循环的条件,而线程二中对flag值进行修改。

整个原子操作流程如下,read主内存的flag值(主取),然后load主内存的flag值寸到线程一的工作内存的变量中中(工存),然后线程一就可以通过use来使用变量(工用),同理线程二也是这样的操作将变量的值放到它的工作内存中,使用的时候通过use操作读取工作内存中的值,线程二要改变flag的值,就要通过assign进行赋值操作(工赋)。线程二通过store操作将flag的新值重新加载到主内存中(工取),然后通过write操作将新值写入变量中(主存)。但此时的线程一还是读取自己工作内存中的旧值。而load和unlock是用于线程对变量进行独享的操作。

21 volatile关键字是如何保证线程之间的数据的可见性的?底层是通过系统总线(连接CPU和主内存)的MESI缓存一致性协议,CPU的总线嗅探机制会监听其他CPU通过总线传输给主内存的数据是否发生改变,如果改变了,CPU就会将自己工作内存中的变量值设为失效,即为空,一旦该CPU中的线程要用这个变量值的时候发现为空,就会重新去主内存中读取新的值。

而volatile是通过汇编lock前缀命令,锁定这块内存区域的缓存(缓存行锁定)并写回主内存。

lock指令:会将当前处理器缓存行的数据立即写回到主内存,然后这个lock操作通过上面的MESI协议引起其他CPU缓存该内存地址的数据无效。

22 volatial的底层源码是C语言

23 有一个问题,因为线程二在写回flag新值的时候是两步操作,一个store和write,而在store的时候就会被其他CPU监测到flag变量的改变就会设置自己工作内存中变量值失效,如果此时线程一读取主内存的变量的操作在write之前,就会造成哪怕CPU监测到值变化了,但还是读取了旧值。那该如何解决呢?volatile会将lock指令在store之前执行lock操作,这样再经过store出发线程一重新读取主内存的数据时,线程一发现lock的锁还没放开,就会等待线程二的write操作后unlock掉锁之后才会进行读取数据。

24 并发编程的三大特征:可见性、原子性、有序性。volatile保证可见性和有序性,但不保证原子性,保证原子性需要借助synchronized这样的锁机制

25 在多个线程中频繁对同一个变量的进行修改值的操作,就会造成某些操作就会被覆盖,例如面试题的多个线程进行count++的例子。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值