4 Mar 2017

java虚拟机:运行时数据区域, 垃圾回收, 类的加载机制

运行时数据区域

  1. PC(program counter register)-程序计数器
  2. 虚拟机栈
  3. 本地方法栈
  4. 方法区

PC

pc:根据计数器中的值来选取下一条需要执行的字节码指令。任何一个确定的时刻,一个处理器都只会执行一条线程中的指令,为了线程切换后能恢复到正确的执行位置, 每条线程都需要有一个独立的程序计数器。各条线程之间的计数器互不影响, 独立存储, 称这类内存区域为’线程私有’。

Java虚拟机栈

线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时会创建一个栈帧用于存储局部变量表、 操作数栈、 动态链接、 方法出口等信息。 **每一个方法从调用直至执行完成的过程, 就对应着一个栈帧在虚拟机中入栈到出栈的过程。
局部变量表存放了编译器可知的各种基本数据类型、 对象引用类型和returnAddress类型。局部变量表所需的内存空间是在编译期间完成分配, 当进入一个方法时, 这个方法需要在帧中分配多大的局部变量空间时完全确定的, 在方法运行期间不会改变局部变量表的大小。

本地方法栈(Native Method Stack)

Native Method:简单地讲,一个Native Method就是一个java调用非java代码的接口。
本地方法栈与虚拟机栈发挥的作用时非常相似的, 区别不过时虚拟机栈为Java方法服务, 本地方法栈则为虚拟机使用到的Native方法服务。–>功能与虚拟机栈相似, 服务对象不同. 线程私有的

Java堆

Java堆时被所有线程共享的一块内存区域, 在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,堆也是垃圾收集器管理的主要区域, 因此很多时候被称为GC堆。堆可以处于物理上不连续的内存空间, 只要逻辑上时连续的即可。

方法区(Method Area)

线程共享的内存区域, 它用于存储已被虚拟机加载的类信息、 常量、 静态变量、 即时编译器编译后的代码等数据。运行时常量池是方法区的一部分。用于存储编译期生成的各种字面量和符号引用。

垃圾回收

通常时针对Java堆的。

判断对象是否存活?

  1. 引用计数法:难以解决对象直接相互循环引用的问题。于是引用计数算法无法通知GC收集器回收它们。
  2. 可达性分析算法(GC Roots):通过一系列称为GC Roots的对象作为起始点, 从这些节点开始向下搜索, 当一个对象到GC Roots没有任何引用链相连接时(从GC Roots到这个对象不可达), 则证明此对象是不可达的。
    可作为GC Roots的对象包括下面几种:
    1. 虚拟机栈(栈帧中的局部变量表)中的引用的对象。
    2. 本地方法栈中Native方法引用的对象。
    3. 方法区中类静态属性引用的对象。
    4. 方法区中常量引用的对象。

垃圾收集算法

  1. 标记-清除算法(Mark-Sweep):先标记出所有需要回收的对象, 在标记完成后统一回收所有被标记的对象。
    标记过程:至少经历两次标记过程:
    1.如果对象在进行可达性分析后没有与GC Roots相连接的引用链, 那么它将会被第一次被标记并进行一次筛选, 筛选的条件是此对象是否有必要执行finalize()方法。如果对象没有覆盖finalize()方法或finalize()方法已经被虚拟机调用过, 则视为”没有必要执行”。
    2.如果对象被判定有必要执行finalize()方法,那么这个对象将会被放置在一个叫做F-Queue的队列中, 并在稍后由一个虚拟机自动建立的、 低优先级的Finalizer线程去执行它。稍后GC将对F-Queue中的对象进行第二次小规模标记, 如果这时候对象还没有逃脱(没有重新与引用链上的任何一个对象建立关联),那么它就真的被回收了。
    算法的问题:
    1. 可能产生大量不连续的内存碎片
    2. 标记和清除两个过程的效率都不高。
  2. 复制算法(Copying):将可用内存按容量大小划分为大小相等的两块。每次只使用其中一块,当这一块内存用完了, 就将还存活着的对象复制到另外一块上面, 然后再把已使用过的内存空间一次清理掉。实现简单,运行高效。但代价是将内存缩小为了原来的一半,过多了。 目前都采用这种收集算法来回收新生代。

  3. 标记-整理算法(Mark-Compact):复制收集算法在对象存活率较高时就要进行较多的复制操作,效率变低。根据老年代的特点,提出了标记-整理算法。标记过程标记-清除算法一样, 但后续步骤不是直接堆可回收对象进行清理, 而是让所有存活的对象都向一端移动, 然后直接清理掉端边界以外的内存。

  4. 分代收集算法:一般是把Java堆分为新生代和老年代, 这样就可以根据各个年代的特点采用最适当的收集算法。新生代使用复制算法, 老年代使用标记-整理或标记-清理算法来进行回收。

新生代:Eden, from-survivor,to-survivor。 8:1:1 老年代:

类的加载机制

Java虚拟机中类加载的全过程, 也就是:

  1. 加载
  2. 验证
  3. 准备
  4. 解析
  5. 初始化

    加载

    加载是类加载过程的一个阶段, 在加载阶段虚拟机需要完成一下三件事:

  6. 通过一个类的全限定名来获取定义此类的二进制字节流
  7. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
  8. 在内存中生成一个代表这个类的java.lang.Class对象, 作为方法区这个类的各种数据的访问入口。

验证

验证时连接阶段的第一步, 这一阶段的目的时为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求。整体上看, 验证阶段大致上会完成下面4个阶段的检验动作: 文件格式验证、 元数据验证、 字节码验证、 符号引用验证。

准备

正式为类变量(被static修饰的变量)分配内存并设置类变量初始值的阶段, 这些变量所使用的内存都将在方法区中进行分配。这里的初始值通常情况下是数据类型的零值,如果类字段的字段属性为ConstantValue属性那么在准备阶段变量value就会被初始化为ConstantValue属性所指定的值(见value2),假设一个类变量的定义为:
public static int value = 123; //value is 0 在准备阶段
public static final int value2 = 123; // value is 123 在准备阶段 那变量value在准备阶段后的初始值为0而不是123, 把value赋值为123的指令在初始化阶段才会执行。

解析

虚拟机将常量池内的符号引用替换为直接引用的过程。

初始化

初始化阶段时执行类构造器方法的过程。


Tags:
Stats: