JVM 一

微信图片_20221110153740

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
2
3
4
int a  =10 ; //1    int a ; a = 10 ;
int b ;//2
b = 20 ;//3
int c = a * b ;//4
1
2
3
4
5
//2 3 1 4
int b ;
b = 20 ;
int a =10 ;
int c = a * b ;

volatile防止重排序问题

  • 在volatile写操作前,插入StoreStore屏障

  • 在volatile写操作后,插入StoreLoad屏障

  • 在volatile读操作后,插入LoadLoad屏障

  • 在volatile读操作后,插入LoadStore屏障

volatile原子性和线程安全性问题

volatile不保证线程的原子性也不保证线程的安全性

想保证原子性/线程安全,可以使用原子包java.util.cocurrent.aotmic中的类,该类能够保证原子性的核心,是因为提供了compareAndSet()方法,该方法提供了 cas算法(无锁算法)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class TestVolatile {

// static volatile int number = 0;

static AtomicInteger number = new AtomicInteger(0);

public static void main(String[] args) throws InterruptedException {

for (int i = 0; i < 100; i++) {
new Thread(() ->{
for (int i1 = 0; i1 < 10000; i1++) {
// number++;
number.incrementAndGet();
}
}).start();
}

Thread.sleep(1000);
System.out.println(number);
}
}

JVM运行时内存

程序计数器

行号指示器,指向当前线程所执行的字节码指令的地址,大白话:class文件中的行号

JVM运行时内存

  • 一般情况下,程序计数器是行号;但如果正在执行的方法是native方法,则程序计数器的值undefined

  • 程序计数器 是唯一一个不会产生 内存溢出 的区域

  • goto的本质就是改变的 程序计数器的值

虚拟机栈

描述方法执行的内存模型

虚拟机栈

方法在执行的同时,会在虚拟机栈中创建一个栈帧,栈帧中包含方法的局部变量表,操作数栈、动态链接、方法出口信息等

注意

当方法太多时,就可能发生 栈溢出异常StackOverflowError,或者内存溢出异常OutOfMemoryError

1
2
3
public static void main(String[] args) {
main(new String[]{"小博"});
}

本地方法栈

原理和结构与虚拟机栈一致,不同的是虚拟机栈中存放的是jdk或我们自己编写的方法,而本地方法栈调用的是操作系统底层的方法

局部变量表AND操作数栈

局部变量表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
2
3
4
5
6
7
8
9
10
11
public class Test {

static {
System.out.println("初始化了");
}

public static void main(String[] args) {
new Test();
new Test();
}
}
1
2
运行结果:
初始化了
  • 访问类/接口的 静态成员(属性、方法)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class A {

static {
System.out.println("初始化了");
}

public static void method(){
System.out.println("method 初始化了");
}

public static void main(String[] args) {
A.method();
}
}
1
2
3
运行结果:
初始化了
method 初始化了

main()本身也是一个静态方法,main()的所在类 也会在执行被初始化

如果成员变量既是static,又是final ,即常量,则不会被初始化

上边的这一种情况中,如果常量的值 是一个随机值,则会被初始化 (为了安全)

  • 使用Class.forName(“”)执行反射时使用的类
1
2
3
4
public static void main(String[] args) throws ClassNotFoundException {
A.method();
Class.forName("com.dream.xiaobo.initiative.Test");
}
  • 初始化一个子类时,该子类的父类也会被初始化
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Father {

static {
System.out.println("我是爸爸");
}
}

class Son extends Father{

public static void main(String[] args) {
new Son();
}
}
  • 动态语言在执行所涉及的类 也会被初始化(动态代理)

被动使用

除了主动以外,其他都是被动使用

1
2
3
4
5
6
7
8
9
10
11
12
public class Test {

public static void main(String[] args) {
B[] bs = new B[10];
}
}

class B{
static {
System.out.println("BBB");
}
}

不属于主动使用类,因此不会被初始化

助记符

反编译

编译的是class文件

javap -c class文件名

编译过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class C {
public static void main(String[] args) {
System.out.println(Test.i);
System.out.println(Test.s);
System.out.println(Test.l);
System.out.println(Test.d);
System.out.println(Test.f);
System.out.println(Test.b);
System.out.println(Test.bl);
System.out.println(Test.c);
System.out.println(Test.string);
System.out.println(Test.test);
System.out.println(Test.array);
}
}
class Test{
public static int i = 10;
public static short s = 10;
public static long l = 10;
public static double d = 10.0;
public static float f = 11.1f;
public static byte b = 10;
public static boolean bl = true;
public static char c = 'c';
public static String string = "xiaobo";
public static C test = new C();
public static C[] array = new C[3];
}
  • 反编译结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
PS D:\Users\86158\jvm\volatile\out\production\volatile\com\dream\xiaobo\compile> javap -c C
警告: 文件 .\C.class 不包含类 C
Compiled from "C.java"
public class com.dream.xiaobo.compile.C {
public com.dream.xiaobo.compile.C();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return

public static void main(java.lang.String[]);
Code:
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: getstatic #3 // Field com/dream/xiaobo/compile/Test.i:I
6: invokevirtual #4 // Method java/io/PrintStream.println:(I)V
9: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
12: getstatic #5 // Field com/dream/xiaobo/compile/Test.s:S
15: invokevirtual #4 // Method java/io/PrintStream.println:(I)V
18: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
21: getstatic #6 // Field com/dream/xiaobo/compile/Test.l:J
24: invokevirtual #7 // Method java/io/PrintStream.println:(J)V
27: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
30: getstatic #8 // Field com/dream/xiaobo/compile/Test.d:D
33: invokevirtual #9 // Method java/io/PrintStream.println:(D)V
36: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
39: getstatic #10 // Field com/dream/xiaobo/compile/Test.f:F
42: invokevirtual #11 // Method java/io/PrintStream.println:(F)V
45: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
48: getstatic #12 // Field com/dream/xiaobo/compile/Test.b:B
51: invokevirtual #4 // Method java/io/PrintStream.println:(I)V
54: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
57: getstatic #13 // Field com/dream/xiaobo/compile/Test.bl:Z
60: invokevirtual #14 // Method java/io/PrintStream.println:(Z)V
63: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
66: getstatic #15 // Field com/dream/xiaobo/compile/Test.c:C
69: invokevirtual #16 // Method java/io/PrintStream.println:(C)V
72: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
75: getstatic #17 // Field com/dream/xiaobo/compile/Test.string:Ljava/lang/String;
78: invokevirtual #18 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
81: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
84: getstatic #19 // Field com/dream/xiaobo/compile/Test.test:Lcom/dream/xiaobo/compile/C;
87: invokevirtual #20 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
90: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
93: getstatic #21 // Field com/dream/xiaobo/compile/Test.array:[Lcom/dream/xiaobo/compile/C;
96: invokevirtual #20 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
99: return
}

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 国际 转载请保留原文链接及作者。