Java语言的一个非常重要的特点就是与平台的无关性。而使用Java虚拟机(JVM)是实现这一特点的关键。JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。之前市场上主要有三种主流的JVM,

  1. Sun公司的HotSpot
  2. BEA公司的JRockit
  3. IBM公司的J9 JVM

后来Sun公司和BEA公司都被oracle收购,并且oracle采用Sun公司的HotSpot和BEA公司的JRockit两个JVM中精华退出了 jdk1.8 的JVM。

下图是JVM的结构,非常重要,尤其是运行时区域。

运行时数据区

1、堆

JVM中最大的一块,主要用来存放对象实例和数组,几乎所有的对象实例都在这里分配内存。线程共享,内部会划分出多个线程私有的分配缓冲区(TLAB)。可以位于物理上不连续的空间,但是逻辑上要连续。

2、虚拟机栈

每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。线程私有,生命周期和线程一致。

3、方法区(非堆)

属于共享内存区域,存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

4、本地方法栈

本地方法栈(Native MethodStacks)与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java 方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native 方法服务。

5、程序计数器

程序计数器(Program CounterRegister)是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器。此内存区域是唯一一个在Java 虚拟机规范中没有规定任何 OutOfMemoryError 情况的区域。

6、直接内存

直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域,但是这部分内存也被频繁地使用,而且也可能导致OutOfMemoryError 异常出现。在JDK 1.4 中新加入了NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆里面的DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在Java 堆和Native 堆中来回复制数据。

堆的分代收集

JVM有多种垃圾回收算法,其中目前在用最经典的就是分代收集算法,这里优先记录下。

  • 永久代(Perm):主要保存class,method,field等对象,该空间大小,取决于系统启动加载类的数量,一般该区域内存溢出均是启动时溢出。java.lang.OutOfMemoryError: PermGen space
  • 老年代(Old):一般是经过多次垃圾回收(GC)没有被回收掉的对象。
  • 新生代(Eden):新创建的对象。
  • 新生代(Survivor0):经过垃圾回收(GC)后,没有被回收掉的对象。
  • 新生代(Survivor1):同Survivor0相同,大小空间也相同,同一时刻Survivor0和Survivor1只有一个在用,一个为空。

Java堆是垃圾收集器管理的主要区域,按照分代收集算法的划分,堆内存空间可以继续细分为年轻代,老年代。年轻代又可以划分为较大的Eden区,两个同等大小的From Survivor,To Survivor区。默认的Eden区和Survivor区的大小比例为8:1:1。在为新创建的对象分配内存的时候先将对象分配到Eden区和From Survivor区,在立即回收时,会将Eden区和Survivor区还存活的对象复制到To Survivor区中,如果To Survivor区的大小不能容纳存活的对象,会把存活的对象分配到老年区。总体来说,新创建的小对象会放在年轻代,年轻代的对象大多在下一次垃圾回收时被回收,老年代存储大的对象和存活时间长的对象。

附录:相关概念

1、基本类型和引用类型

JVM中,数据类型可以分为两类:基本类型引用类型。基本类型的变量保存原始值,即:他代表的值就是数值本身;而引用类型的变量保存引用值。“引用值”代表了某个对象的引用,而不是对象本身,对象本身存放在这个引用值所表示的地址的位置。

2、堆和栈

 栈是运行时的单位,而堆是存储的单位。在Java中一个线程就会相应有一个线程栈与之对应,这点很容易理解,因为不同的线程执行逻辑有所不同,因此需要一个独立的线程栈。而堆则是所有线程共享的。栈因为是运行单位,因此里面存储的信息都是跟当前线程(或程序)相关信息的。包括局部变量、程序运行状态、方法返回值等等;而堆只负责存储对象信息。

栈代表了处理逻辑,而堆代表了数据。 

堆中存的是对象,栈中存的是基本数据类型和堆中对象的引用。

在Java中,Main函数就是栈的起始点,也是程序的起始点。

3、引用类型

对象引用类型分:强引用、软引用、弱引用和虚引用

强引用:不会被回收。就是我们一般声明对象是时虚拟机生成的引用,强引用环境下,垃圾回收时需要严格判断当前对象是否被强引用,如果被强引用,则不会被垃圾回收。

软引用:内存充足,则不回收;不足则回收。软引用一般被做为缓存来使用。与强引用的区别是,软引用在垃圾回收时,虚拟机会根据当前系统的剩余内存来决定是否对软引用进行回收。如果剩余内存比较紧张,则虚拟机会回收软引用所引用的空间;如果剩余内存相对富裕,则不会进行回收。换句话说,虚拟机在发生OutOfMemory时,肯定是没有软引用存在的。

弱引用:会被回收。弱引用与软引用类似,都是作为缓存来使用。但与软引用不同,弱引用在进行垃圾回收时,是一定会被回收掉的,因此其生命周期只存在于一个垃圾回收周期内。

 

 

 

 

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐