美文网首页
JVM内存模型

JVM内存模型

作者: Jacquie葭葵 | 来源:发表于2019-01-04 15:09 被阅读0次

    什么是内存模型

    1. 写入动作可见

      内存模型定义了一个充分必要条件,保证其它CPU的写入动作对该CPU是可见的,而且该CPU的写入动作对其它CPU也是可见的

      1. 强内存模型,所有CPU在任何时候都能看到内存中任意位置相同的值,这种完全是硬件提供的支持。
      2. 弱内存模型,需要执行一些特殊指令(就是经常看到或者听到的,memory barriers内存屏障),刷新CPU缓存的数据到内存中,保证这个写操作能够被其它CPU可见,或者将CPU缓存的数据设置为无效状态,保证其它CPU的写操作对本CPU可见。通常这些内存屏障的行为由底层实现,对于上层语言的程序员来说是透明的(不需要太关心具体的内存屏障如何实现)。
      3. 重排序可以发生在好几个地方:编译器、运行时、JIT等,比如编译器会觉得把一个变量的写操作放在最后会更有效率,编译后,这个指令就在最后了(前提是只要不改变程序的语义,编译器、执行器就可以这样自由的随意优化),一旦编译器对某个变量的写操作进行优化(放到最后),那么在执行之前,另一个线程将不会看到这个执行结果。
    Class Reordering {
        int x = 0, y = 0;
        public void writer() {
            x = 1;
            y = 2;
            }
        public void reader() {
            int r1 = y;
            int r2 = x; 
            } 
        /*在writer方法中,可能发生了重排序,
        *y的写入动作可能发在x写入之前,这种情况下,
        *线程B就有可能看到 x的值还是0。
        */
    }
    
    

    在Java内存模型中,描述了在多线程代码中,哪些行为是正确的、合法的,以及多线程之间如何进行通信,代码中变量的读写行为如何反应到内存、CPU缓存的底层细节。

    在Java中包含了几个关键字:volatile、final和synchronized,帮助程序员把代码中的并发需求描述给编译器。Java内存模型中定义了它们的行为,确保正确同步的Java代码在所有的处理器架构上都能正确执行。

    synchronization 可以实现什么

    Synchronization有多种语义,其中最容易理解的是互斥,对于一个monitor对象,只能够被一个线程持有,意味着一旦有线程进入了同步代码块,那么其它线程就不能进入直到第一个进入的线程退出代码块(这因为都能理解)

    对于两个线程来说,在相同的monitor对象上同步是很重要的,以便正确的设置happens-before关系。

    final 可以影响什么

    如果一个类包含final字段,且在构造函数中初始化,那么正确的构造一个对象后,final字段被设置后对于其它线程是可见的。

    这里所说的正确构造对象,意思是在对象的构造过程中,不允许对该对象进行引用,不然的话,可能存在其它线程在对象还没构造完成时就对该对象进行访问,造成不必要的麻烦。

    JVM的内存分区

    每个线程都有独立的栈并且是相互隔离的,

    栈的大小

    一个是jvm参数 -XSS,默认值随着虚拟机版本以及操作系统影响。我们可以认为64位linux默认是1m的样子。 除了JVM设置,我们还可以在创建Thread的时候手工指定大小

    栈的大小影响到了线程的最大数量,尤其在大流量的server中,我们很多时候的并发数受到的是线程数的限制,这时候需要了解限制在哪里

    可看作:

    线程数 = (系统空闲内存-堆内存(-Xms, -Xmx)- perm区内存(-XX:MaxPermSize)) / 线程栈大小(-Xss)

    堆的结构

    对于大多数应用来说,Java 堆(Java Heap)是Java 虚拟机所管理的内存中最大的一块。Java 堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。

    新对象会首先分配在Eden中(如果对象过大,比如大数组,将会直接放到老年代)。在GC中,Eden中的对象会被移动到survivor中,直至对象满足一定的年纪(定义为熬过minor GC的次数),会被移动到老年代。

    方法区

    又称为“静态区”,和堆一样,被所有线程共享。 其中包含所有的 class 和 static 变量

    GC 垃圾收集

    思考一下复制和标记清除/整理的区别,为什么新生代要用复制?因为对新生代来讲,一次垃圾收集要回收掉绝大部分对象,我们通过冗余空间的办法来加速整理过程(不冗余空间的整理操作要做swap,而冗余只需要做move)。同时可以记录下每个对象的『年龄』从而优化『晋升』操作使得中年对象不被错误放到老年代。而反过来老年代偏稳定,我们哪怕是用清除,也不会产生太多的碎片,并且整理的代价也并不会太大。

    引用与内存实例

    首先,字面值及其引用
    int a = 1;
    int b = 1;
    int b = 2;
    //先创建名为a的引用,再去找是否已存在字面值为1的地址,没找到开辟新地址放1.
    //再创建b的引用变量,---栈---中已有“1”,直接指向它
    //改变b时,a不会改变,因为改变的是b的引用,而不是指向的字面值
    
    new String()
    String str1 = "Hello";
    String str2 = "Hello";
    System.out.println(str1 == str2);//true
    String str3 = new String("Hello");
    String str4 = new String("Hello");
    System.out.println(str3 == str4);//false
    //String 是特殊的包装类
    //用new()来新建,会存放在---堆---中,每次调用都是新的。
    //而创建引用常量还是和字面值一样,不会新建,而去---栈---中的常量池中寻找
    String str5 = str1+"World";
    String str6 = "HelloWorld";
    System.out.println(s5 == s6);//false
    //equals判断的是字面值是否相等,而==判断的是引用是否指向同一个对象
    

    new()出一个实例,JVM首先在堆中为其分配内存,拥有着指向方法区

    类加载过程

    参考链接:https://www.jianshu.com/p/ace2aa692f96

    从.java文件到实际加载到内存中

    JVM调用指定的ClassLoader去加载.class文件等各类路径、文件的类

    .java文件 -> 通过你的JDK环境相关指令编译 -> .class文件 -> JVM初始化之后,如果有类的执行、调用等相关操作,JVM就会将.class文件加载到内存中,并开始下面的一系列处理:(链接->初始化)

    image
    /* 类加载方式:都是JVM调用ClassLoader去加载类 */
    //方式一  Class.forName(String name);
    public static Class<?> forName(String className) throws ClassNotFoundException { 
        return forName(className, true, VMStack.getCallingClassLoader()); 
    }
    public static Class<?> forName(String className, boolean shouldInitialize,
                                   ClassLoader classLoader) throws ClassNotFoundException {
        if (classLoader == null) { 
            classLoader = BootClassLoader.getInstance(); 
        } 
        Class<?> result; 
        try { 
            result = classForName(className, shouldInitialize, classLoader); 
        } catch (ClassNotFoundException e) { 
            Throwable cause = e.getCause(); 
            if (cause instanceof LinkageError) { 
                throw (LinkageError) cause; 
            } 
            throw e; 
        } 
        return result; 
    } 
    
    //方式二
    
    
    类的初始化之前
    1. 加载
      • 通过一个类全限定名(java.lang.String)来获取定义此类的二进制字节流
      • 将其所代表的静态存储结构 》 方法区运行时数据结构
      • 在---堆---中生成一个代表这个类的java.lang.Class

    相关文章

      网友评论

          本文标题:JVM内存模型

          本文链接:https://www.haomeiwen.com/subject/iutkrqtx.html