java运行时数据区学习
引用文献 深入理解Java运行时数据区
前情回顾
在深入理解Java虚拟机到底是什么 这篇博客中, 我们有提到过, JVM就是一个特殊的进程, 我们执行的java程序, 都运行在一个JVM进程中, 这个进程的作用就是加载class文件, 并且执行class文件中的代码。 当然, 从一个class文件的加载, 到准备好可执行之前, 还有一段很长的路要走, 以后的文章会详细介绍这个过程。 既然虚拟机作为一个虚拟的计算机, 来执行我们的程序, 那么在执行的过程中, 必然要有地方存放我们的代码(class文件); 在执行的过程中, 总会创建很多对象, 必须有地方存放这些对象; 在执行的过程中, 还需要保存一些执行的状态, 比如, 将要执行哪个方法, 当前方法执行完成之后, 要返回到哪个方法等信息, 所以, 必须有一个地方来保持执行的状态。 上面的描述中, “地方”指的当然就是内存区域, 程序运行起来之后, 就是一个动态的过程, 必须合理的划分内存区域, 来存放各种数据。 所以, 在本文中, 将会详细介绍JVM的运行时数据区
JVM体系结构和运行时数据区概述
要理解JVM的运行时数据区,必须先要理解JVM的体系结构,因为虚拟机的体系结构基本上解释了“为什么会有这些运行时数据区”。JVM体现结构如下:
由此可见,运行时数据区的划分,是和JVM的体系结构相关的。本文主要介绍运行时数据区的划分,对体系结构不做深入的讲解。简单概括一下,类加载器子系统用于将class文件加载到虚拟机的运行时数据区中(准确的说应该是方法区)。可以认为执行引擎是字节码的执行机制,一个线程可以看做是一个执行引擎的实例。下面介绍运行时数据区:
JVM运行时数据区
方法区
在字面意思上,“方法区”这个词会让人产生误解。因为方法区存放的不只是方法,它存放的是类型信息。我们在写程序的时候,几乎总是在和类,对象打交道,我们知道根据一个类可以创建对象。一般来说,我们操纵的是对象,访问对象的属性,调用对象的方法等,但是我们要思考这样一个问题,虚拟机根据什么信息知道如何创建对象的呢?当然是根据这个对象的类型信息,但是这个类型信息在哪里呢?现在我们知道是在方法区中。那么类型信息是被谁加载到方法区中的呢?由上面的体系结构图,我们可以知道是类加载器子系统?那么所谓的类型信息,都包含什么信息呢?这些信息又是如何存放的呢?这里的类型信息,可以笼统的认为就是我们前面讲解过的一个class文件,类加载器子系统将会提取class文件里面的类型信息,并将这些类型信息存放到方法区中。至于方法区中如何存放一个类型数据,是和JVM的具体实现相关的。但是不管如何实现,一个类的类型信息总是会包含如下信息:
类的全限定名
当前类的直接父类的全限定名
这个类是接口类型,类类型,还是枚举类型
类的访问修饰符信息
当前类型的超接口的全限定名
当前类型的常量池
字段信息
方法信息
如果对class文件格式比较属性的话,可以看出,这些信息都是在class文件中描述过的。由于我们无法看到类型信息具体是如何存储的,但是大致可以将类型信息看做一个class文件,这有助于我们的理解。
类型数据中,除了这些基本信息外,类型信息还包括一下两个方面:
一个到类的ClassLoader对象的引用
一个到表示该类的Class实例对象的引用
静态变量存储区
每个class都是被一个类加载器加载到方法区,类型信息中的到类的ClassLoader对象的引用,表明了当前的类是被哪个类加载器加载的,这个信息同时也标示了当前的类型的名称空间。
每当一个class文件被成功的加载到方法区中,JVM总会创建一个Class对象,来唯一标示这个类。这个Class对象可以看做是类加载过程的产物,由于它描述了整个类型信息,而Java中的反射也是针对的类型信息,所以这个Class对象是反射的基石,大多数反射API都是根据Class对象来实现的。
而静态变量也是存在于类型信息中,可以这么说,类型信息中,会有专门的区域存放类的静态变量。与存在于对象中的实例变量不太,静态变量存在于类型数据中,每个类型只有一份,所以也叫类变量。
方法区是一个相对来说比较固定的内存区,因为它存放的是类型信息,而类型信息在被加载到方法区中之后,除了必要的连接和初始化,一般不会由较大改动,一般情况下,JVM也不会卸载类型信息,所以方法区也可以称为JVM的静态区。一个类型的生命周期一般就是整个程序的生命周期。这也是为什么要慎用静态变量的原因所在,因为静态变量随类型信息存放在方法区中,生命周期很长,如果使用不当,很容易造成内存泄露。一个JVM实例中只存在一个方法区,方法区中的所有类型数据被所有线程共享。
堆
方法区是存放类型数据的,而堆则是存放运行时产生的对象的。和C++ 不同的是,Java只能在堆中存放对象,而不能在栈上分配对象,所有运行时产生的对象全部都存放于堆中,包括数组。我们知道,在Java中,数组也是对象。一个JVM实例中只有一个堆,所有线程共享堆中的数据(对象)。
Java虚拟机支持几种不同的创建对象的指令,如 new , anewarray等。这些指令执行的结果就是在堆中分配内存,并创建对象。但是Java虚拟机的指令集中并不包含任何释放内存的指令,因而我们也就不能手动释放内存。所有被创建的对象都会被一个叫做垃圾收集器(GC)的模块自动回收,垃圾收集器有不同的实现方式,它们以特定的方式判断对象是否过期,并以特定的方式对对象进行回收,关于垃圾收集器的话题不是本文的重点,这里就不多说了。我们只要知道:所有创建的对象都存放在堆中,而垃圾收集器会自动回收过期的对象,所以,JVM的堆区是垃圾收集器的“重点管理区”。
Java栈
Java栈是一个线程的执行区域,它保存着一个线程中的方法的调用状态,也可以说,一个Java线程的运行状态,都由一个Java栈来保存。在这个栈中,每一方法对应一个栈帧,请注意区分栈帧和栈这两个概念。栈指的是整个线程的执行栈,栈帧是栈中的一个单位,每个方法对应一个栈帧。JVM会对Java栈执行两种操作:压栈和出栈。这两种操作在执行时都是以帧(栈帧)为单位的。当调用了一个新的方法,就会压入一个栈帧,当一个方法调用完成,就会弹出这个方法的栈帧,回到调用者的栈帧。
举例来说,如果方法a调用了方法b,而方法b中调用了方法c。这个过程的方法调用和返回的栈状态是这样的(其中图中两天虚线之间表示Java栈,每个方块表示一个特定方法的栈帧)
Java栈上的所有的数据都是线程私有的,也就是说,每个线程都会有自己的Java栈,不会相互访问其他Java栈中的数据。
PC寄存器
pc寄存器用于存放一条指令的地址,这条指令就是虚拟机要执行的下一条指令。pc寄存器和线程相关联,每一个线程都有一个PC寄存器。
本地方法栈
我们知道Java可以和C/C++互调。如果当前线程执行的代码是C/C++写的本地代码,那么这些方法就在本地方法栈中执行,而不会在Java栈中执行,Java栈中只执行Java方法。