JVM虚拟机——内存结构

虚拟机的执行

虚拟机的执行方式有两种:解释执行和编译执行。

  • 解释执行:一边翻译成机器码一边执行。加载快。
  • 编译执行:将一个方法中的所有字节码全部翻译成机器码再执行。加载慢,但加载完成后执行效率高。

Hotspot使我们最常用的虚拟机,它采用的是先解释执行,但它有一个热点探测技术(通过执行计数器找到最有编译价值的代码,将它们编译成本地代码)。

未来的Java技术

  • 模块化:OSGI(动态化,模块化),微服务。
  • 混合语言:多种语言都可以运行在JVM中。
  • 多核并行:CPU从高频次转为多核心。JDK1.7引入Fork/Join,JDK1.8提出Lambda表达式(函数式变成天生就适合并行运行)。
  • 丰富语法:JDK1.5的自动装箱、泛型、动态注解等语法。JDK1.7的二进制原生支持。
  • 64位:虚拟机会完全过渡到64位,32位的JVM有4G的堆大小限制。
  • 更强的垃圾回收器:目前主流的是CMS和G1,JDK11引入ZGC(暂停时间不超过10毫秒,且不会随着堆的增加而增加,可以达到TB级别的回收,使用的主要技术是有色指针,加载屏障)。JDK12支持并发类卸载。JDK13将最大堆大小从4GB增加大16GB。

Java SE体系架构

  • JDK:Java开发工具包,包含JRE,以及开发所需要的编译器和调试工具等。
  • JRE:JavaSE的运行环境,提供类库,Java虚拟机和其他组件。
  • JVM:java虚拟机,负责平台硬件和操作系统无关性、编译执行代码(字节码)和平台安全性。

运行时数据区

是个抽象概念。内部实现依赖寄存器、高速缓存、主内存。
计算机的运行=指令+数据。指令用于执行方法,数据用于存储数据和对象。

线程私有区域

程序计数器

较小的内存区域,当前线程执行的字节码的行号指示器。各线程之间相互独立,互不影响。如果执行的是一个Java方法,计数器记录的是当前线程执行的字节码行号。如果执行的是一个Native方法,这个计数器的值为空。此区域是唯一一个不会发生OutOfMemoryError的区域。

虚拟机栈

:数据结构——先进后出。内存分配在栈上,变量出了作用域就会自动释放。
可能发生的异常:线程请求的栈深度大于虚拟机所允许的深度:StackOverflowError;JVM动态扩展时无法申请到足够的内存时:OutOfMemoryError。
虚拟机栈:每个线程私有,线程在运行时,执行每一个方法时都会打包成一个栈帧,存储了局部变量表、操作数栈、动态链接、方法出口等信息,然后放入栈。执行的当前方法就是虚拟机栈顶的栈帧。方法的执行对应着入栈和出栈的过程。栈帧的数据在编译后就已经确定了,写在了字节码文件的code属性中(属性表集合)。
栈帧:当前栈帧有效,一个方法可能会有很长的调用链,意味着很多方法会被压入栈,但在线程的执行的某个时间点只有位于栈顶的栈帧才是有效的,该栈帧称为“当前栈帧”,与这个栈帧相关联的方法称为“当前方法”。

  • 局部变量表:局部变量表中的容量以变量槽(Variable Slot)为最小单位。一个Slot存放32位数据类型数据,如果是64位的数据类型,则以高位对齐的方式为其分配两个Slot空间。java中明确的64位数据类型只有long和double两种。总的来说:是存储局部变量的表,主要存放八大基础数据类型,如果是存储局部的一些对象,只需要存放一个对象的地址引用即可
  • 操作数栈:操作数栈也常称为操作栈,是一个先进后出的栈。和局部变量表一样,操作数栈每一个元素可以使任意java数据类型,32位占的栈容量为1,64位占2。存放方法执行的操作数。一个方法刚开始操作数栈是空的,运行方法时会一直进行入栈出栈操作。例如:进行算术运算是通过操作数栈进行的,调用其他方法是通过操作数栈来传递参数的。Java虚拟机的执行引擎称为”基于栈的执行引擎“,栈就指的是操作数栈。
    PS:虚拟机中每两个栈都是相互独立的,但在进行方法调用时会进行参数传递,虚拟机做了一些优化:令两个栈帧出现一部分重叠,让下面栈帧的一部分操作数栈与上面栈帧的部分局部变量表重叠在一起,就可以共用一部分数据无需进行额外的参数复制传递,称为数据重叠优化
  • 动态连接:执行方法时我们需要知道执行的是哪一个方法,栈帧中会持有一个引用(符号引用),指向某一个具体方法。在符号引用转为直接引用的解析时机,解析分两大类:静态解析(符号引用在类加载阶段或者第一次使用的时候就直接转换成直接引用)和动态解析(符号引用在每次运行期间转换为直接引用,每次运行都重新转换)。
  • 方法返回地址:方法的退出方式包括: 正常退出和异常退出。执行完当前栈帧的方法,调用程序计数器中的地址作为返回,保存在当前栈帧中;异常退出时,返回地址是通过异常处理器来确定的,栈帧中不会保存这部分信息。
    PS:方法退出时会做的操作:恢复上层的局部变量表、操作数栈,把当前方法的返回值压入调用者栈帧的操作数栈中,使用保存的返回地址调整PC计数器的值,当前栈帧出栈,随后,执行PC计数器指向的指令。

本地方法栈

各个虚拟机自由实现,本地方法栈Native方法调用JNI到了底层的C/C++。

线程共享区域

方法区/永久代

用于存储已经被虚拟机加载的类信息、常量、静态变量等数据。
类信息:类的完整有效名、返回值类型、修饰符、变量名、方法名、方法代码、这个类的直接父类的完整有效名、类的直接接口的一个有序列表。

几户所有的对象都分配在这里,是垃圾回收的主要区域。

运行时常量池

符号引用

这只是一个概念,在一个Java类A被编译成一个Class文件时,如果A类引用了B类,而在编译过程中A类并不知道B类的实际内存地址,只能使用符号引用来代替。
在类加载器加载A类时,此时可以通过虚拟机获得B类的实际内存地址,就可以将符号引用替换为B类的实际内存地址了。

字面量

文本字符串、八种基本数据类型、声明为final的常量。

各版本之间的变化

JDK1.6运行时常量池在方法区中,JDK1.7运行时常量池在堆中。

直接内存

使用Native函数库直接分配额外内存(NIO),并不是JVM运行时数据区的一部分,但是会被频繁使用。使用直接内存避免了在Java堆和Native堆中来回复制数据,能够提高效率。

栈溢出

java.lang.StackOverflowError,一般很难出现,出现了可能是无线递归。
虚拟机栈的启示:方法的执行因为要打包成栈帧,所以天生比实现同样功能的循环慢,所以树的遍历算法中:递归和非递归(循环实现)都有存在的意义,递归代码简洁,非递归代码复杂但更快。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值