JVM 体系结构

dbww4269 8年前
   <p>JVM 体系结构分为三部分:1. 类加载器(ClassLoader):用于装载 .class 文件 2. 执行引擎:用于执行字节码,或者执行本地方法 3. 运行时数据区:包括方法区、堆、Java 栈、PC 寄存器、本地方法栈</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/f19fa06867cdb66272f6055685d9e4a6.png"></p>    <p>JVM 体系结构分为三部分:1. 类加载器(ClassLoader):用于装载 .class 文件</p>    <p>2. 执行引擎:用于执行字节码,或者执行本地方法</p>    <p>3. 运行时数据区:包括方法区、堆、Java 栈、PC 寄存器、本地方法栈</p>    <p><img src="https://simg.open-open.com/show/a8859978769ce5a604842ab115fbd11e.png"></p>    <p>程序计数器</p>    <p>占用很小的内存空间,可以看做是当前线程所执行的字节码的行号指示器。</p>    <p>JVM 的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个时刻一个处理器(或多个处理器的一个内核)只会执行一个条线程的指令。程序计数器用于在线程切换后为了恢复在正确执行的位置(正在执行的字节码指令的地址)。</p>    <p>每个线程都需要一个独立的程序计数器,它的生周期与当前线程相同。</p>    <p>虚拟机栈</p>    <p>JVM 栈是线程私有的,每个线程创建的同时都会创建 JVM 栈,JVM 栈是以栈帧为数据单元。</p>    <p>栈帧中存放着局部变量表(Java 中定义的八种基本类型:boolean、char、byte、short、int、long、float、double 和引用类型 —— 一个指向堆上的地址)、返回地址以及操作数栈。</p>    <p>每一个方法被调用直至执行完成的过程,就是对于的一个栈帧在虚拟机栈中从入栈到出栈的过程。</p>    <p>每个线程都有一个独立的栈,生命周期与当前线程相同。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/7e138e1ed9fe8df4c6a26a746818fd42.png"></p>    <p>栈帧是一个后入先出(LIFO)的栈。Sun JVM 采用基于栈(内存)的指令集来实现操作数栈,并把一些访问最频繁的数据放到寄存器中以提高性能(另一种基于 CPU 寄存器的指令集访问速度快,但可移植性差)。</p>    <p>E.g.</p>    <p>int a =  100 ;</p>    <p>int b =  98 ;</p>    <p>int c = a   b;</p>    <p>字节指令如下;</p>    <pre>  <code class="language-java">bipush 100   istore_1   bipush 98   istore_2   iload_1   iload_2   iadd   istore_3</code></pre>    <p>本地方法栈</p>    <p>与虚拟机栈相比,本地方法栈是为虚拟机使用到的本地(Native)方法服务的。此区域用于存储每个 Native 方法调用的状态。Sun HotSpot 虚拟机直接把本地方法栈和虚拟机栈合二为一。</p>    <p>每个线程都有一个独立的本地方法栈,生命周期与当前线程相同。</p>    <p>堆</p>    <p>Java 虚拟机管理的最大一块内存。Heap 是大家最为熟悉的区域,它是 JVM 用来存储对象实例以及数值的区域,可以认为 Java 中所有通过 new 创建的对象的内存都在此分配,Heap 中的对象的内存需要等待 GC  进行回收。</p>    <p>堆是在同一个 JVM 中所有线程共享的,因此在其上进行对象内存的分配君需要进行枷锁,这也导致了 new 对象的开销是比较大的。(频繁的创建对象会带来 GC 增加,可采用对象池技术来优化)。</p>    <p>JVM 将 Heap 分为 New Generation 和 Old Generation(方法 GC)。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/da17d4052fbbf13274e03dad04f15954.jpg"></p>    <p>New Generation 称为新生代,程序中创建的对象都将分配到新生代中,新生代又由 Eden Space 和两块 Survivor Space 构成,可通过 -Xmn 参数来指定其大小。</p>    <p>Old Generation 称为老生代,用于存放程序中经过几次垃圾回收还存活的对象(或者大对象),例如缓存的对象等,老生代所占用的内存大小即为 -Xmx 制定的大小减去 -Xmn 指定的大小。</p>    <p>方法区</p>    <p>方法区存放了所加载的类的信息(名称、修饰符等)、类中的静态变量、类中的定义为 final 类型的常量、类中 Field 信息、类中的方法信息,当在程序中通过 Class 对象中的 getName、isInterface 等方法来获取信息时,这些数据都来源于方法区。</p>    <p>在 Sun JDK 中这块区域对应的是 Permanet Generation,又称为持久代,默认为 64M,可通过 -XX:PermSize 以及 -XX:MaxPermSize 来指定其大小。</p>    <p>方法区是全局共享的,在一定的条件下它会被 GC,当方法区需要使用的内存超过其允许的大小时,会抛出 OutOfMemory 的错误。</p>    <p>方法区之运行时常量池</p>    <p>运行时常量池用于存放编译器生成的各种字面量和符号引用,这部分将在类加载后存放到方法区的运行时常量池中。</p>    <pre>  <code class="language-java">public class Sample {       private int a;         public int method() {           int b= 0;           a++;           b =a ;           return b;       }         public static void main(String[] args) {           Sample s = null;           int a = 0;           s = new Sample();           a = s.method();           System.out.println(a);      }  }</code></pre>    <p style="text-align:center"><img src="https://simg.open-open.com/show/ca22b8d0e0fa1863892bb2fcd6c17cd7.png"></p>    <p>Java 对象占用</p>    <p>每个对象由4个域构成:</p>    <p>1. 对象头,表述 Object 当前状态的信息(普通对象占 8字节,数组占12字节)</p>    <p>2. 基本类型(boolean、byte 占 1字节,char、short 占 2字节,int、float 占 4字节,long、double 占 8字节。</p>    <p>3. 引用类型占 4字节</p>    <p>4. 填充物,不足8的倍数的时候,自动补齐</p>    <p>E.g.</p>    <p>一个空对象(没有声明任何变量)占 8字节(对象头占 8字节)</p>    <p>只声明了一个 boolean 类型变量的类,占用 16字节(对象头占 8字节,boolean 占 1字节,填充物占 7字节)</p>    <p>声明了 8个 boolean 类型变量的类,占用 16字节(对象头占 8字节,boolean 占 1字节 * 8</p>    <p> </p>    <p>来自:http://www.androidchina.net/5774.html</p>    <p> </p>