jvm内存管理

这几天在学习jvm内存结构,网上的资料有些混乱,而且关于一些知识点并没有说清楚,这里自己总结了一下。

jvm内存划分区域

JVM 在执行 Java 程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域都有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,有些区域则依赖用户线程的启动和结束而建立和销毁。如下图所示:
jvm内存结构
如图所示,其中方法区和堆是所有线程共享的,虚拟机栈、本地方法栈、程序计数器则是线程私有的,其中我们重点要了解的是栈内存,堆内存和方法区这三个部分。

程序计数器(了解):

程序计数器(Program Counter Register)是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里,字节码解释器工作就是通过改变程序计数器的值来选择下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都要依赖这个计数器来完成。

虚拟机栈(重点):

Java 虚拟机栈(Java Virtual Machine Stacks)也是线程私有的,它的生命周期与线程相同

每个 Java 方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、常量池引用等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程。

Java的参数和局部变量只能是基本类型的变量(比如int),或者对象的引用(reference)。因此,在栈中,只保存有基本类型的变量和对象引用。引用所指向的对象保存在堆中。(引用可能为Null值,即不指向任何对象)。

当被调用方法运行结束时,该方法对应的帧将被删除,参数和局部变量所占据的空间也随之释放(注意,此时释放的只是栈中的基本类型变量和对象的引用所占的空间,而引用所指向的、存在于堆中的对象并没有释放)。线程回到原方法,继续执行。当所有的栈都清空时,程序也随之运行结束。
注: 该区域会抛出StackOverFlowError异常和OutOfMemoryError异常;可以通过-Xms设置虚拟机栈内存大小。

本地方法栈(了解):

作用于虚拟机栈类似,区别是虚拟机栈为java方法服务,而本地方法栈为native方法服务。native方法指的是非java实现的方法。

注: 该区域也会抛出StackOverFlowError异常和OutOfMemoryError异常。

堆(重点)

堆是所有线程共享的一块内存区域,在虚拟机启动时创建,此内存区域的唯一目的就是存放对象实例 。堆可以处于物理上不连续的内存空间,只要逻辑上连续的即可。在实现上,既可以实现固定大小的,也可以是扩展的。

Java的普通对象存活在堆中。与栈不同,**堆的空间不会随着方法调用结束而清空。如果程序不断创建对象,那么内存空间总是会被消耗殆尽,所以jvm提供了垃圾回收(garbage collection,简称GC)**来清理堆中不再使用的对象(即没有指针指向);堆又可以划分为:

  • 新生代:是用来存放新生的对象。一般占据堆的1/3 空间;
  • 老年代:主要存放应用程序中生命周期长的内存对象,老年代的对象都比较稳定;

注:

  1. 当一个对象被创建时,它首先进入新生代,之后有可能被转移到老年代中。新生代存放着大量的生命很短的对象,因此新生代在三个区域中垃圾回收的频率最高;
  2. Java 堆不需要连续内存,并且可以动态扩展其内存,扩展失败会抛出OutOfMemoryError 异常,可以使用-Xms-Xmx设置堆内存;

方法区(重点)

指内存的永久保存区域,主要用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据

相对而言,垃圾收集行为在这个区域比较少出现,但并非数据进了方法区就永久的存在了,这个区域的内存回收目标主要是针对常量池的回收和对类型的卸载,当方法区无法满足内存分配需要时,将抛出OutOfMemoryError异常。

注意:

  1. 和 Java 堆一样不需要连续的内存,并且可以动态扩展,动态扩展失败一样会抛出OutOfMemoryError 异常。

  2. JDK 1.7 之前,HotSpot 虚拟机把它当成永久代来进行垃圾回收。可通过-XX:PermSize-XX:MaxPermSize这两个参数设置。

    JDK 1.8 之后,取消了永久代,用metaspace(元空间)区替代,两者本质类似,最大区别在于:元空间直接使用本地内存,并不存在于虚拟机中。可通过XX:MaxMetaspaceSize参数设置。

方法区与永久代(拓展)

经常有文章把这部分混淆,这里解释一下二者的联系与区别,目前有三大Java虚拟机:HotSpot,oracle JRockit,IBM J9。目前使用最多的就是HotSqot,JRockit和J9不存在永久代这种说法,方法区和永久代的关系很像Java中接口和类的关系,类实现了接口,而永久代就是HotSpot虚拟机对虚拟机规范中方法区的一种实现方式。类似的,1.8之后,方法区的实现由永久代变为了元空间。

运行时常量池

运行时常量池(Runtime Constant Pool)是方法区的一部分,Class 文件中除了有类的版本、字段、方法、接口等描述信息,还有一项信息是常量池(Constant Pool Table),用于存放编译器生成的各种字面量和符号引用,这部分内容会在类加载后被放入这个区域。

  • 字面量 : 文本字符串、声明为 final 的常量值等。
  • 符号引用 : 类和接口的完全限定名(Fully Qualified Name)、字段的名称和描述符(Descriptor)、方法的名称和描述符。

注:

  1. 在jdk1.7之前运行时常量池包含字符串常量池,在jdk1.7中字符串常量池被单独从方法区中拿到了堆中, 运行时常量池其他部分任然在方法区中;1.8中方法区被元空间取而代之,拿到了本地内存中,此时,字符串常量池任然在堆中

  2. 通过以下文章可以加深理解:String和new String

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值