jvm加载类的过程
一句话解释清楚整个过程
- 先加载 class 进内存
- 对 class信息内容进行语法校验,判断是否有错误
- 对静态变量初始化
- 调用构造器 初始化实例
加载类的方式
加载类的方式有以下几种:
1)从本地系统直接加载
2)通过网络下载.class文件
3)从zip,jar等归档文件中加载.class文件
4)从专有数据库中提取.class文件
5)将Java源文件动态编译为.class文件(服务器)
1、invokeinterface:调用接口中的方法,实际上是在运行期决定的,决定到底调用实现该接口的那个对象的特定方法。4
2、invokestatic : 调用静态方法
3、invokespecial: 调用自己的私有方法,构造方法()以及父类的方法。
4、invokevirtual: 调用虚方法,运行期动态查找的过程。
5、invokedynamic: 动态调用方法。
————————————————
版权声明:本文为CSDN博主「魔鬼_」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/wzq6578702/article/details/82711236
初始化的过程
虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。
Java语言中类的加载、连接和初始化过程都是在程序运行期间完成的,领Java具备高度的灵活性。
类加载的过程:加载、连接(验证、准备、解析)、初始化。
– 加载:通过一个类的名字获取此类的二进制字节流(PS:不限于从文件中读取);将这个字节流代表的静态存储结构转换为方法区的运行时结构(由具体的虚拟机自己定义);在内存中生成一个java.lang.Class对象,作为方法区这个类的各种数据结构的访问入口。
– 验证:文件格式验证、元数据验证(语义分析,类与类的继承关系等)、字节码验证(数据流和控制流分析)、符号引用验证(对类自身以外的信息进行匹配校验)
– 准备:正式为类变量分配内存并设置初始值,这里类变量指的是被static修饰的变量。例外:如果类字段是常量,则在这里会被初始化为表达式指定的值。
– 解析:将常量池内的符号引用替换为直接引用。符号引用:类似于OS中的逻辑地址;直接引用:类似于OS中的物理地址,直接指向目标的指针、相对偏移量或一个能间接定位到目标的句柄。
– 初始化:真正开始执行类中定义的Java程序代码;初始化用于执行Java类的构造方法。类初始化的过程是不可逆的,如果中间一步出错,则无法执行下一步。
双亲委派机制
- bootstrap class loader 是 加载 c++ 编写的java核心库
- ext 加载 jre, javax 等类
- app 加载 user.dir, class 等
- custom: 用户自己定义加载方式
JAVA 语言怎么实现跨平台
- jvm 将java代码编译为.class 文件的中间代码文件, 通过 jvm 来解释执行 这个 字节码, 将字节码翻译为适配操作系统底层的机器语言, jvm 是不跨平台的, 每个操作系统要安装对应版本的jvm 进行语言解释。
什么是 OOM , 哪里会出现 OOM
out of memory
- 堆内存异常 , java.lang.OutOfMemoryError, java heap space
- 栈内存溢出, stackOverflowError
- 永久代溢出, java.lang.OutOfMemoryError: Permgen space
内存分布原理
打印 classLayout
1
2
3
4
5
|
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.10</version>
</dependency>
|
参考博客
参考博客2
- 查看对象内部信息: ClassLayout.parseInstance(obj).toPrintable()
- 查看对象外部信息:包括引用的对象:GraphLayout.parseInstance(obj).toPrintable()
- 查看对象占用空间总大小:GraphLayout.parseInstance(obj).totalSize()
打印测试
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
|
import org.openjdk.jol.info.ClassLayout;
/**
* @Author lyr
* @create 2021/9/15 19:20
*/
public class Main {
static class User {
int p1, p2, p3;
long l1, l2, l3;
}
public static void main(String[] args) throws InterruptedException {
User user = new User();
System.out.println(ClassLayout.parseInstance(user));
}
}
/*
Main.User.p1 @12 (int, 4b)
Main.User.l1 @16 (long, 8b)
Main.User.l2 @24 (long, 8b)
Main.User.l3 @32 (long, 8b)
Main.User.p2 @40 (int, 4b)
Main.User.p3 @44 (int, 4b)
size = 48
*/
|
disruptor 缓存行伪共享原理解释
参考博客
缓存中的数据并不是独立的进行存储的,它的最小存储单位是缓存行,缓存行的大小是2的整数幂个字节,最常见的缓存行大小是 64 字节。CPU 为了执行的高效,会在读取某个对象时,从内存上加载 64 的整数倍的长度,来补齐缓存行。
以 Java 的 long 类型为例,它是 8 个字节,假设我们存在一个长度为 8 的 long 数组 arr,那么CPU 在读取 arr[0] 时,首先查询缓存,缓存没有命中,缓存就会去内存中加载。由于缓存的最小存储单位是缓存行,64 字节,且数组的内存地址是连续的,则将 arr[0] 到 arr[7] 加载到缓存中。后续 CPU 查询 arr[6] 时候也可以直接命中缓存。
注意: 数组 0-7 有8个 long 的 数被存到同一个缓存行里面
这就是缓存行共享,听起来不错,但是一旦牵扯到了写入操作就不妙了。
假设 Core-1 想要更新 arr[7] 的值,根据 CPU 的 MESI 协议,那么它所属的缓存行就会被标记为失效。因为它需要告诉其他的 Core,这个 arr[7] 的值已经被更新了,缓存已经不再准确了,你必须得重新去内存拉取。但是由于缓存的最小单元是缓存行,因此只能把 arr[7] 所在的一整行给标识为失效。
此时 Core-2 就会很郁闷了,刚刚还能够从缓存中读取到对象 bar,现在再读取却被告知缓存行失效,必须得去内存重新拉取,延缓了 Core-2 的执行效率。
这就是缓存伪共享问题,两个毫无关联的线程执行,一个线程却因为另一个线程的操作,导致缓存失效。这两个线程其实就是对同一缓存行产生了竞争,降低了并发性。
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
|
import org.openjdk.jol.info.ClassLayout;
/**
* @Author lyr
* @create 2021/9/15 19:20
*/
public class Main {
// 前:填充 56 字节
static class RhsPadding extends Value {
protected long p9, p10, p11, p12, p13, p14, p15;
}
static class Value extends LhsPadding {
protected volatile long value;
}
static class LhsPadding {
protected long p1, p2, p3, p4, p5, p6, p7;
}
public static void main(String[] args) throws InterruptedException {
Value user = new Value();
System.out.println(ClassLayout.parseInstance(user));
}
}
/*
Main.LhsPadding.p1 @16 (long, 8b)
Main.LhsPadding.p2 @24 (long, 8b)
Main.LhsPadding.p3 @32 (long, 8b)
Main.LhsPadding.p4 @40 (long, 8b)
Main.LhsPadding.p5 @48 (long, 8b)
Main.LhsPadding.p6 @56 (long, 8b)
Main.LhsPadding.p7 @64 (long, 8b)
Main.Value.value @72 (long, 8b)
size = 80
这里 我解释一下
LhsPadding 这个对象头部 (object header ) 占用 8字节
p1到 p7 7个元素 各占用 8字节
也就是 padding 一共 64字节
---------------
继承类的那个 value 就 占用8字节,就存到了下一个缓存行里面
*/
|
一个对象16个字节
普通对象
对象头:markword 8 (锁的标识位:标识对象的状态,GC标记:对象被回收了多少次 分代年龄)
ClassPointer指针:-XX:+UseCompressedClassPointers
为4字节(默认开启) 不开启为8字节 (对象属于哪个Class)
实例数据 引用类型:-XX:+UseCompressedOops
为4字节(默认开启) 不开启为8字节 Oops Ordinary Object Pointers(成员变量的引用 比如下面的Object o)
Padding对齐,8的倍数 (64位的机器 按块来读,一下子读16个字节)
观察虚拟机配置命令 java -XX:+PrintCommandLineFlags -version
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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
|
import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.info.GraphLayout;
/**
* @Author lyr
* @create 2021/9/15 19:20
*/
public class Main {
// 前:填充 56 字节
// static class RhsPadding extends Value {
// protected long p9, p10, p11, p12, p13, p14, p15;
// }
static class Value/* extends LhsPadding */{
// protected volatile long value;
}
// static class LhsPadding {
// protected long p1, p2, p3, p4, p5, p6, p7;
// }
public static void main(String[] args) throws InterruptedException {
Value user = new Value();
System.out.println(ClassLayout.parseInstance(user));
System.out.println( GraphLayout.parseInstance(user).toPrintable()
);
System.out.println( GraphLayout.parseInstance(user).totalSize()
);
}
}
/*
size = 16
Main$Value@f2a0b8ed object externals:
ADDRESS SIZE TYPE PATH VALUE
7404f0d08 16 Main$Value (object)
16
*/
/*
Main$Value object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
--------------------------
前2行是 markword 占用8字节
打印结果:
16 = 对象头8个字节 + ClassPointer指针 4个字节 +padding对齐 4个字节
*/
|
对象是怎么定位的
T t = new T()
t是怎么找到堆内存中的T对象
-
句柄池:
-
直接指针(HotSpot的实现):