Java运行时数据区域

  Java虚拟机定义了若干种程序运行期间会使用到的运行时数据区,其中一些会随着虚拟机启动而创建,随着虚拟机退出而销毁。另一些则是与线程一一对应的,这些与线程对应的数据区域会随着线程开始和结束而创建和销毁。主要分为以下几种数据区域:

  • 程序计数器
  • Java虚拟机栈
  • 本地方法栈
  • Java堆
  • 方法区
  • 运行时常量池


程序计数器

  程序计数器是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。Java虚拟机可以支持多条线程同时执行,每一条Java虚拟机线程都有自己的程序计数器,Java虚拟机的多线程是通过线程轮流切换并分配时间片来实现的,在任何一个确定的时刻,一个处理器(单核)都只会执行一条线程中的指令,为了多线程切换后还能恢复到正确的执行位置,因此每条线程都有一个独立的程序计数器,各条线程之间互不影响。就是线程隔离数据。

  如果现成正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果执行执行的是Native方法,这个计数器值则为空(undefined)。

  此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。

 

Java虚拟机栈

  与程序计数器一样,Java虚拟机栈为线程私有,每一条Java虚拟机线程都有自己私有的Java虚拟机栈,这个栈与线程同时创建,用于存储栈帧。Java虚拟机栈的作用与传统语言(例如c语言)中的栈相似,用于存储局部变量与一些尚未计算好的结果。另外,他在方法调用和返回中也扮演了很重要的角色。因为除了栈帧的出栈和入栈之外,Java虚拟机栈不会再受其他因素影响,所以栈帧可以在堆中分配,Java虚拟机栈所使用的内存不需要保证是连续的。

  Java虚拟机规范中既允许Java虚拟机栈实现成固定大小,也允许根据计算来动态扩展。

  Java虚拟机实现应当提供给程序员或者最终用户调节虚拟机栈初始容量的手段,对于可以动态扩展和收缩Java虚拟机栈来说,则应当提供调节其最大、最小容量的手段。

  Java虚拟机栈可能发生如下异常情况:

  • 如果线程请求分配的栈容量超过Java虚拟机栈允许的最大容量,Java虚拟机栈会抛出一个StackOverflowError异常。
  • 如果Java虚拟机栈可以动态扩展,并且在尝试扩展的时候无法申请到足够的内存,或者在创建新的线程时没有足够的内存去创建对应的虚拟机栈,那么Java虚拟机将会抛出OutOfMemoryError异常。

  可以通过以下参数设置Java虚拟机栈:

  -Xss108K

 

本地方法栈

  Java虚拟机实现可能会使用到传统的栈来支持Native方法的执行,这个栈就是本地方法栈。如果Java虚拟机不支持native方法,或是本身不依赖传统栈,那么可以不提供本地方法栈,日过支持本地方法栈,那这个栈一般会在线程创建的时候按线程分配。

  Java虚拟机规范允许本地方法栈实现成固定大小或者根据计算来动态扩展和收缩。

  Java虚拟机实现应当提供给程序员或者最终用户调节本地方法栈初始容量的手段,对于长度可动态变化的本地方法栈来说,则应当提供调节其最大、最小容量的手段。

  本地方法栈可能发生如下异常情况:

  • 如果现成请求分配的栈容量超过本地方法栈允许的最大容量,Java虚拟机会抛出一个StackOverflowError异常。
  • 如果本地方法栈可以动态扩展,并且在尝试扩展的时候无法申请到足够的内存,或者在创建新的线程是没有足够的内存去创建对应的本地方法栈,那么Java虚拟机将会抛出OutOfMemoryError异常。

  可以通过一下参数设置本地方法栈:

  -Xoss1M

  在Sun提供的HotSpot虚拟机中并不区分虚拟机栈和本地方法栈,对HotSpot来说,-Xoss参数无效。

 

Java堆

   在Java虚拟机中,堆是可供各个线程共享的运行时内存区域,也是供所有类实例和数组对象分配内存的区域。在虚拟机启动时创建,此区域的唯一目的就是存放实例对象,几乎所有的对象实例都在这里分配内存。但也不是绝对的,因逃逸分析的技术出现,会致使一些对象在栈上分配,这些对象会随着栈帧的出栈而销毁,这样大大减轻了GC回收的压力。Java堆所使用的内存不需要保证是连续的。

  Java虚拟机实现应当提供给程序员或者最终用户调节Java堆初始容量的手段,对于可以动态扩展或收缩Java堆来说,则应当提供调节其最大、最小容量的手段。

  Java堆可能发生如下异常情况:

  • 如果实际所需要的堆超过了自动内存管理系统能提供的最大容量,那Java虚拟机将会抛出一个OutOfMemoryError的异常。

  可以通过一下参数设置Java堆:

  • -Xms2G:设置 Java 应用程序启动时的初始堆大小;
  • -Xmx2G:设置 Java 应用程序能获得的最大堆大小;

 

方法区

  在Java虚拟机中,方法区是供各个线程共享的运行时内存区域,它存储了每一个类的结构信息,如运行时常量池、字段和方法数据、构造函数和普通方法的字节码内容,还包括一些在类、实例、接口初始化时用到的特殊方法。

  方法区在虚拟机启动的时候创建,虽然方法区是堆的逻辑组成部分,但有一个别名叫非堆,也有很多人成为是“永久代”,目的是与Java堆区分开,因此简单的虚拟机实现可以选择在这个区域不实现垃圾收集与压缩。方法区的容量可以是固定的,也可以随着程序执行的需求动态扩展,并在不需要过多空间时自动收缩,方法区在实际内存空间中可以是不连续的。

  Java虚拟机实现应当提供给程序员或者最终用户调节方法区初始容量的手段,对于可以动态扩展或收缩方法区来说,则应当提供调节其最大、最小容量的手段。

  方法区可能发生如下异常情况:

  • 如果方法区的内存空间不能满足内存分配需求,那么Java虚拟机将抛出一个OutOfMemoryError异常。

  可以通过一下参数设置方法区:

  JDK7

  • -XX:PermSize=5M 设置永久代初始大小
  • -XX:MaxPermSize=5M 设置最大上限

  JDK8

  从JDK8开始已经把永久代去掉,取而代之的是元空间(Metaspace),为什么从永久代切换到元空间?

  • 由于永久代内存经常不够用或发生内存泄露,爆出异常java.lang.OutOfMemoryError: PermGen
  • 动态类加载的情况越来越多,这块内存我们变得不太可控
  • 类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。
  • 永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。

  元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制,但可以通过以下参数来指定元空间的大小:

  -XX:MetaspaceSize,初始空间大小,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。
  -XX:MaxMetaspaceSize,最大空间,默认是没有限制的。

 

 

运行时常量池

   运行时常量池是Class文件中每一个类或接口的常量池表的运行时表示形式,它包括了若干种不同的常量,从编译期可知的数值字面量到必须在运行期解析后才能获得的方法或字段引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。

  在创建类或接口的运行时常量时,可能会发生如下异常情况:

  当创建类或接口时,如果构造运行时常量池所需要的内存空间超过了方法区所能提供的最大值,那么Java虚拟机将会抛出一个OutOfMemoryError异常。

 

posted on 2018-07-20 15:50  soinve  阅读(219)  评论(1编辑  收藏  举报

导航

欢迎

Soinve的博客