一、类文件加载
- 加载阶段
- 验证阶段
2.1 文件格式校验
class文件的字节流(01文本串),会被存储在方法区内。后面所有的验证阶段,都是基于方法区的存储结构进行的,不会再直接操作字节流。
2.2 元数据校验
字段,类继承,抽象类,接口,重载等等元数据的校验
2.3 字节码校验
方法体的校验,也是耗时最长的校验。会进行数据流和控制流分析。不容许:越界访问,类型强转合法性
2.4 符号引用验证
符号描述是否存在,符号引用的访问性等等。
JDK和JRE的区别
- JDK:Java Development Kit。java开发工具包,包含了JRE
JRE:Java Runtime Environment。java程序运行时环境。 - 开发人员只要安装了JDK,就可以愉快的开发了。如果程序员不需要jdk提供的tools.jar工具包的话,比如:javac,jconsole.jar等工具的话,只要JRE其实也可以开发程序。因为JRE包含了java程序运行所有的基础类库。比如:Class.java,HashMap.java等等程序员需要的java api。
- 所以一般服务器机器上,只需安装JRE就够了,不需要按照JDK。因为服务器上无需提供开发工具包,只要能运行部署上去的应用程序即可。
happens-before原则探索
happens-before和指令重排序无关,和线程安全无关,只和内存可见性有关。
指令能不能重排序,取决于是否符合as-if-serial语义(前提没用volatile关键字修饰)。
可见性问题是CPU缓存导致的,有序性问题是编译优化,对指令重排序导致的。这两个问题都可以用volatile解决。禁用CPU缓存和重排序不一定就是好的,没必要的时候禁用会导致性能问题。所以要有选择,有目的性的去禁用。参考就是happens-before原则。
对于不符合happen-before原则的指令,编译器或者cpu可以对它们进行任何情况的重排序。对于符合happen-before原则的指令,不能对它们进行重排序。
- 对于符合happen-before原则,单线程和多线程他们的指令顺序是一致的。
- 不符合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章节):
- 程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作;--虽然满足规则,但是两个操作之间存在happens-before关系,并不意味着一定要按照happens-before原则制定的顺序来执行。如果重排序之后的执行结果与按照happens-before关系来执行的结果一致,那么这种重排序并不非法。所以,这种规则可以允许重排序。
- 管程锁定规则:一个unLock操作先行发生于后面对同一个锁的lock操作;(此处后面指时间的先后) -- 存在数据依赖关系,不允许重排序
- volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作;(此处后面指时间的先后) --java语言规定了,禁止重排序,所以不允许重排序
- 线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作; --存在数据依赖,不允许重排序
- 线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行;-- 同上
- 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生; -- 同上
- 对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始; -- 同上
- 传递性:如果操作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语义是一回事。
-
as-if-serial语义保证单线程中程序执行结果不被改变,happens-before保证正确同步的多线程程序的执行结果不被改变。
-
as-if-serial语义编写单线程程序的程序员创造了一个幻境:单线程程序是按程序的顺序来执行的。happens-before关系给编写正确同步的多线程程序的程序员创造了一个幻境:正确同步的多线程程序是按happens-before指定的顺序来执行的。
as-if-serial VS happen-before
- as-if-serial语义保证单线程内程序的执行结果不被改变,happens-before关系保证正确同步的多线程程序的执行结果不被改变。
- as-if-serial语义给编写单线程程序的程序员创造了一个幻境:单线程程序是按程序的顺序来执行的。happens-before关系给编写正确同步的多线程程序的程序员创造了一个幻境:正确同步的多线程程序是按happens-before指定的顺序来执行的。
- as-if-serial语义和happens-before这么做的目的,都是为了在不改变程序执行结果的前提下,尽可能地提高程序执行的并行度。
二、java的跨平台,依赖哪些技术
- 解释器:把字节码解释为不同平台下的机器码,用来解决执行问题
- JMM:给java提供一致的内存模型,屏蔽底层不同平台各自内存读取操作模型,用来解决不同平台执行结果不一致的问题。
没有JMM,会有什么问题?
我理解:JMM是为了屏蔽不同平台读取规则的不一致。比如:有些平台直接读取主内存,有些平台会优先读取cpu缓存,再读取主内存。JMM索性自己在内存中开辟一个工作内存,模拟一个cpu缓存出来。下面的平台该咋读咋读,但是都必须在我的工作内存读取。主内存对平台就透明了,平台以为自己读取的是主内存,其实JMM偷偷替换为了工作内存
- 没有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”会先去字符串常量池查询,找不到就在创建一个字符串到字符串常量池里面。
网友评论