[toc]
将字节码转换为机器编码
解释执行和即时编译执行----一般是混合执行----codecache可能会满
垃圾回收 安全检查等
-
jvm 一行代码是怎么运行的?
首先,java代码会被编译成字节码,字节码就是java虚拟机定义的一种编码格式,需要java虚拟机才能够解析,java虚拟机需要将字节码转换成机器码才能在cpu上执行。
我们可以用硬件实现虚拟机,这样虽然可以提高效率但是就没有了一次编译到处运行的特性了,所以一般在各个平台上用软件来实现,目前的虚拟机还提供了一套运行环境来进行垃圾回收,数组越界检查,权限校验等。
虚拟机一般将一行字节码解释成机器码然后执行,称为解释执行,也可以将一个方法内的所有字节码解释成机器码之后在执行,前者执行效率低,后者会导致启动时间慢,一般根据二八法则,将百分之20的热点代码进行即时编译。JIT编译的机器码存放在一个叫codecache的地方,这块内存属于堆外内存,如果这块内存不够了,那么JIT编译器将不再进行即时编译,可能导致程序运行变慢。
加载---双亲委派、获取字节码,类加载器决定一个类的唯一性 (类加载器+类名)
链接---验证、分配内存、解析
初始化---cinit()方法加synchronized
-
jvm如何加载一个类
第一步:加载,双亲委派:启动类加载器(jre/lib),系统扩展类加载器(ext/lib),应用类加载器(classpath),前者为c++编写,所以系统加载器的parent为空,后面两个类加载器都是通过启动类加载器加载完成后才能使用。
加载的过程就是查找字节流,可以通过网络,也可以自己在代码生成,也可以来源一个jar包。另外,同一个类,被不同的类加载器加载,那么他们将不是同一个类,java中通过类加载器和类的名称来界定唯一,所以我们可以在一个应用程序存在多个同名的类的不同实现。
第二步:链接:(验证,准备,解析) 验证主要是校验字节码是否符合约束条件,一般在字节码注入的时候关注的比较多。准备:给静态字段分配内存,但是不会初始化,解析主要是为了将符号引用转换为实际引用,可能会触发方法中引用的类的加载。
第三步:初始化,如果赋值的静态变量是基础类型或者字符串并且是final的话,该字段将被标记为常量池字段,另外静态变量的赋值和静态代码块,将被放在一个叫cinit的方法内被执行,为了保证cinit方法只会被执行一次,这个方法会加锁,我们一般实现单例模式的时候为保证线程安全,会利用类的初始化上的锁。 初始化只有在特定条件下才会被触发,例如new 一个对象,反射被调用,静态方法被调用等
-
对象头加上字段
-
字段重排序
-
独占缓存行 falseSharding
-
计算对象占用大小
-
Java对象的内存布局
java中每一个非基本类型的对象,都会有一个对象头,对象头中有(16字节,前八个字节markword)64位作为标记字段,存储对象的哈希码,gc信息,锁信息,另外64位存储class对象的引用指针,如果开启指针压缩的话,该指针只需要占用32位字节。
- 为什么是64位,工业界得出的结论最优, cpu在进行读取的时候,遵循的是按块读取,(程序运行原理:时间局部性/空间局部性),(即执行完当前指令后会很快执行下次或者挨着的空间的时间),充分发挥cpu针脚读取数据能力,提高效率
Java对象中的字段,会进行重排序,主要为了保证内存对齐,使其占用的空间正好是8的倍数,不足8的倍数会进行填充,所以想知道一个属性相对对象其始地址的偏移量需要通过unsafe里的fieldOffset方法,
内存对齐也为了避免让一个属性存放在两个缓存行中,disruptor中为了保证一个缓存行只能被一个属性占用,也会用空对象进行填充,因为如果和其他对象公用一个缓存行,其他对象的失效会将整个缓存行失效,影响性能开销,jdk8中引入contended注解来让一个属性独占一个缓存行,将注解属性移动到远离对象头的地方,处于不同缓存行,内部也是进行填充,用空间换取时间,
- 如何计算一个对象占用多少内存?
如果不精确的话就进行遍历然后加上对象头,这种情况没办法考虑重排序和填充,如果精确的话只能通过javaagent的instrument工具。
-
创建一个对象的过程
1:检查类是否已经被加载;
当程序遇到new关键字的时候,首先会去运行时常量池检查该引用所指向的类是否被JVM加载,如果么有被加载,会进行类的加载过程,如果已经被加载,那么进行下一步
2:为对象分配内存空间;
需要在堆内存中为该对象分配一定空间,空间的大小在类加载完成的时候就已经确定下来啦.
其中为对象分配空间方式有两种,
a. 指针碰撞(Bump the pointer) jvm将堆区抽象为两个区域,一个已经被占用,一个是空白区域,中间通过一个指针进行标注,这时只需要将指针向空白区域移动相应大小空间,就完成了内存分配,当然这种方式需要jvm的堆内存地址连续,且堆内存带有内存压缩机制,可以在分配完成时候压缩内存,形成连续的地址空间.这种分配方式成为"指针碰撞",但明显这种方式是有问题的,当多线程时候,划分内存会出现不一致的情况.当A线程刚将指针移到新位置,B线程读取了之前的指针地址,会出现. JVM对这样的情况采用了CAS配上失败重试的方式保证更新操作的原子性.
b.**空闲列表(Free List)**第二种也是微了第一种分配方式的不足而创建.
多线程分配内存时候,虚拟机为每个线程分配了不同的空间,这样每个线程再分配内存时候,只在自己的空间中操作,从而避免了上述问题,不需要同步,但是当线程空间用完了后,需要申请空间,这时候需要进行同步锁. 这种方式成为"本地线程分配缓冲空间(TLAB)"是否启用TLAB需要通过 -XX:+/-UseTLAB参数来设定
3:为对象字段设置零值;
分配完内存后需要对对象字段进行初始化 null 0 "" 等
4:设置对象头;
jvm对创建出来的对象进行信息标记,包括是否为新生代/老年代,对象的哈希码,元数据信息等,这些标记存放在对象信息头中
5:执行构造方法。
初始化对象。
源码到汇编
0 new #2
3 dup
4 invokespecial #3 <T.>
7 astore_1
8 return
eg:
public class T(){
int m = 8;
}
T t = new T();
0 t------------------------------->开辟一个内存空间
4 对对象字段 int m=0; 根据属性类型赋默认值
7 进行关系绑定,即设置对象信息头,标记新生代、老年代、哈希码,元数据信息
初始化对象,执行构造方法 int m = 8
-
对应问题 DCL(Double check Lock)单例是否需要volatile
CPU执行创建对象命令的时候,是会乱序执行的,(在这个时候,半初始化的时候)命令交换了,直接建立关联,可能就会导致新来的线程调用有问题。
因为volatile修饰的对象,不可以乱序执行,就能保证不会被拿到半初始化状态的对象
volatile 怎么保证不乱序呢, 通过内存屏障 jvm级别的屏障是一种规范不是具体实现,不是os级别、硬件级别的实现
写内存屏障(Store Memory Barrier):处理器将存储缓存值写回主存(阻塞方式)。
读内存屏障(Load Memory Barrier):处理器,处理失效队列(阻塞方式)。
保证两个操作之间数据的可见性。
volatile读前插读屏障,写后加写屏障,避免CPU重排导致的问题,实现多线程之间数据的可见性
对于处理器来说,内存屏障会导致cpu缓存的刷新,刷新时,会遵循缓存一致性协议。
lock**:解锁时,jvm会强制刷新cpu缓存,导致当前线程更改,对其他线程可见。
volatile**:标记volatile的字段,在写操作时,会强制刷新cpu缓存,标记volatile的字段,每次读取都是直接读内存。
final:即时编译器在final写操作后,会插入内存屏障,来禁止重排序,保证可见性
- 并发的本质 原子性、可见性、乱序执行 (乱序执行可以进行反证法)
- happen-before描述内存可见性 a happen-before b a线程的所有操作对线程b是可见的
管程:
文档:线程共享数据可见性.note 我的note有道的笔记 链接:http://note.youdao.com/noteshare?id=fe2e3867cfe1799c399da8fedf94770e
各个区域的功能不是本文重点,就不在这里详细介绍了。这里简单提几个需要特别注意的点:
1、以上是Java虚拟机规范,不同的虚拟机实现会各有不同,但是一般会遵守规范。
2、规范中定义的方法区,只是一种概念上的区域,并说明了其应该具有什么功能。但是并没有规定这个区域到底应该处于何处。所以,对于不同的虚拟机实现来说,是有一定的自由度的。
3、不同版本的方法区所处位置不同,上图中划分的是逻辑区域,并不是绝对意义上的物理区域。因为某些版本的JDK中方法区其实是在堆中实现的。
4、运行时常量池用于存放编译期生成的各种字面量和符号引用。但是,Java语言并不要求常量只有在编译期才能产生。比如在运行期,String.intern也会把新的常量放入池中。
5、除了以上介绍的JVM运行时内存外,还有一块内存区域可供使用,那就是直接内存。Java虚拟机规范并没有定义这块内存区域,所以他并不由JVM管理,是利用本地方法库直接在堆外申请的内存区域。
6、堆和栈的数据划分也不是绝对的,如HotSpot的JIT会针对对象分配做相应的优化。
如上,做个总结,JVM内存结构,由Java虚拟机规范定义。描述的是Java程序执行过程中,由JVM管理的不同数据区域。各个区域有其特定的功能。