Java虚拟机的内存分区

JVM

前言

java虚拟机在执行java程序的时候,会把它所管理的内存分成若干个不同的运行时数据区,不同的运行时数据区,具有不同的作用,以及运行及销毁时间,有的数据区随着java虚拟机进程的启动而启动,有的数据去则依赖于用户线程的启动和结束而建立和销毁。

运行时数据区

java虚拟机所管理的内存将会划分为以下几个数据区域:

  • 程序计数器
  • java虚拟机栈
  • 本地方法栈
  • java堆
  • 方法区
  • 运行时常量池
  • 直接内存

程序计数器

程序技术器是比较小的内存区域,可以看作是当前线程所执行的字节码的行号指示器,java虚拟机的概念模型里,字节码解释器工作的时候就是通过改变程序计数器的值,来选取下一条要执行的字节码指令。

每个线程都有一个独立的程序计数器,各个程序计数器互不影响,独立存储,这种内存区域,成为线程的私有内存。

如果线程正在执行的是java方法,计数器记录的是解释器执行的字节码的指令的地址;如果执行的是native方法,则计数器的值为空,这个区域是唯一没有规定任何OutOfMemoryError情况的区域。

java虚拟机栈

存储的数据

java虚拟机栈跟程序计数器一样是线程私有的,生命周期跟线程相同。java虚拟机栈描述的java方法执行的内存模型:每个方法在执行的时候都会创建一个栈帧,用来存储局部变量表,操作数栈,动态连接,方法出口等信息。每一个方法的调用直到完成都对应着一个栈帧在虚拟机栈的入栈出栈的过程。

空间分配

局部变量表存放了编译的时候可以知道各种基本类型的数据以及引用类型的数据,局部变量表所需要的内存空间在编译时完成分配,执行一个方法的时候这个的方法的栈帧中的局部变量表分配多大的空间在编译时就已经确定了,方法执行的过程中不会改变栈帧的大小。

异常

这个区域对应了两种异常

  • 如果线程请求的栈的深度超过了虚拟机允许的栈的深度,会抛出StackOverFlow异常
  • 如果虚拟机栈是允许动态扩展的,如果扩展的时候无法申请到足够多的内存空间,就会抛出OutOfMemoryError异常

本地方法栈

本地方法栈跟java虚拟栈一样,只不过他是为native方法服务的,也具有上述的两种的异常。

java堆

存储的数据

java堆是虚拟机所管理的最大的一块内存区域,是所有线程所共享的,java堆得唯一目的是存放对象实例,几乎所有的对象实例都在堆上分配,当然并不绝对。

细分

java堆是垃圾回收的主要的区域,因此很多时候也被成为GC堆。
java堆细分

  • 从内存回收的角度,由于收集器都采用分代收集算法,java堆可以细分为新生代跟老年代,再细分一点还有Eden空间,From Survivor空间,TO Survivor空间等。
  • 从内存分配的角度,线程共享的堆中,可能会划分出线程所私有的分配缓冲区(TLAB).

异常

当对无法完成对象的内存分配,同时堆无法扩展的时候,会抛出OutOfMemoryERror.

方法区

存储的数据

多个线程所共享的内存区域,用来存储java虚拟机所加载的类信息(如类名,访问修饰符,字段描述,方法描述),常量,静态变量,即时编译器编译后的代码等数据。

永久代

方法区也习惯上被称为永久代,但是本质上两者并不等价,只不过是使用了永久代实现了方法区。使用永久代来实现方法区,很容易出现内存溢出的问题,官方也有放弃永久代的规划,在java 1.7中,已经把原本放在永久代的字符串常量池移出了。

垃圾回收

方法区可以再连续的内存上实现也可以在不连续的内存上实现,可以选择固定大小实现也可以是可扩展的,还可以选择不实现垃圾回收,垃圾回收在方法区出现的很少,方法区的垃圾回收目标主要是针对常量池的回收跟类型的卸载,这部分区域的回收效果不是很好,尤其是类型的卸载。
类型卸载的条件:

  • 该类的所有实例都已经被回收了;
  • 加载这个类的类加载器被回收了;
  • 这个类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射来访问这个类的方法;

满足了回收条件的类,也可以不被回收,可以通过设置jvm的参数,来决定回不回收满足无用的类。

异常

当方法去无法满足内存分配的需要的时候,会抛出OutOfMemoryError异常。

运行时常量池

存储的数据

运行时常量池是方法区的一部分。Class文件中除了有类的版本,字段,方法,还有常量池,常量池用来存放编译时生成的各种字面量跟符号引用,这部分内容在类加载后会进入,运行时常量池。

动态性

运行时常量池是跟Class文件常量池的区别是动态性,也就是说一个常量并不一定在编译期才能产生,也就是并非预置入Class文件中的常量池的内容才能进入方法去运行时常量池,运行期间也能将新的常量放入池中。应用的比较多的是String类的intern方法。

异常

当常量池无法再申请到内存的时候,会抛出OutOfMemoryError异常。

直接内存

直接内存并不是java虚拟机运行时内存的一部分,java虚拟机规范,也没有规定这一区域,但这一区域的使用很频繁,而且很可能导致异常,所以在此处一并给出。

在JDK 1.4中引入了NIO,NIO是基于管道(Channel)跟缓冲区的I/O方式,他可以使用Native函数库直接分配域外内存,然后通过一个存储在java中的DirectByteBuffer对象作为这块内存的引用进行操作,这避免了在java堆跟native堆中来回的复制数据,提高了效率。

本机的直接内存分配不会受到java堆大小的限制,但是会受到本地计算内存的限制。在配置jvm的参数的时候,如果忽视直接内存,很可能会出现内存溢出的错误。

当前网速较慢或者你使用的浏览器不支持博客特定功能,请尝试刷新或换用Chrome、Firefox等现代浏览器