Java 虚拟机:Java 内存区域及对象,java 反射面试
为以后写文章考虑,也为巩固自己的知识和一些基本概念,这里要理清楚几个计算机中的概念。
1、计算机存储单位
从小到大依次为位 Bit、字节 Byte、千字节 KB、兆 M、千兆 GB、TB,相邻单位之间都是 1024 倍,1024 为 2 的 10 次方,即:
1Byte = 8bit
1K = 1024Byte
1M = 1024K
1G = 1024M
1T = 1024G
2、计算机存储元件
寄存器:中央处理器 CPU 的一部分,是计算机中读写速度最快的存储元件,但是容量很少
内存:属于独立的一个部件,是和 CPU 沟通的桥梁,用于存放 CPU 中的运算数据以及与外部存储器交换的数据。尽管在今天,对内存的读写速度已经很快了,但是由于寄存器是在 CPU 上的,所以对于内存的读写速度和对于寄存器的读写速度上还是有几个数量级的差距。但是没办法,对于内存的读写 I/O 操作是很难消除的,寄存器数量有限,不可能通过寄存器来完成所有的运算任务
3、内核空间和用户空间
连接内存和寄存器的是地址总线,地址总线的宽度影响了物理地址的索引范围,因为总线宽度
决定了处理器一次可以从寄存器或内存中获取多少个 Bit,同时也决定了处理器最大可以寻址的地址空间。比如 32 位 CPU 的系统,可寻址范围为 0×00000000~0xFFFFFFFF,即 232=4294967296 个内存位置,每个内存位置 1 个字节,即 32 位 CPU 系统可以有 4GB 的内存空间。不过应用程序是不可以完全使用这些地址空间的,因为这些地址空间被划分为了内核空间和用户空间,程序只能使用用户空间的内存。内核空间主要是指操作系统运行时所使用的用于程序调度、虚拟内存的使用或者链接硬件资源的程序逻辑。区分内核空间和用户空间的目的主要是从系统的稳定性的角度考虑的。Windows 32 操作系统默认内核空间和用户空间的比例是 1:1,即 2G 内核空间、2G 内存空间,32 位 Linux 系统中默认比例则是 1:3,即 1G 内核空间,3G 内存空间。
4、字长
CPU 的主要技术指标之一,指的是 CPU 一次能并行处理二进制的位数(Bit)。通常称处理字长为 8 位数据的 CPU 为 8 位 CPU,32 位 CPU 就是在同一时间内处理字长为 32 位的二进制数据。不过目前虽然 CPU 大多是 64 位的,但还是以 32 位字长运行
前言
======
说到 Java 内存区域,可能很多人第一反应是“堆栈”。首先堆栈不是一个概念,而是两个概念,堆和栈是两块不同的内存区域,简单理解的话,堆是用来存放对象而栈是用来执行程序的。其次,堆内存和栈内存的这种划分方式比较粗糙,这种划分方式只能说明大多数程序员最关注的、与对象内存分配关系最密切的内存区域是这两块,Java 内存区域的划分实际上远比这复杂。对于 Java 程序员来说,在虚拟机自动内存管理机制的帮助下,不再需要为每一个 new 操作去配对 delete/free 代码,不容易出现内存泄露和内存溢出问题。但是,也正是因为 Java 把内存控制权交给了虚拟机,一旦出现内存泄露和内存溢出的问题,就难以排查,因此一个好的 Java 程序员应该去了解虚拟机的内存区域以及会引起内存泄露和内存溢出的场景。
运行时数据区域
===========
Java 虚拟机(JVM)内部定义了程序在运行时需要使用到的内存区域,从http://images.blogjava.net/blogjava_net/nkjava/jvmstructure.png拷贝一张图下来:
![](https://static001.geekbang.org/infoq/40/4032782803687bfa66ccaf3bcf73fe2f.webp?x-oss-process=image/resize,p_80/format,jpg)
之所以要划分这么多区域出来是因为这些区域都有自己的用途,以及创建和销毁的时间。有些区域随着虚拟机进程的启动而存在,有的区域则依赖用户线程的启动和结束而销毁和建立。图中绿色部分就是所有线程之间共享的内存区域,而白色部分则是线程运行时独有的数据区域,从这个分类角度来看一下这几个数据区。
1、线程独有的内存区域
(1)PROGRAM COUNTER REGISTER,程序计数器
这块内存区域很小,它是当前线程所执行的字节码的行号指示器,字节码解释器通过改变这个计数器的值来选取下一条需要执行的字节码指令。Java 方法这个计数器才有值,如果执行的是一个 Native 方法,那这个计数器是空的。
(2)JAVA STACK,虚拟机栈
生命周期和线程相同。每个方法执行的同时都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息,每一个方法从调用直至执行完毕的过程,就对应着一个栈帧在虚拟机中入栈到出栈的过程。栈的大小和具体 JVM 的实现有关,通常在 256K~756K 之间。
(3)NATIVE METHOD STACK,方法栈
和虚拟机栈起的作用一样,只不过方法栈为虚拟机使用到的 Native 方法服务。虚拟机规范并没有对这个区域有什么强制规定,因此我们使用的 HotSpot 虚拟机,就干脆没有这块区域了,它和虚拟机栈是一起的。
2、线程间共享的内存区域
(1)HEAP,堆
大多数应用,堆都是 Java 虚拟机所管理的内存中最大的一块,它在虚拟机启动时创建,此内存唯一的目的就是存放对象实例。由于现在垃圾收集器采用的基本都是分代收集算法,所以堆还可以细分为新生代和老年代,再细致一点还有 Eden 区、From Survivior 区、To Survivor 区,这个后面都会讲到的。
(2)METHOD AREA,方法区
这块区域用于存储虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据,虚拟机规范是把这块区域描述为堆的一个逻辑部分的,但实际它应该是要和堆区分开的。从上面提到的分代收集算法的角度看,HotSpot 中,方法区≈永久代。不过 JDK 7 之后,我们使用的 HotSpot 应该就没有永久代这个概念了,会采用 Native Memory 来实现方法区的规划了。
(3)RUNTIME CONSTANT POOL,运行时常量池
上面的图中没有画出来,因为它是方法区的一部分。Class 文件中除了有类的版本信息、字段、方法、接口等描述信息外,还有一项信息就是常量池,用于存放编译期间生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中,另外翻译出来的直接引用也会存储在这个区域中。这个区域另外一个特点就是动态性,Java 并不要求常量就一定要在编译期间才能产生,运行期间也可以在这个区域放入新的内容,String.intern()方法就是这个特性的应用。
3、直接内存
想想还是把这块加上。直接内存并不是虚拟机运行时数据区的一部分,也不是 Java 虚拟机规范中定义的内存区域。但是这部分内存也被频繁地使用,而且也可能导致内存溢出问题。JDK1.4 中新增加了 NIO,引入了一种基于通道与缓冲区的 I/O 方式,它可以使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在 Java 堆和 Native 堆中来回复制数据。显然,本机直接内存的分配不会受到 Java 堆大小的限制,但是,既然是内存,肯定还是会受到本机总内存(包括 RAM、SWAP 区)大小以及处理器寻址空间的限制。
对象创建
========
Java 是一门面向对象的语言,Java 程序运行过程中无时无刻都有对象被创建出来。在语言层面上,创建对象(克隆、反序列化)就是一个 new 关键字而已,但是虚拟机层面上却不是如此。看一下在虚拟机层面上创建对象的步骤:
1、虚拟机遇到一条 new 指令,首先去检查这个指令的参数能否在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已经被加载、解析和初始化。如果没有,那么必须先执行类的初始化过程。
评论