JVM1-运行机制内存工作原理

JVM 规范

JVM可以运行除了java之外的很多语言,如groovy,jruby,scala。JVM规范包括:

1、class文件格式
2、数据的内部表示和存储
3、returnAddress 数据类型定义
4、invokedynamic
5、定义pc
6、堆、栈、方法区
7、 类型转化
8 、出栈入栈
9 、运算
10、流程控制
11、函数调用
12 对javaLibrary提供支持、反射、类加载、初始化类。安全、多线程、弱引用

运行时数据区的划分

  • 程序计数器
  • java堆
  • java虚拟机栈
  • 本地方法栈
  • 方法区

程序计数器

  一块较小的内存空间,他的作用可以看做是当前线程所执行的字节码的行号指示器。线程私有的内存区域,生命周期与线程相同。
  如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是native方法,则个计数器值则为空。
  此内存区域是唯一一个在java虚拟机规范中没有任何OutOfMemoryError情况的区域。
  Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多喝处理器老说是一个内核)都只会执行一条线程中的指令。为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的计数器。

JVM栈

  栈上分配 :小对象,不存在内存逃逸的情况下,直接分配在栈上。可以自动回收,减轻GC压力,大对象或者逃逸对象无法栈上分配 。内存逃逸指被多个线程共享。

  • 执行字节码
  • 线程私用
  • 存储栈帧,执行java方法的调用、执行和退出
  • 存在OutofMemoryError 和 stackOverFlow

本地方法栈NativeMethodStack

  本地方法栈与JVM栈所发挥的作用是非常相似的,他们之间的区别不过是虚拟机栈为虚拟机执行Java方法服务,本地方法栈为JVM使用到的Native方法服务

  • 线程私用
  • 执行Native方法的调用、执行和退出
  • OutofMemoryError 和 stackOverFlow
  • hotspot将栈与本地方法栈合并。
  • 方法结束自动释放内存

栈帧 stack frame

  JVM栈中存储数据的内容,他被用于存储局部变量表、操作数和部分过程结果的数据结构,同时也被用来处理动态链接,方法返回值和异常分派。
  包含局部变量表,操作数栈,动态链接信息,方法正常完成、异常完成信息
  局部变量由如若干slot组成,长度由编译器决定。单个slot可以存储一个基本类型、reference和returnAddress的数据,两个slot可以存储一个类型为long或double的数据。reference 直接或间接的找到对象,直接或间接的找到对象在方法区中的数据类型。
第一个为本对象的引用地址。
每个方法创建一个栈帧,并且压入栈。
操作数栈,进行数学计算 。

Java堆

  JVM明确要求该区域需要实现自动内存管理。从内存回收的角度来看,由于现在收集器基本都采用分代收集算法,所以Java堆中还可以细分为:新生代和老年代:再细致一点的有Eden空间、FromSurvior空间,To Survivor、tenured 。从内存分配的角度来看,线程共享的Java堆中可能划分出多个私有的分配缓冲区(TLAB)。存在OutofMemoryError。

方法区(别名 :Method Area、Non-Heap、永久代、permanent Gerneration)

  • 线程共享。
  • 作用是存储Jva类的类型信息、常量、静态变量、即时编辑后的代码等数据。
  • JVM不要求该区域实现自动内存管理,常量池的回收已经类型信息的卸载
  • JDK1.2-1.6 hotspot 使用永久代与方法区 jdk1.7 使用堆

运行时常量池(Runtime Constant Pool)

  • 全局共享
  • 方法区的一部分
  • 作用是存储Java类文件常量池中的数据信息、字面量和符号引用
  • 并非只有类加载使用,运行期也可以动态添加,如String.intern()

直接内存

jdk1.4中nio被引入,目的是避免在java堆和native堆中来来回复制数据带来的性能损失。全局共享。

对象创建

  假设Java堆中内存是绝对规整的,所有用过的内存都放在一边,空闲的内存放在另一边,中间放着一个指针作为分界点的指示器,这种分配方式成为“指针碰撞”。相对的分配方式成为“空闲列表”。
  对象内存分配的同步:(1)采用CAS配上失败重试的方式保证更新操作的原子性(2)内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在Java堆中预先分配一块内存,本地线程缓冲(TLAB)。
  对象头(Object Header)存放对象哈希码、对象的GC分代、类的元数据信息。

对象的内存布局

  对象头、实例数据、和对齐填充(Padding)。对象头包含两部分信息,第一部分用于存储对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等、这部分数据的长度在32位和64位,官方称它为Mark Word。25位存储哈希码、4位存储对象分代年龄、2位用于存储锁标志

对象的访问定位

(1)使用直接指针访问方式的最大好蠢就是速度更快,他节省了一次指针定位 的时间开销

(2)使用句柄

Java反编译

javap -verbose Test01 反编译java字节码 查看java字节码

happen-before原则(指令重排原则)

  • 程序顺序原则:一个程序内保证语意的串行
  • volatile规则:volatile变量的写必须先于读 。
  • 锁原则: 解锁必然先于加锁
  • 传递性
  • 线程的start方法优先于他的每一动作
  • 线程的所有操作先于线程的中断
  • 线程中断先于被中断线程的代码

- 构造函数先于finalize()

解释运行

  • 解释执行以解释方式运行字节码
  • 解释执行的意思是读一句执行一句

编译运行JIT

  • 将字节码编译成机器码
  • 直接执行机器码
  • 运行时编译
  • 速度快十倍