美文网首页
jvm学习笔记

jvm学习笔记

作者: 7d972d5e05e8 | 来源:发表于2020-03-13 11:01 被阅读0次

    一、类文件加载

    1. 加载阶段
    2. 验证阶段
      2.1 文件格式校验

    class文件的字节流(01文本串),会被存储在方法区内。后面所有的验证阶段,都是基于方法区的存储结构进行的,不会再直接操作字节流。

    2.2 元数据校验

    字段,类继承,抽象类,接口,重载等等元数据的校验

    2.3 字节码校验

    方法体的校验,也是耗时最长的校验。会进行数据流和控制流分析。不容许:越界访问,类型强转合法性

    2.4 符号引用验证

    符号描述是否存在,符号引用的访问性等等。

    JDK和JRE的区别

    1. JDK:Java Development Kit。java开发工具包,包含了JRE
      JRE:Java Runtime Environment。java程序运行时环境。
    2. 开发人员只要安装了JDK,就可以愉快的开发了。如果程序员不需要jdk提供的tools.jar工具包的话,比如:javac,jconsole.jar等工具的话,只要JRE其实也可以开发程序。因为JRE包含了java程序运行所有的基础类库。比如:Class.java,HashMap.java等等程序员需要的java api。
    3. 所以一般服务器机器上,只需安装JRE就够了,不需要按照JDK。因为服务器上无需提供开发工具包,只要能运行部署上去的应用程序即可。
    image.png image.png

    happens-before原则探索

    happens-before和指令重排序无关,和线程安全无关,只和内存可见性有关。
    指令能不能重排序,取决于是否符合as-if-serial语义(前提没用volatile关键字修饰)。

    可见性问题是CPU缓存导致的,有序性问题是编译优化,对指令重排序导致的。这两个问题都可以用volatile解决。禁用CPU缓存和重排序不一定就是好的,没必要的时候禁用会导致性能问题。所以要有选择,有目的性的去禁用。参考就是happens-before原则。

    对于不符合happen-before原则的指令,编译器或者cpu可以对它们进行任何情况的重排序。对于符合happen-before原则的指令,不能对它们进行重排序。

    1. 对于符合happen-before原则,单线程和多线程他们的指令顺序是一致的。
    2. 不符合happen-before原则,可以对它们进行任何形式的重排序。但是不管怎么重排序,单线程程序的执行结果不能被改变。编译器、运行时和处理器都必须遵守“as-if-serial”语义。

    什么是happen-before原则(先行发生原则)?

    它解决了什么问题?
    happen-before仅仅用来解决内存可见性的问题。如下代码:

    int a = 0;
    int b = 1;
    int c = a + b;
    

    如果按照happens-before的严格定义,第1,2,3行应该是严格按照顺序执行的。但是第1,2行虽然符合第一个规则:程序次序规则,由于它们不存在依赖关系,cpu或者编译器可以不遵守这么强的约定。它们是可以被重排序的。
    有个疑问:这种虽然语义上是无害的,但是应用层面这种排序是有害的啊!既然是happens-before严格定义了程序持续规则,为啥cpu不去遵守而去重排序呢?
    其实我搞混了happes-before和重排序的关系,他们之间没有任何关系。谁说符合happens-before原则的代码,就不能重排序它了。比如上面那段代码,

    volatile关键字提供了两个功能:
    1、可见性:一个线程写完,其他线程能立马得到最新值。这个是happens-before其中一个规则定义的,它是JMM实现的,语言规范所强制实现的。
    2、禁止重排序:这个是JVM对这个关键字,提供了特殊功能,和happens-before没有一毛钱关系。纯粹是JVM特殊处理这个关键字。就算没有volatile这个关键字,JVM也会找到其他关键字来表达”禁止重排序“的功能。

    所以,是否满足happens-before规则,并不会决定是否重排序。比如:上面volatile满足happens-before,但是禁止重排序。上面a=0,b=1也满足happens-before,但是它就可以重排序。

    在举一个例:

    public class HappenBeforeDemo {
    
        public static int i = 1;
        public static int j = i;
        public static int i = 2;
    
      public void print(){
        //多线程执行,这个j值会是多少?1还是2?
        System.out.println(j);
      }
    }
    

    Happens-Before的八个规则(摘自《深入理解Java虚拟机》12.3.6章节):

    1. 程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作;--虽然满足规则,但是两个操作之间存在happens-before关系,并不意味着一定要按照happens-before原则制定的顺序来执行。如果重排序之后的执行结果与按照happens-before关系来执行的结果一致,那么这种重排序并不非法。所以,这种规则可以允许重排序
    2. 管程锁定规则:一个unLock操作先行发生于后面对同一个锁的lock操作;(此处后面指时间的先后) -- 存在数据依赖关系,不允许重排序
    3. volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作;(此处后面指时间的先后) --java语言规定了,禁止重排序,所以不允许重排序
    4. 线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作; --存在数据依赖,不允许重排序
    5. 线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行;-- 同上
    6. 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生; -- 同上
    7. 对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始; -- 同上
    8. 传递性:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C; --同上

    从上面可以看到,满足happens-before八大原则的代码,基本都不允许重排序,除了第一个。所以满足了happens-before之后,还需要在判断是否存在数据依赖。如果没有依赖,其实是可以重排序的。

    网上摘录:
    原文:https://www.jianshu.com/p/b4c1e4809003
    JSR-133使用happens-before的概念来指定两个操作之间的执行顺序。由于这两个操作可以在一个线程之内,也可以是在不同线程之间。因此,JMM可以通过happens-before关系向程序员提供跨线程的内存可见性保证(如果A线程的写操作a与B线程的读操作b之间存在happens-before关系,尽管a操作和b操作在不同的线程中执行,但JMM向程序员保证a操作将对b操作可见)。

    1)如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行按顺序排在第二个操作之前。

    2)两个操作之间存在happens-before关系,并不意味着Java平台的具体实现必须要按照happens-before关系指定的顺序来执行。如果重排序之后的执行结果,与按happens-before关系来执行的结果一致,那么这种重排序并不非法。

    上面的1)是JMM对程序员的承诺。从程序员的角度来说,可以这样理解happens-before关系:如果A happens-before B,那么Java内存模型将向程序保证——A操作的结果将对B可见,且A的执行顺序排在B之前。注意,这只是Java内存模型向程序员做出的保证。

    上面的2)是JMM对编译器和处理器重排序的约束原则。正如前面所言,JMM其实是在遵循一个基本原则:只要不改变程序的执行结果(指的是单线程程序和正确同步的多线程),编译器和处理器怎么优化都行。JMM这么做的原因是:程序员对于这两个操作是否真的被重排序并不关心,程序员关心的是程序执行时的语义不能被改变(即执行结果不能被改变)。因此,happens-before关系本质上和as-if-serial语义是一回事。

    1. as-if-serial语义保证单线程中程序执行结果不被改变,happens-before保证正确同步的多线程程序的执行结果不被改变。

    2. as-if-serial语义编写单线程程序的程序员创造了一个幻境:单线程程序是按程序的顺序来执行的。happens-before关系给编写正确同步的多线程程序的程序员创造了一个幻境:正确同步的多线程程序是按happens-before指定的顺序来执行的。

    as-if-serial VS happen-before

    1. as-if-serial语义保证单线程内程序的执行结果不被改变,happens-before关系保证正确同步的多线程程序的执行结果不被改变。
    2. as-if-serial语义给编写单线程程序的程序员创造了一个幻境:单线程程序是按程序的顺序来执行的。happens-before关系给编写正确同步的多线程程序的程序员创造了一个幻境:正确同步的多线程程序是按happens-before指定的顺序来执行的。
    3. as-if-serial语义和happens-before这么做的目的,都是为了在不改变程序执行结果的前提下,尽可能地提高程序执行的并行度。

    二、java的跨平台,依赖哪些技术

    1. 解释器:把字节码解释为不同平台下的机器码,用来解决执行问题
    2. JMM:给java提供一致的内存模型,屏蔽底层不同平台各自内存读取操作模型,用来解决不同平台执行结果不一致的问题。
    没有JMM,会有什么问题?

    我理解:JMM是为了屏蔽不同平台读取规则的不一致。比如:有些平台直接读取主内存,有些平台会优先读取cpu缓存,再读取主内存。JMM索性自己在内存中开辟一个工作内存,模拟一个cpu缓存出来。下面的平台该咋读咋读,但是都必须在我的工作内存读取。主内存对平台就透明了,平台以为自己读取的是主内存,其实JMM偷偷替换为了工作内存

    1. 没有JMM的话,执行一段代码,在不同平台上得到的结果。有可能得到正确的,有可能得到错误的。因为平台有自己独立的读写规则。

    三、class文件的常量池、运行时常量池以及字符串常量池的区别

    首先看下面一段代码:

    public class StringTest {
        public static void main(String[] arg){
            String s1 = "abc";
      }
    }
    

    最开始这段java代码经过编译后,生成class文件。“abc”这个字符串会出现class文件的CONSTANT_Utf8结构中。它在文件里面,不在jvm内存里面。

    然后,我们开始加载StringTest类到方法区,会把该class文件里面的所有CONSTANT_Utf8结构中的字符串加载到运行时常量池。把符号引用转换为直接引用。

    最后,执行到main方法时,“abc”会先去字符串常量池查询,找不到就在创建一个字符串到字符串常量池里面。

    相关文章

      网友评论

          本文标题:jvm学习笔记

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