jvm内存模型以及各个分区

1. JVM 内存模型

Java虚拟机内存的各个区域包括:

  • 程序计数器
  • Java虚拟机栈
  • 本地方法栈
  • Java堆
  • 方法区

2. 程序计数器

什么是程序计数器呢?

  • 程序计数器是一块较小的内存空间,它可以是当前程序所执行的字节码的行号指示器。

  • 注意:如果线程正在执行的是java的方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Native方法,这个计数器值则为空(Undefined)。

程序计数器的作用

  • 字节码解释器工作时,就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,从而分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖此计数器来完成。

  • 在多线程的情况下,在任何一个确定的时刻,一个处理器都只会执行一条线程中的指令。线程切换后,能恢复到正确的执行位置。

程序计数器的特点

  • 是一块较小的内存空间
  • “线程私有”;每条线程都有一个独立的程序计数器,各个线程之间计数器互不影响,独立存储。
  • 是唯一一个在Java虚拟机规范中没有任何OutOfMemoryError情况的区域。

3. Java虚拟机栈

什么是Java虚拟机栈?

  • 描述的是Java方法执行的内存模型。每个方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等。

  • 当方法在运行过程中需要创建局部变量时,就将局部变量的值存入栈帧的局部变量表中,当这个方法执行完毕后,这个方法所对应的栈帧将会出栈,并释放内存空间。

  • 注意: 人们常说,Java内存区分为 和 ,栈中存放局部变量,堆中存放对象。这里所说的只代表了Java虚拟机栈中的局部变量表部分。真正的Java虚拟机栈是由一个个栈帧组成,每个栈帧都拥有:局部变量表、操作数栈、动态链接、方法出口信息。

Java虚拟机栈的特点

  • 局部变量表的创建是在方法被执行的时候,随着栈帧的创建而创建。而且,局部变量表所需的内存空间在编译期间完成分配,当进入一个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法 运行期间不会改变局部变量表的大小。

  • 如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;如果虚拟机栈可以动态扩展,如果扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常。

  • Java虚拟机栈也是线程私有的,每个线程都有各自的Java虚拟机栈,而且随着线程的死亡而死亡。

  • 注意: StackOverFlowError 和 OutOfMemoryError的异同。
    StackOverFlowError 表示当前线程申请的栈超过了事先定好的栈的最大深度,但内存空间肯还有很多。而OutOfMemoeryError是指当线程申请栈时发现栈已经满了,而且内存也都用光了。

本地方法栈

什么是本地方法栈?

  • 本地方法栈与虚拟机栈所发挥的作用是非常相似的,他们的区别是虚拟机栈为虚拟机执行Java方法服务,而本地方法栈则为虚拟机使用到 的Native方法服务。

  • 本地方法被执行的时候,在本地方法栈也会创建一个栈帧,用于存放该本地方法的局部变量表、操作数栈、动态链接、出口信息。

  • 方法执行完毕后相应的栈帧也会出栈并释放内存空间。也会抛出StackOverFlowError 和 OutOfMemoryError异常。

Java堆

什么是堆?

  • java 堆是Java虚拟机所管理的内存中最大的一块,被所有线程共享的区域,存放对象实例,几乎所有对象实例都在这里分配内存。

堆的特点

  • 线程共享:整个java虚拟机只有一个堆,所有的线程都访问同一个堆。而程序计数器、java虚拟机栈、本地方法栈都是一个线程对应一个。

  • 在线程启动时被创建。

  • 垃圾收集器管理的主要区域。

  • Java堆中还可以细分为:新生代和老年代,再细致一点有:Eden空间、From Survivor空间、To Survivor空间。不同的区域存放具有不同声明周期的对象。这样可以根据
    不同的区域使用不同的垃圾回收算法,从而更具有针对性,从而更加高效。

  • java-堆里面的分区:Eden,survival(from) to,老年代,各自的特点。

    • Eden区
      Eden区位于Java堆的年轻代,是新对象分配内存的地方,由于堆是所有线程共享的,因此在堆上分配内存需要加锁。而Sun JDK为提升效率,会为每个新建的线程在Eden上分配一块独立的空间由该线程独享,这块空间称为TLAB(Thread Local Allocation Buffer)。在TLAB上分配内存不需要加锁,因此JVM在给线程中的对象分配内存时会尽量在TLAB上分配。如果对象过大或TLAB用完,则仍然在堆上进行分配。如果Eden区内存也用完了,则会进行一次Minor GC(young GC)。

    • Survival from to
      Survival区与Eden区相同都在Java堆的年轻代。Survival区有两块,一块称为from区,另一块为to区,这两个区是相对的,在发生一次Minor GC后,from区就会和to区互换。在发生Minor GC时,Eden区和Survivalfrom区会把一些仍然存活的对象复制进Survival to区,并清除内存。Survival to区会把一些存活得足够旧的对象移至年老代。

    • 年老代
      年老代里存放的都是存活时间较久的,大小较大的对象,因此年老代使用标记整理算法。当年老代容量满的时候,会触发一次Major GC(full GC),回收年老代和年轻代中不再被使用的对象资源。

  • 线程共享的java堆可能划分出多个线程私有的分配缓冲区,存储的都是对象实例,进一步划分的目的是为了更好的回收内存,更快的分配内存。

  • 堆的大小既可以实现固定大小的,也可以是可扩展的。主流的虚拟机都是按照可扩展的来实现的。当线程请求分配内存,但堆中没有内存完成实例分配,并且堆也无法扩展时,将会抛出OutOfMemoryError

方法区

什么是方法区?

  • 描述为堆的一个逻辑分区,别名:Non-Heap(非堆)。
  • 是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。

方法区特点

  • 线程共享,方法区是堆的一个逻辑分区,和堆一样,都是线程共享的,整个虚拟机中只有一个方法区。

  • 永久代,方法区中的信息一般需要长期存在而且它又是堆的逻辑分区,因此用堆的划分方法,我们把方法区成为老年代

  • 内存回收效率低,方法区中的信息一般需要长期存在,回收一遍内存之后可能只有少量信息无效。对方法区的内存回收的主要目标是:对常量池的回收和类型的卸载。

  • Java虚拟机规范对方法区的限制非常宽松,和java堆一样不需要连续的内存和可以选择固定的大小或者可扩展,还可以不实现垃圾收集。

什么是运行常量池?

  • 方法区中存放 三种数据:类信息、常量、静态变量、即时编译后的代码。其中常量存储在运行时常量池中。

  • 我们一般在一个类中通过public static final来声明一个常量。这个类被编译后便生成Class文件,这个类的所有信息都存储在这个class文件中。

  • 当这个类被Java虚拟机加载后,class文件中的常量就存放在方法区的运行时常量池中。而且在运行期间,可以向常量池中添加新的常量。如:String类的intern()方法就能在运行期间向常量池中添加字符串常量。

  • 运行时,常量池是方法区的一部分,自然受到方法区内存的限制,当常量池无法再申请到内存时会抛出OutOfMemoryError异常。

直接内存

  • 直接内存是除java虚拟机之外的内存,但也可能被java使用。

  • 在NIO中引入了一种基于通道和缓冲的IO方式。它可以通过调用本地方法直接分配Java虚拟机之外的内存,然后通过一个存储在Java堆中的DirectByteBuffer对象直接操作该内存,而无需先将外面内存中的数据复制到堆中再操作,从而提升了数据操作的效率。

  • 直接内存的大小不受Java虚拟机控制,但既然是内存,当内存不足时就会抛出OOM异常。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值