i3geek.com
闫庚哲的个人博客

java虚拟机的内存划分

开源项目地址:https://github.com/yangengzhe/coding-guide_i3geek

java虚拟机是java程序运行的基础,所有的java程序在生成字节码.class后都被加载后运行在虚拟机上。每一个线程虚拟机都会为其划分出一定的内存空间,若想优化程序并了解运行原理,就必须掌握虚拟机中内存的分配。本文则主要介绍内存划分后的基本用途和概念。

java程序的执行过程

java_zxgc

如上图所示,在java源码书写完成后,首先需要通过java编译器进行编译,编译后生成.class字节码文件,才可以运行。

在运行中,首先由类加载器(class loader)进行加载相应类,具体的加载方法可以了解后续的文章。加载后,交由执行引擎执行,同时jvm也会分配出一块内存空间也就是运行时数据区(JVM 内存)。因此在虚拟机中的内存分配、划分或者是回收机制都是针对这块运行时数据区而言的。

JVM内存的主要部分

根据《Java虚拟机规范》的规定,运行时数据区(JVM 内存)主要包含:Java栈、堆、本地方法栈、程序计数器和方法区,五大部分。

分别的用途

Java栈

java栈也称作java虚拟机栈,也就是经常说的栈内存。既然叫做栈,顾名思义,就是在内存中以类似于栈的数据结构形式进行存储的。

栈中存放的是一个个的栈帧(Stack Frame),每一个栈帧代表一个被调用的方法。其中包括:局部变量表(Local Variables)、操作数栈(Operand Stack)、指向当前方法所属的类的运行时常量池(运行时常量池的概念在方法区部分会谈到)的引用(Reference to runtime constant pool)、方法返回地址(Return Address)和一些额外的附加信息。

当每一个线程执行每一个方法时,便会向栈中压人一个对应的栈帧,当执行完成后,便弹出栈帧达到销毁的目的。所以每一个线程会维护一个独立的栈。而且当前线程正在执行的方法的栈帧必定位于栈顶。同样,在程序递归调用时,容易产生栈内存溢出的问题。这部分空间是由系统进行自动的创建和释放的,对开发人员来说是透明的。

java_stack

  • 局部变量表:用来存储方法中的局部变量。对于基本类型变量则存储的是其值,若是引用类型变量则存储的是其堆内存的对象引用。局部变量表的大小,在编译器期间就可以确定,因此是不会发生变化的
  • 操作数栈:线程运行时,计算过程中借助的操作数栈
  • 指向运行时常量池的引用:因为在方法执行的过程中有可能需要用到类中的常量,所以必须要有一个引用指向运行时常量。
  • 方法返回地址:当方法执行完后,要返回之前调用它的地方,因此在栈帧中必须保存一个方法返回地址。

java中的堆内存(heap)主要是用来存储对象本身和数组的(引用均在栈内存中,堆只存储具体数据),java中的对象可以通过new 方法生成,但是对于对象的回收销毁并不用程序员所操心,Java的垃圾回收机制(GC)会自动进行处理。

因此,此后要研究的内存回收机制,java的垃圾收集器主要是针对这部分区域。另外,堆是被所有线程共享的,在JVM中只有一个堆。

本地方法栈

本地方法栈与Java栈的作用和原理非常相似。区别只不过是Java栈是为执行Java方法服务的,而本地方法栈则是为执行本地方法(Native Method)服务的。在JVM规范中,并没有对本地方发展的具体实现方法以及数据结构作强制规定,虚拟机可以自由实现它。在HotSopt虚拟机中直接就把本地方法栈和Java栈合二为一。

程序计数器

程序计数器也称为PC寄存器,是用来存储CPU执行时下一条指令的地址。当CPU要执行指令时,根据寄存器的地址获得下一条指令,同时程序计数器的地址加一或者跳到下一个指令的地址,待CPU处理完成后会再次向寄存器获取地址执行指令,如此循环直到程序执行完毕。

由于JVM中的多线程是通过CPU的轮流切换所实现的,因此,为了使线程在轮流切换后仍能继续执行刚刚的指令,所以每一个线程会单独维护一个程序计数器,切互不干扰的

在JVM规范中规定,如果线程执行的是非native方法,则程序计数器中保存的是当前需要执行的指令的地址;如果线程执行的是native方法,则程序计数器中的值是undefined。由于程序计数器中存储的数据所占空间的大小不会随程序的执行而发生改变,因此,对于程序计数器是不会发生内存溢出现象(OutOfMemory)的。

方法区

JVM的方法区类似于堆内存一样,主要存储了每个类的信息(类名、方法信息和字段信息)、静态变量、常亮以及编译器编译后的代码等。所以方法区在线程中是被共享的

方法区中有一个很重要的部分就是常量池,当每一个类和接口被加载到JVM后,对应的常量池就被创建出来。不仅class字节码文件的常量会写入运行时常量池,在运行期间创建的新的常量也会写入常量池中,如String的intern方法。

在JVM规范中,没有强制要求方法区必须实现垃圾回收。很多人习惯将方法区称为“永久代”,是因为HotSpot虚拟机以永久代来实现方法区,从而JVM的垃圾收集器可以像管理堆区一样管理这部分区域,从而不需要专门为这部分设计垃圾回收机制。不过自从JDK7之后,Hotspot虚拟机便将运行时常量池从永久代移除了。

总结

1、线程间相互独立的内存区

Java栈内存、本地方法栈和程序计数器

2、线程间相互共享的内存区

堆内存、方法区

3、需要依靠垃圾回收机制进行回收的内存区

堆内存

4、不会发生溢出的内存区

程序计数器

赞(0)
未经允许不得转载:爱上极客 » java虚拟机的内存划分
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址