Java 虚拟机
注:栈内存、堆内存诊断都没学
概念
定义
java二进制字节码的运行环境
好处
一次编写,到处运行
- 消除了字节码和底层操作系统之间的的差异
- 对外提供了一个一致的运行环境
- jvm可以用解释的方法来执行二进制字节码来达到代码的平台无关性
自动内存管理,垃圾回收功能
数组下标越界越界检查
- 不会覆盖其他代码的内存
多态
- 提升扩展性
- 使用虚方法表的方式实现
概念对比
JVM: Java虚拟机
JRE: Java的运行时环境 <= JVM + 基础类库
JDK: JRE基础上加上一些javac,javap,内存监测工具什么的 <= JVM + 基础类库 + 编译工具
流程
先通过类加载器加载到JVM内存结构
JVM内存结构包括:
方法区
: 存放类堆
:存放类的实例对象虚拟机栈
:实例对象调用方法时用到程序计数器
:实例对象调用方法时用到本地方法栈
:实例对象调用方法时用到
接下来到了执行引擎:
- 方法执行时,每行代码由
解释器
进行逐行执行 - 方法内的热点代码,也就是被频繁调用的代码,会被
即时编译器
进行编译。
可以竟是看做为优化代码
GC垃圾回收
将堆
里面不再被引用的模块进行垃圾回收
还有一些是Java代码无法实现,需要调用底层代码的功能:
- 通过
本地方法接口
调用操作系统的一些功能方法
内存结构
程序计数器
引言
JVM指令 => 解释器 => 机器码 => CPU
- Java源代码经过编译,成为
二进制字节码(JVM指令)
,所有平台下都是一致的 - 接下来每一条
JVM指令
都被解释器
(java虚拟机执行引擎的一个组件)解释成一条机器码
- CPU拿着
机器码
执行命令
CPU只认机器码
作用
程序计数器负责在一些JVM指令的执行过程中,记住下一条JVM指令的执行地址
当JVM指令最后变成机器码被CPU执行后,解释器就去程序计数器找要执行的指令地址
在物理上程序计数器是通过一个叫寄存器的东西实现的,程序计数器是对寄存器的一些屏蔽和抽象
寄存器是整个CPU读取速度最快的单元(因为读取非常频繁)
使用Java虚拟机在手机的时候,就将CPU的寄存器作为程序计数器来存储地址
特点
- 线程私有
在多个线程运行的时候,CPU会有一个调度器组件来给每个线程分配时间片
如果线程1的代码在时间片内没有执行完,线程1的代码就会被暂存,CPU资源切换到线程2
线程2的时间片用完了再切换
在线程切换的改成中,如果要记住下一条指令执行到哪里了,就要用到程序计数器
程序计数器现在就属于被暂存的线程
每个线程都有自己的程序计数器()因为各自执行的地址是不一样的
- JVM规范中唯一一个不会存在内存溢出的区
虚拟机栈
作用
- 每个线程运行时所需要的内存,称为虚拟机栈
一个栈内由多个栈帧组成
一个栈帧对应一次方法的调用
线程执行代码,代码由多个方法组成,每个方法运行需要的内存就是栈帧
栈帧的内存存着方法参数,局部变量,返回地址
方法1被调用,分配栈帧1,栈帧1入栈;方法1调用方法2,分配栈帧2,栈帧2入栈…方法2执行完,栈帧2出栈。出栈就是释放内存
- 每个栈由多个栈帧组成,对应着每次方法调用时所占用的内存
- 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法
注意点
出栈自动回收内存,不需要用到GC,GC是堆用的
栈内存Linux、macOS、Oracle Solaris默认1m
栈内存不是越大越好,因为物理内存是固定的,单个栈内存大了,可以容纳的线程数量就少了
局部变量是线程安全的,因为俩线程调用一个方法,各自都有各自的局部变量
但是全局变量(
static
)对各个方法是共享的。静态变量值的改变发生在方法调用完毕之后。当方法内部进行静态变量的修改时,实际上只是修改了该方法中对静态变量的局部变量,这并不会影响在内存中存储的静态变量值。当方法执行完毕后,再次访问该静态变量时,才能看到被更改的值。因此,静态变量值的改变情况都发生在方法调用完毕之后。判断方法内局部变量是否线程安全:
- 没有进出:不是通过参数引用进来,没有return出去
- 基本类型当然是线程安全的
栈内存溢出的场景
- 栈帧过多,只入不出(错误的递归调用)
比方说一个部门类和员工类,部门类有一个员工类的List,员工类有一个自己所属部门的属性
转换成json时便会出现循环依赖
此时可以在员工类的部门字段上加上@JsonIgnore
来忽略这个属性
- 栈帧过大(理论上才会存在?)
本地方法栈
分配给不是由Java编写的代码
堆
作用
通过new关键字,创建对象都会使用堆内存
特点
- 它是线程共享的,堆中对象都需要考虑线程安全的问题
- 有垃圾回收机制
堆内存溢出
- 集合内元素在集合作用范围内无法被垃圾回收,如果元素过多过大会导致堆内存溢出
- 垃圾回收后,内存占用仍然很高
方法区
存储类的信息
内存溢出
- 1.8以前会导致永久代内存溢出
- 1.8之后会导致元空间内存溢出