写点什么

JVM 调优(一)

用户头像
彭阿三
关注
发布于: 2 小时前

类的加载过程

Loading->Linking(verification->preparation->resolution)->Initializing

Loading

将.class 文件 load 到内存中

Linking

  1. verification(验证):验证文件是否符合 JVM 规定

  2. Preparation(准备):静态成员变量赋默认值

  3. Resolution(解析,解释):将类、方法、属性等符号引用解析为直接引用,常量池中的各种符号引用解析为指针、偏移量等内存地址的直接引用

Initializing

调用类初始化代码 <clinit>,给静态成员变量赋初始值

类加载器(ClassLoader)


说明:一般情况下,是从底层往上按顺序处理,先查找是否已经被 load 过,如果 CustomClassLoad 没有加载过,继续往上从 AppClassLoad 中找,如果加载过直接返回,依次类推到最上层 BootStrap,然后往下去 findClass 并 load,如果不是自己加载的就往下寻找直到类被 ClassLoad 加载,如果到最没有没有加载的话抛出异常 notfoundClass.

Loading

  1. 双亲委派,主要出于安全来考虑

  2. LazyLoading (懒加载,需要时加载)五种情况

  3. new getstatic putstatic invokestatic 指令,访问 final 变量除外

  4. java.lang.reflect 对类进行反射调用时

  5. 初始化子类的时候,父类首先初始化

  6. 虚拟机启动时,被执行的主类必须初始化

  7. 动态语言支持 java.lang.invoke.MethodHandle 解析的结果为 REF_getstatic REF_putstatic REF_invokestatic 的方法句柄时,该类必须初始化

  8. ClassLoader 的源码 :findInCache -> parent.loadClass -> findClass()

  9. 自定义类加载器

  10. 混合执行 编译执行 解释执行

Linking

下面我会用一段小程序来体现一个类的加载过程


public class ClassLoadingDemo{    public static void main(String[] args) {        System.out.println("count:"+T.count);    }}
class T { public static int count = 2; //0 public static T t = new T(); // null

private T() { count ++; }}
// count:3
复制代码


这里为什么 count 会等于 3 呢?因为一般实例化有一个开辟空间准备的过程,然后在赋值,首先 int count =0,T t =null,然后在初始化 int cout =2,t 实例化会调用构造函数执行了 count++,所以打印出来 count:3,下来这段代码的话,我们自己来分析下打印结果是什么


public class ClassLoadingDemo{    public static void main(String[] args) {        System.out.println("count:"+T.count);    }}
class T { public static T t = new T(); // null public static int count = 2; //0
private T() { count ++; }}
复制代码

JMM java 内存模型

硬件级别缓存一致性(缓存锁 MESI)

缓存行,伪共享,一般我们 cpu 执行的时候拿取数据都是一块一块的拿,不用的数据也会拿过来,可能多个 cpu 使用一个缓存行的时候会互相争抢,cache 失效等操作影响性能。but,有一些无法被缓存或者 MESI 解决不了的问题依然必须使用锁总线的方式

MESI Cache 一致性协议

一致性协议(MESI):https://www.cnblogs.com/z00377750/p/9180644.html

MESI 协议中的状态

CPU 中每个缓存行(cache line)使用 4 种状态进行标记(使用额外的两位(bit)表示)


M:被修改(Modified)


该缓存行只被缓存在该 CPU 的缓存中,并且是被修改过的(dirty),即与主存中的数据不一致,该缓存行中的内存需要在未来的某个时间点(允许其它 CPU 读取请主存中相应内存之前)写回(write back)主存。当被写回主存之后,该缓存行的状态会变成独享(exclusive)状态。


E:独享的(Exclusive)


该缓存行只被缓存在该 CPU 的缓存中,它是未被修改过的,与主内存的数据一致,该状态可以在任何时刻当有其它 CPU 读取该内存时变成共享状态(shared)。同样的,它是可以被修改的,状态变为(Modified)被修改的。


S:共享的(Shared)


该状态意味着该缓存行可能被多个 CPU 缓存,并且缓存行数据与主存一致,如果其中一个缓存行被修改,那么其他的 CPU 缓存状态变为(Invalid)无效的


I:无效的(Invalid)


该缓存是无效的(可能有其它 CPU 修改了该缓存行)。

缓存行

缓存行越大,局部性空间效率越高,但是读取效率越低缓存行越小,局部性空间效率越低,但是读取效率越高工业实验取舍后,目前来说,多用 64 字节使用缓存行的对齐能够提高效率。

乱序问题

CPU 为了提高指令执行效率,会在一条指令执行过程中(比如去内存读数据(慢 100 倍)),去同时执行另一条指令,前提是,两条指令没有依赖关系https://www.cnblogs.com/liushaodong/p/4777308.html写操作有一个合并写的概念,WCBuffer,把写操作合并有可能也会出现乱序,原因是有的写操作快有的写操作慢。 一般只有 4 个位置(特别宝贵,稀有物品)在批处理的场景里,我们合理的运用 WCbuffer 可以提高程序的效率。

如何证明乱序问题

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() { 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); }}
第218385次 (0,0)
复制代码


以上代码执行一段时间后,会出现 0,0 的情况,这就充分说明了乱序执行。

如何保证有序性

硬件内存屏障 X86
  1. sfence(savefence):在 sfence 指令前的写操作必须在 sfence 指令后的写操作前完成。

  2. lfence(loadfence):在 lfence 指令前的读操作必须在 lfence 指令后的读操作前完成。

  3. mfence(mixfence):在 mfence 指令前的读写操作必须在 mfence 指令后的读写操作前完成。


原子指令,如 x86 上的”lock …” 指令是一个 Full Barrier,执行时会锁住内存子系统来确保执行顺序,甚至跨多个 CPU。Software Locks 通常使用了内存屏障或原子指令来实现变量可见性和保持程序顺序 如 lock addl

JVM 级别如何规范(JSR133)
  1. LoadLoad 屏障:对于这样的语句 Load1; LoadLoad; Load2, 在 Load2 及后续读取操作要读取的数据被访问前,保证 Load1 要读取的数据被读取完毕。

  2. StoreStore 屏障:对于这样的语句 Store1; StoreStore; Store2,在 Store2 及后续写入操作执行前,保证 Store1 的写入操作对其它处理器可见。

  3. LoadStore 屏障:对于这样的语句 Load1; LoadStore; Store2,在 Store2 及后续写入操作被刷出前,保证 Load1 要读取的数据被读取完毕。

  4. StoreLoad 屏障:对于这样的语句 Store1; StoreLoad; Load2,在 Load2 及后续所有读取操作执行前,保证 Store1 的写入对所有处理器可见。

volatile 实现细节

字节码层面

ACC_VOLATILE

JVM 层面

LoadLoad 屏障、StoreStore 屏障、LoadStore 屏障、StoreLoad 屏障

OS 和硬件层面

https://blog.csdn.net/qq_26222859/article/details/52235930hsdis - HotSpot Dis Assemblerwindows lock 指令实现 | MESI 实现

synchronized 实现细节

字节码层面

ACC_SYNCHRONIZEDmonitorenter ---> monitorexit

JVM 层面

C C++ 调用了操作系统提供的同步机制

OS 和硬件层面

X86 : lock cmpxchg / xxxhttps://blog.csdn.net/21aspnet/article/details/88571740

用户头像

彭阿三

关注

java工程师 2019.06.28 加入

一个慵懒的程序员。

评论

发布
暂无评论
JVM调优(一)