JVM(一)

JVM主要包括两个子系统和两个组件:两个子系统分别是Class loader子系统和Execution engine(执行引擎) 子系统;两个组件分别是Runtime data area(运行时数据区域)组件和Native interface(本地接口)组件。

Class loader子系统

根据给定的全限定名类名(如 java.lang.Object)来装载class文件的内容到 Runtime data area中的method area(方法区域)。Java程序员可以extends java.lang.ClassLoader类来写自己的Class loader。

Execution engine子系统

执行classes中的指令。任何JVM specification实现(JDK)的核心都是Execution engine,不同的JDK例如Sun 的JDK 和IBM的JDK好坏主要就取决于他们各自实现的Execution engine的好坏。

Native interface组件

与native libraries交互,是其它编程语言交互的接口。当调用native方法的时候,就进入了一个全新的并且不再受虚拟机限制的世界,所以也很容易出现JVM无法控制的native heap OutOfMemory。

Runtime Data Area组件

这就是我们常说的JVM的内存了。

Java 虚拟机内存分布

Java 虚拟机内存模型是Java程序运行的基础。为了能使Java应用程序正常运行,JVM虚拟机将其内存数据分为:方法区(Method Area),Java栈区(Java stack),本地方法栈区(native method),Java 堆(heap)和程序计数器(program counter register),其中,和Java垃圾回收器打交道最多的就是堆了。

程序计数器(program counter register)

程序计数器(Program Counter Register)是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里(仅是概念模型,各种虚拟机可能会通过一些更高效的方式去实现),字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。

由于Java 虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)每一时刻只会执行一条线程中的指令,而其它线程必须被切换出去。因此,为了线程切换后能恢复到正确的执行位置,每条线程都必须有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。

如果线程正在执行的是一个Java 方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Native 方法,这个计数器值则为空(Undefined)。此内存区域是唯一一个在Java 虚拟机规范中没有规定任何OutOfMemoryError 情况的区域。

Java栈区(Java stack)

与程序计数器一样,Java 虚拟机栈(Java Virtual Machine Stacks)也是线程私有的,它的生命周期与线程相同。它保存了方法的局部变量、部分结果、并参与方法的调用和返回。

在Java 虚拟机规范中,允许Java栈的大小是动态的或者固定的。对这个区域规定了两种异常状况:将抛出StackOverflowError、OutOfMemoryError。如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError 异常;如果虚拟机栈可以动态扩展(当前大部分的Java 虚拟机都可动态扩展,只不过Java 虚拟机规范中也允许固定长度的虚拟机栈),当扩展时无法申请到足够的内存时会抛出OutOfMemoryError 异常。

public class TStrack {

    private int count = 0;

    public void recursion() {
        count++;
        recursion();
    }

    public static void main(String[] args) {
        TStrack t = new TStrack();
        try {
            t.recursion();
        } catch (Exception e) {
            System.out.println("deep of stack is " + t.count);
            return;
        }
    }

}

打印结果:
deep of stack is 9152
java.lang.StackOverflowErrorjava

在Hot Spot虚拟机中,可以使用 -Xss 参数设置栈的大小。栈的大小决定了函数可调用的深度。如果系统需要支持更深的栈调用,则可以使用参数 -Xss1M 运行程序,扩大栈空间的最大值。

虚拟机栈描述的是Java 方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈(方法调用)到出栈(方法返回)的过程。

如果方法调用时,方法的参数和局部变量相对较多,那么栈帧中局部变量表就会较大,栈帧会膨胀以满足方法调用所需传递的信息。因此,单个方法调用所需的栈空间大小也会比较多。一下代码中,随着调用函数参数的增加和局部变量的增加,单次函数调用对栈空间的需求也增加,嵌套函数调用的次数也减少。

public class TStrack {

    private int count = 0;

    public void recursion(long a, long b, long c) {
        long d = a + b + c;     // 占用了栈空间
        count++;
        recursion(a, b, c);
    }

    public static void main(String[] args) {
        TStrack t = new TStrack();
        try {
            t.recursion(1L, 2L, 3L);
        } catch (Exception e) {
            System.out.println("deep of stack is " + t.count);
            return;
        }
    }

}

在帧栈中,与性能调优关系最为密切的部分就是局部变量表。局部变量表用于存放方法的参数和方法内部的局部变量。局部变量表以“字”为单位进行内存划分,一个字为32位长度。对于long、double 型的变量,则占 2 个字,其余类型使用 1 个字。在方法执行时,虚拟机使用局部变量表完成方法的传递,对于非 static 方法,虚拟机还会将当前对象(this)作为参数通过局部变量表传给当前方法。


本地方法栈(native method)

本地方法栈与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java 方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native 方法服务。虚拟机规范中对本地方法栈中的方法使用的语言、使用方式与数据结构并没有强制规定,因此具体的虚拟机可以自由实现它。甚至有的虚拟机(譬如Sun HotSpot 虚拟机)直接就把本地方法栈和虚拟机栈合二为一。与虚拟机栈一样,本地方法栈区域也会抛出StackOverflowError 和OutOfMemoryError异常。

Java 堆(heap)

Java 堆可以说是 Java 运行时内存中最为重要的部分,是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。

Java 堆是垃圾收集器管理的主要区域,因此很多时候也被称做“GC 堆”(Garbage Collected Heap)。如果从内存回收的角度看,由于现在收集器基本都是采用的分代收集算法,所以Java 堆中还可以细分为:新生代和老年代;再细致一点的有Eden 空间、From Survivor 空间、To Survivor 空间等。如果从内存分配的角度看,线程共享的Java 堆中可能划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB)。不过,无论如何划分,都与存放内容无关,无论哪个区域,存储的都仍然是对象实例,进一步划分的目的是为了更好地回收内存,或者更快地分配内存。

根据Java 虚拟机规范的规定,Java 堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可,就像我们的磁盘空间一样。在实现时,既可以实现成固定大小的,也可以是可扩展的,不过当前主流的虚拟机都是按照可扩展来实现的(通过-Xmx和-Xms 控制)。如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError 异常。


方法区(Method Area)

方法区(Method Area)与Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类型信息、常量池、域信息、方法信息等数据。类型信息包括:类的完整名称、父类的完整名称、类型修饰符(private、protected、public)和类型的直接接口和域修饰符;常量池包括:该类的方法、域等信息所引用的常量信息;域信息包括域名称、域类型和域修饰符;方法信息包括:方法名称、返回类型、方法参数、方法修饰符、方法字节码、操作数栈、方法帧栈的局部变量区大小以及异常表。总之,方法区内保持的信息,大部分来自class文件,是Java应用程序必不可少的重要数据。

http://gull.iteye.com/blog/1109643

http://jbutton.iteye.com/blog/1569737

http://hllvm.group.iteye.com/group/wiki/2858-JVM

原文地址:https://www.cnblogs.com/xuekyo/p/2936039.html