类加载-初始化
加载过程
Loading
- 双亲委派,主要出于安全来考虑
-
LazyLoading 五种情况
- –new getstatic putstatic invokestatic指令,访问final变量除外
–java.lang.reflect对类进行反射调用时
–初始化子类的时候,父类首先初始化
–虚拟机启动时,被执行的主类必须初始化
–动态语言支持java.lang.invoke.MethodHandle解析的结果为REF_getstatic REF_putstatic REF_invokestatic的方法句柄时,该类必须初始化
- –new getstatic putstatic invokestatic指令,访问final变量除外
-
ClassLoader的源码
- findInCache -> parent.loadClass -> findClass()
- 自定义类加载器
- extends ClassLoader
- overwrite findClass() -> defineClass(byte[] -> Class clazz)
- 加密
- parent是如何指定的,打破双亲委派
- 用super(parent)指定
- 双亲委派的打破
- 如何打破:重写loadClass()
- 何时打破过?
- JDK1.2之前,自定义ClassLoader都必须重写loadClass()
- ThreadContextClassLoader可以实现基础类调用实现类代码,通过thread.setContextClassLoader指定
- 热启动,热部署
- osgi tomcat 都有自己的模块指定classloader(可以加载同一类库的不同版本)
- 混合执行 编译执行 解释执行
- 检测热点代码:-XX:CompileThreshold = 10000
Linking – 链接
- Verification-验证
- 验证文件是否符合JVM规定
- Preparation-准备
- 静态成员变量赋默认值
- Resolution-解析
- 将类、方法、属性等符号引用解析为直接引用
常量池中的各种符号引用解析为指针、偏移量等内存地址的直接引用
- 将类、方法、属性等符号引用解析为直接引用
Initializing – 初始化
- 调用类初始化代码
,给静态成员变量赋初始值
例子:
public class T001_ClassLoadingProcedure {
public static void main(String[] args) {
System.out.println(T.count);
}
}
class T {
public static T t = new T(); //Preparation-> null
public static int count = 2; //Preparation-> 0
private T() {
count ++;
}
}
/**结果
2
*/
public class T001_ClassLoadingProcedure {
public static void main(String[] args) {
System.out.println(T.count);
}
}
class T {
public static int count = 2; //Preparation-> 0
public static T t = new T(); //Preparation-> null
private T() {
count ++;
}
}
/**结果
3
*/
小总结:
- load – 默认值 – 初始值
-
new – 申请内存 – 默认值 – 初始值. (半初始化状态,DCL单例模式)
public static void main(String[] args) { Hello_03 h = new Hello_03(); } //字节码 0 new #2 <com/mashibing/jvm/c4_RuntimeDataAreaAndInstructionSet/Hello_03> 3 dup 4 invokespecial #3 <com/mashibing/jvm/c4_RuntimeDataAreaAndInstructionSet/Hello_03.<init>> 7 astore_1 8 aload_1 9 invokevirtual #4 <com/mashibing/jvm/c4_RuntimeDataAreaAndInstructionSet/Hello_03.m1> 12 istore_2 13 return
JMM
硬件层数据一致性
协议很多
intel 用MESI
https://www.cnblogs.com/z00377750/p/9180644.html
现代CPU的数据一致性实现 = 缓存锁(MESI …) + 总线锁
读取缓存以cache line为基本单位,目前64bytes
位于同一缓存行的两个不同数据,被两个不同CPU锁定,产生互相影响的伪共享问题
伪共享问题:JUC/c_028_FalseSharing
使用缓存行的对齐能够提高效率
private static class Padding {
public volatile long p1, p2, p3, p4, p5, p6, p7;
}
private static class T extends Padding {
public volatile long x = 0L;
}
public static T[] arr = new T[2];
static {
arr[0] = new T();
arr[1] = new T();
}
public static void main(String[] args) throws Exception {
Thread t1 = new Thread(()->{
for (long i = 0; i < 1000_0000L; i++) {
arr[0].x = i;
}
});
Thread t2 = new Thread(()->{
for (long i = 0; i < 1000_0000L; i++) {
arr[1].x = i;
}
});
final long start = System.nanoTime();
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println((System.nanoTime() - start)/100_0000);
}
乱序问题
CPU为了提高指令执行效率,会在一条指令执行过程中(比如去内存读数据(慢100倍)),去同时执行另一条指令,前提是,两条指令没有依赖关系
https://www.cnblogs.com/liushaodong/p/4777308.html
写操作也可以进行合并
https://www.cnblogs.com/liushaodong/p/4777308.html
JUC/029_WriteCombining
public final class WriteCombining {
private static final int ITERATIONS = Integer.MAX_VALUE;
private static final int ITEMS = 1 << 24;
private static final int MASK = ITEMS - 1;
private static final byte[] arrayA = new byte[ITEMS];
private static final byte[] arrayB = new byte[ITEMS];
private static final byte[] arrayC = new byte[ITEMS];
private static final byte[] arrayD = new byte[ITEMS];
private static final byte[] arrayE = new byte[ITEMS];
private static final byte[] arrayF = new byte[ITEMS];
public static void main(final String[] args) {
for (int i = 1; i <= 3; i++) {
System.out.println(i + " SingleLoop duration (ns) = " + runCaseOne());
System.out.println(i + " SplitLoop duration (ns) = " + runCaseTwo());
}
}
public static long runCaseOne() {
long start = System.nanoTime();
int i = ITERATIONS;
while (--i != 0) {
int slot = i & MASK;
byte b = (byte) i;
arrayA[slot] = b;
arrayB[slot] = b;
arrayC[slot] = b;
arrayD[slot] = b;
arrayE[slot] = b;
arrayF[slot] = b;
}
return System.nanoTime() - start;
}
public static long runCaseTwo() {
long start = System.nanoTime();
int i = ITERATIONS;
while (--i != 0) {
int slot = i & MASK;
byte b = (byte) i;
arrayA[slot] = b;
arrayB[slot] = b;
arrayC[slot] = b;
}
i = ITERATIONS;
while (--i != 0) {
int slot = i & MASK;
byte b = (byte) i;
arrayD[slot] = b;
arrayE[slot] = b;
arrayF[slot] = b;
}
return System.nanoTime() - start;
}
}
乱序执行的证明:JVM/jmm/Disorder.java
原始参考:https://preshing.com/20120515/memory-reordering-caught-in-the-act/
public class T04_Disorder {
private static int x = 0, y = 0;
private static int a = 0, b =0;
public static void main(String[] args) throws InterruptedException {
int i = 0;
for(;;) {
i++;
x = 0; y = 0;
a = 0; b = 0;
Thread one = new Thread(new Runnable() {
public void run() {
//由于线程one先启动,下面这句话让它等一等线程two. 读着可根据自己电脑的实际性能适当调整等待时间.
//shortWait(100000);
a = 1;
x = b;
}
});
Thread other = new Thread(new Runnable() {
public void run() {
b = 1;
y = a;
}
});
one.start();other.start();
one.join();other.join();
String result = "第" + i + "次 (" + x + "," + y + ")";
if(x == 0 && y == 0) {
System.err.println(result);
break;
} else {
//System.out.println(result);
}
}
}
public static void shortWait(long interval){
long start = System.nanoTime();
long end;
do{
end = System.nanoTime();
}while(start + interval >= end);
}
}
如何保证特定情况下不乱序
硬件内存屏障 X86
sfence: store| 在sfence指令前的写操作当必须在sfence指令后的写操作前完成。
lfence:load | 在lfence指令前的读操作当必须在lfence指令后的读操作前完成。
mfence:modify/mix | 在mfence指令前的读写操作当必须在mfence指令后的读写操作前完成。原子指令,如x86上的”lock …” 指令是一个Full Barrier,执行时会锁住内存子系统来确保执行顺序,甚至跨多个CPU。Software Locks通常使用了内存屏障或原子指令来实现变量可见性和保持程序顺序
JVM级别如何规范(JSR133)
LoadLoad屏障:
对于这样的语句Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
StoreStore屏障:
对于这样的语句Store1; StoreStore; Store2,
在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。
LoadStore屏障:
对于这样的语句Load1; LoadStore; Store2,
在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。
StoreLoad屏障:
对于这样的语句Store1; StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。
volatile的实现细节
- 字节码层面
access_flag: ACC_VOLATILE -
JVM层面
volatile内存区的读写 前后都加屏障StoreStoreBarrier
volatile 写操作
StoreLoadBarrier
LoadLoadBarrier
volatile 读操作
LoadStoreBarrier
- OS和硬件层面
https://blog.csdn.net/qq_26222859/article/details/52235930
hsdis – HotSpot Dis Assembler
windows lock 指令实现 | MESI实现
synchronized实现细节
- 字节码层面
方法上加synchronized,是access_flag: ACC_SYNCHRONIZED
非方法上加synchronized,是monitorenter monitorexitvoid n() { synchronized (this) { } } //字节码 0 aload_0 1 dup 2 astore_1 3 monitorenter 4 aload_1 5 monitorexit 6 goto 14 (+8) 9 astore_2 10 aload_1 11 monitorexit 12 aload_2 13 athrow 14 return
- JVM层面
C C++ 调用了操作系统提供的同步机制 -
OS和硬件层面
X86 : lock cmpxchg / xxx
https://blog.csdn.net/21aspnet/article/details/88571740
对象的内存布局
待补充
运行时数据区和指令集
指令集分类
- 基于寄存器的指令集
- 基于栈的指令集
Hotspot中的Local Variable Table = JVM中的寄存器
Runtime Data Area
PC 程序计数器
存放指令位置
虚拟机的运行,类似于这样的循环:
while( not end ) {
取PC中的位置,找到对应位置的指令;
执行该指令;
PC ++;
}
JVM Stack
- Frame – 每个方法对应一个栈帧
- Local Variable Table
局部变量
-
Operand Stack
操作数栈
对于long的处理(store and load),多数虚拟机的实现都是原子的
单条指令都可能不是原子性的
jls 17.7,没必要加volatile
public class TestIPulsPlus { public static void main(String[] args) { int i = 8; //i = i++; i = ++i; System.out.println(i); } //main()字节码 /** 0 bipush 8 8压栈 2 istore_1 出栈存储到局部变量表1,也就是i 3 iinc 1 by 1 局部变量表1增加1(局部变量1变成了9) 6 iload_1 从局部变量表1拿出来压栈,也就是i=9 7 istore_1 出栈存储到局部变量表1,也就是i=9 8 getstatic #2 <java/lang/System.out> 11 iload_1 12 invokevirtual #3 <java/io/PrintStream.println> 15 return */ }
- Dynamic Linking
指向运行时常量池的符号链接
https://blog.csdn.net/qq_41813060/article/details/88379473
jvms 2.6.3 -
return address-返回值地址
a() -> b(),方法a调用了方法b, b方法的返回值放在什么地方
- Local Variable Table
Heap
Method Area — 可能会导致OOM的
-
Perm Space (<1.8)
字符串常量位于PermSpace
FGC不会清理
大小启动的时候指定,不能变 -
Meta Space (>=1.8)
当使用lambda表达式时,会动态创建class,所以使用不当可能导致oom
字符串常量位于堆
会触发FGC清理
不设定的话,最大就是物理内存思考:
如何证明1.7字符串常量位于Perm,而1.8位于Heap?
提示:结合GC, 一直创建字符串常量,观察堆,和Metaspace
Runtime Constant Pool
Native Method Stack
Direct Memory
JVM可以直接访问的内核空间的内存 (OS 管理的内存)
NIO , 提高效率,实现zero copy
常用指令
store
load
pop
mul
sub
invoke
- InvokeStatic
- InvokeVirtual
- InvokeInterface
- InovkeSpecial
可以直接定位,不需要多态的方法
private 方法 , 构造方法 - InvokeDynamic
JVM最难的指令
lambda表达式或者反射或者其他动态语言scala kotlin,或者CGLib ASM,动态产生的class,会用到的指令