目录
jvm运行时数据区包括堆、方法区、虚拟机栈、本地放法栈、程序计数器。
其中堆和方法区是线程共享的,而虚拟机栈、本地放法栈、程序计数器是线程私有的。
jdk1.6到jdk1.8运行时数据有所变化,jdk1.8将方法区彻底移除,取而代之的是元空间并且使用的是直接内存。
在jdk1.6时运行时数据区域分布如下:
在jdk1.8时运行时数据区域分布如下:
下面我们分别来了解一下每个区域:
堆
java所管理的内存最大的一块,这是线程共享的一块区域,在虚拟机启动时就会创建。堆主要就是放置对象实例的,几乎所有的对象实例以及数组都是在这里分配内存。
堆也是垃圾收集器管理的主要区域,从垃圾回收的角度来看,堆还可以分为:新生代和老年代。再细致一点又可分为Eden、from Survivor、To Survivor、tentired。这样划分的目的是为了更好的回收内存。
Eden区、S0、S1都属于新生代,tentired属于老年代。大部分情况下,对象都会先在eden区分配,在一次新生代垃圾回收后,如果对象还存活,则会进入S0或者S1,并且对对象的年龄加1,当年龄加到一定值时(默认是15),就会进入老年区。这里可以通过设置参数
-XX:MaxTenuringThreshold
来改变默认值。
方法区
方法区和堆区一样也是线程共享的。它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译的代码等数据。java虚拟机规范将方法区描述为堆的一个逻辑部分。⽅法区也被称为永久代。
⽅法区和永久代的关系
⽅法区和永久代的关系很像Java中接⼝和类 的关系,类实现了接⼝,⽽永久代就是HotSpot虚拟机对虚拟机规范中⽅法区的⼀种实现⽅式。
永久代是HotSpot的概念,⽅法区是Java虚拟机规范中的定义,是⼀种规范。⽽永久代是⼀ 种实现,⼀个是标准⼀个是实现,其他的虚拟机实现并没有永久带这⼀说法。
常⽤参数
JDK 1.8 之前永久代还没被彻底移除的时候通常通过下⾯这些参数来调节⽅法区⼤⼩
-XX:PermSize=N //⽅法区(永久代)初始⼤⼩
-XX:MaxPermSize=N //⽅法区(永久代)最⼤⼤⼩,超过这个值将会抛出
OutOfMemoryError异常:java.lang.OutOfMemoryError: PermGen
垃圾收集⾏为在这个区域是⽐少出现的,但并⾮数据进⼊⽅法区后就“永久存在”了。
JDK 1.8 的时候,⽅法区(HotSpot的永久代)被彻底移除了(JDK1.7就已经开始了),取⽽代之是元 空间,元空间使⽤的是直接内存。
可用下面的参数设置:
-XX:MetaspaceSize=N //设置Metaspace的初始(和最⼩⼤⼩)
-XX:MaxMetaspaceSize=N //设置Metaspace的最⼤⼤⼩
与永久代很⼤的不同就是,如果不指定⼤⼩的话,随着更多类的创建,虚拟机会耗尽所有可⽤的系统内存。
整个永久代有⼀个 JVM 本身设置固定⼤⼩上线,⽆法进⾏调整,⽽元空间使⽤的是直接内存,受本机 可⽤内存的限制,并且永远不会java.lang.OutOfMemoryError。你可以使⽤ -XX: MaxMetaspaceSize 标志设置最⼤元空间⼤⼩,默认值为 unlimited,这意味着它只受系统内存的限制。 -XX:MetaspaceSize 调整标志定义元空间的初始⼤⼩如果未指定此标志,则 Metaspace 将根据运⾏时的应⽤程序需求动态地重新调整⼤⼩。
运⾏时常量池
运⾏时常量池是⽅法区的⼀部分。Class ⽂件中除了有类的版本、字段、⽅法、接⼝等描述信息外,还有常量池信息(⽤于存放编译期⽣成的各种字⾯量和符号引⽤)在方法区中所以会受到⽅法区内存的限制,当常量池⽆法再申请到内存时会抛 出 OutOfMemoryError 异常。
jdk1.8及后面的JVM 已经将运⾏时常量池从⽅法区中移了出来,在 Java 堆(Heap)中开辟了⼀ 块区域存放运⾏时常量池。
Java 虚拟机栈
Java虚拟机栈是线程私有的,每个线程都有各⾃的Java虚拟机栈,它的⽣命周期和线程相同,随着线程的创建⽽创建,随着线程的死亡⽽死亡。,描述的是 Java ⽅法执⾏的内存模型,每次⽅法调⽤的数据都是通过栈传递的。
Java虚拟机栈是由⼀个个栈帧组成,⽽每个栈帧中都拥有:局部变量表、操作数栈、动态链接、⽅法出⼝信息。
局部变量表主要存放了编译器可知的各种数据类型(boolean、byte、char、short、int、float、 long、double)、对象引⽤(reference类型,它不同于对象本身,可能是⼀个指向对象起始地址的引⽤指针,也可能是指向⼀个代表对象的句柄或其他与此对象相关的位置)。
Java 虚拟机栈会出现两种异常:StackOverFlowError 和 OutOfMemoryError。若Java虚拟机栈的内存⼤⼩不允许动态扩展,那么当线程请求栈的深度超过当前Java虚拟机栈的最⼤深度的时候,就抛出OvStackerFlowError异常。若 Java 虚拟机栈的内存⼤⼩允许动态扩展,且当线程请求栈时内存⽤完 了,⽆法再动态扩展了,此时抛出OutOfMemoryError异常。
Java 栈可⽤类⽐数据结构中栈,Java 栈中保存的主要内容是栈帧,每⼀次函数调⽤都会有⼀个对应的 栈帧被压⼊Java栈,每⼀个函数调⽤结束后,都会有⼀个栈帧被弹出。
本地⽅法栈
本地⽅法栈和虚拟机栈所发挥的作⽤⾮常相似,区别是虚拟机栈为虚拟机执⾏ Java ⽅法 (也就是字节码)服务,⽽本地⽅法栈则为虚拟机使⽤到的 Native ⽅法服务。
本地⽅法被执⾏的时候,在本地⽅法栈也会创建⼀个栈帧,⽤于存放该本地⽅法的局部变量表、操作数 栈、动态链接、出⼝信息。 ⽅法执⾏完毕后相应的栈帧也会出栈并释放内存空间,也会出现 StackOverFlowError 和 OutOfMemoryError 两种异常。
程序计数器
程序计数器是⼀块很小的内存空间,可以看作是当前线程所执⾏的字节码的⾏号指示器。⼯字节码解释器工作时通过改变这个计数器的值来选取下⼀条需要执⾏的字节码指令,分⽀、循环、跳转、异常处理、 线程恢复等功能都需要依赖这个计数器来完。
为了线程切换后能恢复到正确的执⾏位置,每条线程都需要有⼀个独⽴的程序计数器,各线程之 间计数器互不影响,独⽴存储,所以程序计数器是线程私有的。
程序计数器是唯⼀⼀个不会出现 OutOfMemoryError 的内存区域,它的⽣命周期随着线程的创建 ⽽创建,随着线程的结束⽽死亡。
更多内容请移步个人博客:乌托邦