阿里P8的这点Java底层?-JVM内存模型

JVM内存模型

内存模型图

在这里插入图片描述

四个概念

  • class文件
    class文件就是我们说的字节码文件,它是由.java、.groovy等文件通过编译器解析产出的文件。class文件才是jvm要使用的文件。
  • class content
    class content就是class文件读取到内存中的那片内存区域
  • Class对象
    类加载器将class content里的内容逐个字节读出,按照jvm规范解析成Class对象,放入方法区中。java.lang.String.class 就是一个Class对象
  • 对象
    由Class对象实例化出来的具体实例

方法区

方法区

方法区是规范,永久代和元空间都是方法区的具体实现。

  • 永久代
    jdk8以前的方法区实现,处于堆中,因此会产生OOM,会触发GC
  • 元空间
    jdk8以后的方法区实现,处于本地内存中

本地内存?也称为直接内存、操作系统内存、native memory

jdk8后,方法区的实现为什么用元空间取代了永久代??

  1. 硬件的发展
    32bit机时代,内存最大支持为4G,其中内核层使用2G,应用层使用2G。说白了就是内存不够,若将方法区实现在本地内存中时,当运行时创建大量Class时如cglib,方法区会撑爆本地内存,这会影响系统中其他程序的运行。
    64bit机器,理想情况下内存可以扩展到2^48 (为什么不是 2^64 呢?)大小即256T,此时本地内存容量大增,即有条件将方法区搬到本地内存中

64bit操作系统,其中有16位是保留位,内存寻址空间只使用了48位,因此64位操作系统,最大支持到2^48内存大小
在这里插入图片描述

  1. 降低jvm的GC复杂度
    永久代中有字符常量和Class对象等,其GC算法复杂难度较大。
    替换成元空间后,元空间放在操作系统内存中,由操作系统进行回收,字符串移到了堆中。

元空间

默认大小

最小 20.75M、最大 256T 无限

*元空间调优

  1. 最小、最大值设置成一样,防止内存扩展导致内存抖动
  2. 设置成物理内存的 1/32 (经验值),具体按实际确定。 (相关工具:arthas、visualvm)
  3. 元空间大小一般按占用大小预留20~30%的空间,高并发防止?
    在这里插入图片描述

程序计数器

JVM的程序计数器是程序模拟出来的,和操作系统的程序计数器(EIP)不一样
在这里插入图片描述
上图中绿框中的就是java中的程序计数,如上图当JVM 处理new操作时,会将3置入程序计数器(是3还是0???

虚拟机栈

虚拟机栈、数据结构:栈、栈帧

  1. 栈,是数据结构理论
  2. 虚拟机栈是数据结构栈的一个实现,下文中出现的栈若无特殊说明那指的就是虚拟机栈
  3. 栈帧是虚拟机栈中一个单元,存储执行方法所需要的信息

虚拟机栈

栈是线程私有的,创建线程时就会创建一个栈,栈的生命周期和线程同步;
栈中的变量不需要JVM进行GC回收,而是随着作用域的结束而释放,如一个方法执行完就会清理对应栈帧;
栈的大小和深度是固定的,在编译期就确定了。

栈帧

栈包含多个栈帧,栈帧由方法调用创建,方法结束释放;
调用方法时,创建栈帧并压入栈顶。方法结束时栈会弹出栈顶栈帧。该栈帧会被jvm释放;
栈只会操作栈顶栈帧,不会同时操作多个栈帧;
栈帧组成:局部变量表、操作数栈、动态链接、返回地址

动态链接 ????

动态链接,存放的是这个方法在方法区的内存地址

返回地址

恢复现场应该表达的更准确,a方法内调用b方法结束后,需要返回,这里要分两种情况,方法调用正常完成、方法调用异常完成。

  • 方法调用正常完成
    b方法正常完成(遇到方法返回字节码指令)时,当前栈帧承担着恢复调用者a状态的责任,恢复a的局部变量表和操作数栈,正确递增程序计数器到a调用b的下一步动作,以跳过刚才执行的方法调用指令等。b的执行结果压入a栈帧的操作数栈后,a会正常执行。(那返回地址(恢复现场)到底是个什么结构,存的是什么内容,怎么就能恢复现场了??
  • 方法调用异常
    从异常抛出的地方转换至处理异常的地方。(细节是什么样的?

一个方法执行完,JVM需要做哪些事情(上图标识2的那条线)??

  1. 恢复局部变量表指针(当前线程的局部表开始指针)
  2. 恢复操作数栈当前指针 (当前线程的操作数栈指针)
  3. 恢复程序计数器 (按上图,程序计数器从8恢复到上个方法调用处即12,5->12)
  4. 如果方法有返回地址,需要返回(???)
  5. 清理栈帧(程序计数器判断方法执行完成,调用清理栈帧的函数)(栈帧???)

局部变量表

每个栈帧内都包含一组局部变量列表。局部变量表长度从编译期就确定,存储在class文件的二进制表示中;
当调用类方法时,参数会依次传递到局部变量表从0开始的连续位置上;
当调用实例方法时,第0个局部变量永远存储的都是该实例方法所在对象的引用,即this,后续参数传递到1开始的连续位置。

知识点:非静态方法及构造方法中,局部变量表index=0的位置存放的都是this

操作数栈

顾名思义,操作数栈就是供字节码指令操作的数据栈。栈帧刚创建时,操作数栈是空的,一些字节码指令从局部变量表或对象实例的字段中复制常量或变量到操作数栈中,也有一些字节码指令可以从操作数栈中取走数据、操作数据或将操作结果重新入栈。调用其他方法时,操作数栈也用于准备调用方法的参数及接收方法的返回结果

》通过一个简单案例字节码执行过程分析,了解局部变量表和操作数栈的流转情况

java和字节码内容

在这里插入图片描述
在这里插入图片描述

本例中,局部变量表状态

在这里插入图片描述

字节码分析

  • main方法中实例化Hzw对象,栈帧变化过程
    在这里插入图片描述
  • main方法中调用hzw对象add方法,栈帧变化过程
    结合上图和字节码指令手册,调用add和add执行的过程应该很好理解了(懒,不想画了,后面画了再更吧)

在这里插入图片描述

  • 堆,新生代和老年代默认分配比例是1/3
    -Xms30M、-Xmx30M、-Xmn10M把Java堆大小设置为30MB,不可扩展(防止内存抖动)。其中10M分配给新生代,另外20M分配给老生代。

  • 新生代分Eden区、From区、To区,比例8/1/1
    -XX:SurvivorRatio=8来分配新生代各区的比例,设置为8,表示eden与一个survivor区的空间比例为8:1

  • 堆的默认大小,最大是物理内存的1/64,最大是1/4

  • From区和To区会相互转换,???

  • 内存担保机制 JVM内存分配担保机制
    -XX:+HandlePromotionFailure允许新生代收集担保,JDK1.5及以前内存担保默认关闭,1.6以后默认开启

内存担保是在JVM在内存分配的时候,新生代内存不足时,把新生代的存活的对象搬到老生代,然后新生代腾出来的空间用于为分配给最新的对象。这里老生代是担保人。在不同的GC机制下,也就是不同垃圾回收器组合下,担保机制也略有不同。在Serial+Serial Old的情况下,发现放不下就直接启动担保机制;在Parallel Scavenge+Serial Old的情况下,却是先要去判断一下要分配的内存是不是>=Eden区大小的一半,如果是那么直接把该对象放入老生代,否则才会启动担保机制

你可能感兴趣的:(java,java,jvm,栈)