JVM 一
JVM
java virtua Machine
JDK
jdk中包含了jvm
和 屏蔽操作系统差异的组件
- jvm
jvm在各个操作系统上是一致的
- 屏蔽操作系统差异组件
在各个操作系统上各不相同,就像Windows、Mac、Linux上安装Jdk需要下载不同的jdk
类的生命周期
类的加载–>连接–>初始化–>使用–>卸载
类的加载
查找并加载二进制数据,并且从硬盘上加载到jvm内存中
连接
验证
校验二进制数据(.class)是否正确
准备
在准备阶段,JVM中只有类,没有对象
首先static静态变量分配内存,并赋初始化默认值,之后(初始化阶段)再进行修改为赋值
- 初始化顺序
static –>非static –>构造方法
解析
把类中符号引用,转为直接引用
前期阶段,不知道类的具体内存地址,只能使用“com.xiaobo.dream.Student ”来替代Student类,“com.xiaobo.dream.Student ”就称为符号引用
解析阶段,JVM就可以将 “com.xiaobo.dream.Student ”映射成实际的内存地址,以会后就用内存地址来代替Student,这种称为直接引用
初始化
给static变量 赋予正确的值
使用
对象的初始化
对象的垃圾回收
对象的销毁
卸载
JVM结束生命周期时机
正常结束
异常结束/错误 Throwable –> Exception AND error
System.exit()
操作系统异常
JVM内存模型
JMM(Java Memoery Model) 定义变量(所有线程的共享变量, 不能是局部变量)的访问规则
JMM内存划分
主内存区
存放真实变量
工作内存区
主内存中变量的副本,供各个线程所使用
线程访问问题
各个线程只能访问自己私有的工作内存,不能访问其他线程的工作内存,也不能访问主内存
不同线程之间,可以通过主内存 间接的访问其他线程的工作内存
不同线程之间交互数据
Lock
:将主内存中的变量,表示为一条线程的独占状态
Read
:将主内存中的变量,读取到工作内存中
Load
:将读取的变量拷贝到变量副本中
Use
:把工作内存中的变量副本,传递给线程去使用
Assign
:把线程正在使用的变量,传递给工作内存中的变量副本中
Store
:将工作内存中变量副本的值,传递到主内存中
Write
:将变量副本作为一个主内存中的变量进行存储
Unlock
:解决线程的独占状态
以上动作必须是原子性的
原子性
一个操作是不可中断的。即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰
读写半个long/double数据问题
商用JVM解决了此问题
自己解决可以通过volatile避免读写半个数据的问题
Volatile
JVM的一个轻量级的同步机制
Volatile作用
防止JVM对非原子性协议进行的误操作(读取半个数据)
可以使变量对所有的线程立即可见(某一个线程如果修改了 工作内存中的变量副本,那么加上volatile 之后,该变量就会立刻同步到其他线程的工作内存中)
禁止指令的“重排序”优化
原子性
number = 1
非原子性
i++
首先从主内存中拿到i,然后进行 i + 1,然后 i= i + 1;
重排序
排序的对象就是原子性操作,目的是为了提高执行效率,优化
不会影响单线程
的执行结果
1 | int a =10 ; //1 int a ; a = 10 ; |
1 | //2 3 1 4 |
volatile防止重排序问题
在volatile写操作前,插入StoreStore屏障
在volatile写操作后,插入StoreLoad屏障
在volatile读操作后,插入LoadLoad屏障
在volatile读操作后,插入LoadStore屏障
volatile原子性和线程安全性问题
volatile不保证线程的原子性也不保证线程的安全性
想保证原子性/线程安全,可以使用原子包java.util.cocurrent.aotmic中的类,该类能够保证原子性的核心,是因为提供了compareAndSet()方法,该方法提供了 cas算法(无锁算法)
1 | public class TestVolatile { |
JVM运行时内存
程序计数器
行号指示器,指向当前线程所执行的字节码指令的地址,大白话:class文件中的行号
一般情况下,程序计数器是行号;但如果正在执行的方法是native方法,则程序计数器的值undefined
程序计数器 是唯一一个不会产生
内存溢出
的区域goto的本质就是改变的 程序计数器的值
虚拟机栈
描述方法执行的内存模型
方法在执行的同时,会在虚拟机栈中创建一个栈帧,栈帧中包含方法的局部变量表,操作数栈、动态链接、方法出口信息等
注意
当方法太多时,就可能发生 栈溢出异常StackOverflowError,或者内存溢出异常OutOfMemoryError
1 | public static void main(String[] args) { |
本地方法栈
原理和结构与虚拟机栈一致,不同的是虚拟机栈中存放的是jdk或我们自己编写的方法,而本地方法栈调用的是操作系统底层的方法
局部变量表AND操作数栈
动态链接
符号引用会在实际执行时转换为不同的地址,所以转换的行为会在每一次运行时都发生一次
堆
存放对象实例(数组、对象)
堆是jvm区域中最大的一块,在jvm启动时就已经创建完毕
GC主要管理的区域
堆本身是线程共享,但在堆内部可以划分出多个线程私有的缓冲区
堆允许物理空间不连续,只要逻辑连续即可
堆可以分 新生代、老生代 。大小比例,新生代:老生代= 1:2
新生代
新生代中 包含eden、s0、s1 = 8:1:1
新生代的使用率一般在90%。 在使用时,只能使用 一个eden和一块s区间(s0或s1),底层采用的是复制算法,为了避免碎片产生
新生代存放生命周期比较短的对象,小的对象
大部分对象都存在于新生代
新生代的回收频率高、效率高
老生代
存放在老生代中的对象周期较长,对象较大,对象的大小,可以通过参数设置
-XX:PretenureSizeThredshold
。一般而言,大对象一般是 集合、数组、字符串。生命周期:-XX:MaxTenuringThredshold
,使用的回收器 MajorGC\FullGC空间大
增长速度慢
频率低
新生代老生代年龄问题
- 新生代、老生代中年龄:MinorGC回收新生代中的对象。如果Eden区中的对象在一次回收后仍然存活,就会被转移到 s区中;之后,如果MinorGC再次回收,已经在s区中的对象仍然存活,则年龄+1。如果年龄增长一定的数字(默认16),则对象会被转移到 老生代中
在新生代中的对象,每经过一次MinorGC,有三种可能
从eden –>s区
已经在s区中,年龄+1
转移到老生代中
具体问题具体考虑
根据项目中 对象大小的数量,设置新生代或老生代的空间容量,从提高GC的性能
对象太多,会导致内存异常
虚拟机参数
-Xms128m
:JVM启动时的大小
-Xmn32m
:新生代大小
-Xmx128
:总大小
jvm总大小= 新生代 + 老生代
一般-Xms128m 和 -Xmx128相同
方法区
存放类的元数据(描述类的信息)、常量池、方法信息(方法数据、方法代码)
GC:类的元数据(描述类的信息)、常量池(存放编译期间产生的 字面量(“abc”)、符号引用)
内存异常OutOfMemoryError
内存溢出的异常OutOfMemoryError,除了虚拟机中的4个区域以外,还可能是直接内存。在NIO技术中会使用到直接内存
类的使用方式
- 类的初始化
JVM只会在首次主动使用
一个类/接口时,才会初始化它们
主动使用
- new 构造类的使用
1 | public class Test { |
1 | 运行结果: |
- 访问类/接口的 静态成员(属性、方法)
1 | public class A { |
1 | 运行结果: |
main()本身也是一个静态方法,main()的所在类 也会在执行被初始化
如果成员变量既是static,又是final ,即常量,则不会被初始化
上边的这一种情况中,如果常量的值 是一个随机值,则会被初始化 (为了安全)
- 使用Class.forName(“”)执行反射时使用的类
1 | public static void main(String[] args) throws ClassNotFoundException { |
- 初始化一个子类时,该子类的父类也会被初始化
1 | public class Father { |
- 动态语言在执行所涉及的类 也会被初始化(动态代理)
被动使用
除了主动以外,其他都是被动使用
1 | public class Test { |
不属于主动使用类,因此不会被初始化
助记符
反编译
编译的是class文件
javap -c class文件名
编译过程
1 | public class C { |
- 反编译结果
1 | PS D:\Users\86158\jvm\volatile\out\production\volatile\com\dream\xiaobo\compile> javap -c C |
aload_0
: 装载了一个引用类型
Invokespecial
: init,private,super.method():<init>存放的是初始化代码的位置
getstatic
:获取静态成员
bipush
:整数范围 -128 <–> 127之内 (8位带符号的整数),放到栈顶
sipush
: >127 (16个带符号的整数),放到栈顶
无论是定义int或short等,只要在 -128 –127以内 都是bipush,否则是sipush
特殊
:-1 –> 5不是bipush iconst_m1(-1) iconst_0 iconst_1 …. iconst_5
ldc
: int float String 常量 ,放到栈顶
ldc2_w
:long double常量,放到栈顶
正确的开始 微小的长进 然后持续 嘿 我是小博 带你一起看我目之所及的世界……
本文标题:JVM 一
发布时间:2022年11月10日 - 15:42
最后更新:2022年11月10日 - 15:42
原始链接:https://codexiaobo.github.io/posts/3370054281/
许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。