深入理解Java运行时数据区

前情回顾

在本专栏的前12篇博客中, 我们主要大致介绍了什么是JVM, 并且详细介绍了class文件的格式。 对于深入理解Java, 或者深入理解运行于JVM上的其他语言, 深入理解class文件格式都是必须的。 如果读者对class文件的格式不是很熟悉, 在阅读本博客下面的文章之前, 建议先读一下前面的12篇博客, 或者参考其他资料, 熟悉class文件的格式。

在深入理解Java虚拟机到底是什么 这 篇博客中, 我们有提到过, JVM就是一个特殊的进程, 我们执行的java程序, 都运行在一个JVM进程中, 这个进程的作用就是加载class文件, 并且执行class文件中的代码。 当然, 从一个class文件的加载, 到准备好可执行之前, 还有一段很长的路要走, 以后的文章会详细介绍这个过程。 既然虚拟机作为一个虚拟的计算机, 来执行我们的程序, 那么在执行的过程中, 必然要有地方存放我们的代码(class文件); 在执行的过程中, 总会创建很多对象, 必须有地方存放这些对象; 在执行的过程中, 还需要保存一些执行的状态, 比如, 将要执行哪个方法, 当前方法执行完成之后, 要返回到哪个方法等信息, 所以, 必须有一个地方来保持执行的状态。 上面的描述中, “地方”指的当然就是内存区域, 程序运行起来之后, 就是一个动态的过程, 必须合理的划分内存区域, 来存放各种数据。 所以, 在本文中, 将会详细介绍JVM的运行时数据区。

JVM体系结构和运行时数据区概述

要理解JVM的运行时数据区, 必须先要理解JVM的体系结构, 因为虚拟机的体系结构基本上解释了“为什么会有这些运行时数据区” 。 在深入理解Java虚拟机到底是什么 这篇文章中也简单的提到过JVM的体系机构, 这里再详细的讲解一下。 JVM的体系结构如下:

由此可见, 运行时数据区的划分, 是和JVM的体系结构相关的。 本文主要介绍运行时数据区的划分, 对体系结构不做深入的讲解。 简单概括一下, 类加载器子系统用于将class文件加载到虚拟机的运行时数据区中(准确的说应该是方法区) 。 可以认为执行引擎是字节码的执行机制, 一个线程可以看做是一个执行引擎的实例。 下面介绍运行时数据区:

JVM运行时数据区

方法区

在字面意思上, “方法区”这个词会让人产生误解。因为方法区存放的不只是方法, 它存放的是类型信息。我们在写程序的时候, 几乎总是在和类, 对象打交道, 我们知道根据一个类可以创建对象。 一般来说, 我们操纵的是对象, 访问对象的属性, 调用对象的方法等, 但是我们要思考这样一个问题, 虚拟机根据什么信息知道如何创建对象的呢? 当然是根据这个对象的类型信息, 但是这个类型信息在哪里呢?现在我们知道是在方法区中。 那么类型信息是被谁加载到方法区中的呢?由上面的体系结构图, 我们可以知道是类加载器子系统?那么所谓的类型信息, 都包含什么信息呢?这些信息又是如何存放的呢?这里的类型信息, 可以笼统的认为就是我们前面讲解过的一个class文件,类加载器子系统将会提取class文件里面的类型信息,并将这些类型信息存放到方法区中。 至于方法区中如何存放一个类型数据, 是和JVM的具体实现相关的。 但是不管如何实现, 一个类的类型信息总是会包含如下信息: 类的全限定名 当前类的直接父类的全限定名 这个类是接口类型, 类类型, 还是枚举类型 类的访问修饰符信息 当前类型的超接口的全限定名 当前类型的常量池 字段信息 方法信息 如果对class文件格式比较熟悉的话, 可以看出, 这些信息都是在class文件中描述过的。 由于我们无法看到类型信息具体是如何存储的, 但是大致可以将类型信息看做一个class文件, 这有助于我们的理解。下面再次列出class文件结构的表格,读者可以对比class文件中的内容到类型数据上, 该表中的各种数据已经在前面的博客中详细讲解过:

类型数据中,除了这些基本信息外, 类型信息还包括以下两个方面: 一个到类的ClassLoader对象的引用 一个到表示该类的Class实例对象的引用

静态变量存储区 由于之前的博客中详细介绍过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方法。

原文发布于微信公众号 - java一日一条(mjx_java)

原文发表时间:2015-06-12

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Java帮帮-微信公众号-技术文章全总结

Java多线程详解2

Java多线程详解 Java线程:线程的同步与锁 一、同步问题提出 线程的同步是为了防止多个线程访问一个数据对象时,对数据造成的破坏。 例如:两个线程Threa...

37170
来自专栏博岩Java大讲堂

Java集合--非阻塞队列(ConcurrentLinkedQueue基础)

42660
来自专栏Java架构师历程

JVM加载class文件的原理

当Java编译器编译好.class文件之后,我们需要使用JVM来运行这个class文件。那么最开始的工作就是要把字节码从磁盘输入到内存中,这个过程我们叫做【加载...

53920
来自专栏陈树义

Java并发编程:线程控制

在上一篇文章中(Java并发编程:线程的基本状态)我们介绍了线程状态的 5 种基本状态以及线程的声明周期。这篇文章将深入讲解Java如何对线程进行状态控制,比如...

40990
来自专栏我是攻城师

深入理解Java类加载器机制

Java里面的类加载机制,可以说是Java虚拟机核心组件之一,掌握和理解JVM虚拟机的架构,将有助于我们站在底层原理的角度上来理解Java语言,这也是为什么我们...

35620
来自专栏算法channel

Ubuntu|GDB调试常用命令

backtrace(或bt)查看各级函数调用及参数finish连续运行到当前函数返回为止,然后停下来等待命令frame(或f) 帧编号选择栈帧info(或i) ...

31640
来自专栏ios 技术积累

ios内存管理-内存管理范围

1、栈区(stack) — 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。 2、堆区(heap) — 一般由程序员分配释放,若程序员不释放...

14820
来自专栏与神兽党一起成长

使用commons-pool管理FTP连接

在封装一个FTP工具类文章,已经完成一版对FTP连接的管理,设计了模板方法,为工具类上传和下载文件方法的提供获取对象和释放对象支持。

14720
来自专栏栗霖积跬步之旅

java多线程编程核心技术——第二章总结

第一节synchronized同步方法目录 1.1方法内的变量为线程安全的 1.2实例变量非线程安全 1.3多个对象多个锁 1.4synch...

218100
来自专栏WindCoder

Java基础小结(三)

以上这些类是传统遗留的,在Java2中引入了一种新的框架-集合框架(Collection)

7110

扫码关注云+社区

领取腾讯云代金券