深入理解Java虚拟机学习之四Java运行时数据区

写在前边的话


最近在看《深入理解Java虚拟机》这本书,学习Java虚拟机底层实现原理。通过写博客的方式记录自己的学习过程以及对知识的理解。如有总结不正确的地方,欢迎大家指出!


上一篇《深入理解Java虚拟机学习之三虚拟机类加载机制》介绍了虚拟机类加载机制以及加载过程,我们了解了一个Java类的class文件是如何被虚拟机装载到内存的。我们都知道class文件中的内容最终只有被虚拟机装载到内存才能被处理器进行执行。那么class文件被装载到内存中的时候它是如何存储的,Java内存区域又是什么样子的呢?

这篇文章就来介绍一下,Java内存区域的划分。

一、Java内存管理与C语言内存管理的区别

在C语言中,程序员有着至高无上的权利,既拥有分配内存的权利又担负着回收内存的任务。而Java语言则不同,Java语言是自动管理内存的。程序员只负责为对象申请内存而不负责回收内存。Java虚拟机有一个垃圾收集器线程在后台进行内存回收的工作。这简直是Java程序员的福音啊,由于将内存的控制权交给了Java虚拟机,一旦发生内存泄漏或者溢出方面的问题,那么排查错误就会非常困难。

二、Java的运行时数据区

Java虚拟机在运行Java程序的时候会将它所管理的内存划分为几个不同的数据区域。这几个区域就称为运行时数据区。具体部分如图:
在这里插入图片描述

图中的区域有的是随着线程产生的,线程结束了,内存也就自动消失了,这部分内存被称为线程私有。有一部分内存是各个线程共有的,这部分内存被称为线程共享。

那么将运行时数据区按照线程共有和线程私有进行划分。

- 线程共享

线程共享区域总共有两块方法区和Java堆区

  • 方法区
    方法区是线程共享的一块儿区域。它主要是用来存储被虚拟机加载进来的类信息,常量,静态变量,即时编译器编译后的代码等数据。虚拟机加载完类后,为类创建的java.lang.Class对象实例也是保存在方法区的。当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。

  • 堆区
    堆区是Java虚拟机所管理的内存中的最大的一块儿内存。
    是一个Java进程中的所有线程共享的一块儿区域。在虚拟机启动时创建。
    此区域的目的是存储对象实例,几乎所有的对象实例都存储在这个区域。
    堆区是垃圾收集器管理的主要区域,所以Java堆区也会被称为GC堆。
    堆区的内存在物理存储上可以是连续的,也可以是不连续的,但逻辑上必须连续。
    堆区既可以是固定大小的,也可以是可扩展的,不过当前虚拟机的堆区都是可扩展的,通过-Xmx与-Xms来实现。
    如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。

    堆区是垃圾收集器的主要工作场合,因此Java堆又被叫做GC堆。从垃圾收集器分代管理思想又可以将内存分为新生代,老年代和永久代。其中Java堆上划分为:新生代和老年代。方法区被划分为永久代。

    • 新生代
      新生代又被分为一块儿较大的Eden区和两块儿较小的Survivor区。
      • Eden区:主要存放新创建的对象。如果创建的这个对象比较大的话,直接分配在老年代。这个区域内的对象大都是朝生夕灭的,声明周期比较短。当这个区域没有空间可分配给对象的时候会触发一次Minor GC对新生代内存进行回收。

      • Survivor From:上一次GC的幸存者,这一次GC的被扫描对象

      • Survivor To:保留了一次Minor GC过程中的幸存者。

      • 一次Minor GC的工作流程(复制-》清空-》互换):
        Minor GC是新生代内存的垃圾收集,而新生代内存的垃圾收集器一般都采用的垃圾收集算法是复制算法。

    1. Eden区,Survivor From区的存活对象复制到Survivor To区(如果Survivor To区的空间不够也会被复制到老年代),被复制的对象年龄加1.如果复制的过程中有年龄超过15岁的对象直接复制到老年代。
    2. 清空Eden区,Survivor From区的对象
    3. 最后,ServicorTo 和 ServicorFrom 互换,原ServicorTo 成为下一次 GC 时的 ServicorFrom区。
    • 老年代
      老年代是保存声明周期比较长的对象。老年代的对象比较稳定。所以老年代的Major GC不会被频繁执行。
      在进行 MajorGC 前一般都先进行了一次 MinorGC,使得有新生代的对象晋身入老年代,导致空间不够用时才触发。当无法找到足够大的连续空间分配给新创建的较大对象时也会提前触发一次 MajorGC 进行垃圾回收腾出空间。
      老年代的垃圾收集器主要采用标记-整理算法。首先扫描一次所有老年代,标记出存活的对象,再将存活的对象向内存的一端移动,然后将端边界外的内存回收掉。这样就避免的内存碎片,当我大对象的时候,可以进行内存分配。MajorGC 的耗时比较长,因为要扫描再回收。当老年代也满了装不下的时候,就会抛出 OOM(Out of Memory)异常。

    • 永久代
      指内存的永久保存区域,主要存放 Class 和 Meta(元数据)的信息,Class 在被加载的时候被放入永久区域,它和和存放实例的区域不同,GC 不会在主程序运行期对永久区域进行清理。所以这也导致了永久代的区域会随着加载的 Class 的增多而胀满,最终抛出 OOM 异常。

- 线程私有

线程私有区域的内存有:程序计数器,Java虚拟机栈和本地方法栈。

  • 程序计数器
    程序计数器是保存当前线程正在执行的字节码指令的地址。因为Java虚拟机多线程执行实际上都是在轮流竞争处理器时间来实现的,当一个线程被某个任务阻塞后,它就失去了处理器时间,这时如果不记录线程当前正在执行的字节码指令的地址,就根本不知道线程到底执行到哪里了。所以用程序计数器这个内存区域来记录线程当前正在执行的字节码指令地址。
    如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Native方法,这个计数器值则为空(Undefined)。
    此区域是一块儿较小的内存。可以看做是线程正在执行的字节码行号指示器。
  • Java虚拟机栈
    生命周期与线程相同
    虚拟机栈是Java方法在内存中的存储结构。一个Java方法在Java虚拟机中的存储结构其实是一个栈帧。而栈帧就保存在Java虚拟机栈中。Java虚拟机栈是一个先进后出的栈结构。当一个方法要开始执行的时候,会先将这个方法入栈,方法执行结束,会将这个方法出栈。方法从开始执行到执行结束,在虚拟机侧其实就是对应的栈帧入栈和出栈的过程。栈帧的结构在以后的虚拟机执行子系统部分再介绍。
    此区域可能抛出来的异常为有StackOverflowError异常和OutOfMemoryError异常。
    StackOverflowError异常:如果线程请求的栈深度大于虚拟机所允许的深度,会抛出Stack OverflowError
    OutOfMemoryError异常:如果虚拟机栈可以动态扩展(当前大部分的Java虚拟机都可动态扩展,只不过Java虚拟机规范中也允许固定长度的虚拟机栈),如果扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常。
  • 本地方法栈
    与虚拟机栈一样,只不过本地方法栈则为虚拟机使用到的Native方法服务。而虚拟机栈是为Java方法服务的。
    与虚拟机栈一样,本地方法栈区域也会抛出StackOverflowError和OutOfMemoryError异常。

三、运行时常量池

运行时常量池是方法区的一部分。用来存储class文件中的常量池中的字面量和符号引用。在之前的文章中介绍了class文件的存储结构,class文件中除了版本号,字段表,方法表等信息外有一个很重要的部分就是常量池,那么class文件被虚拟机的类加载器加载到内存中后,常量池中的内容就会被保存在运行时常量池中。这个部分除了保存常量池的中符号引用和字面量外,还会将映射到的直接引用也保存在运行时常量池中。
运行时常量池相对于Class文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定只有编译期才能产生,也就是并非预置入Class文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中,这种特性被开发人员利用得比较多的便是String类的intern()方法。
既然运行时常量池是方法区的一部分,自然受到方法区内存的限制,当常量池无法再申请到内存时会抛出OutOfMemoryError异常。

四、直接内存

直接内存并不是 JVM 运行时数据区的一部分, 但也会被频繁的使用: 在 JDK 1.4 引入的 NIO 提供了基于 Channel 与 Buffer 的 IO 方式, 它可以使用 Native 函数库直接分配堆外内存, 然后使用DirectByteBuffer 对象作为这块内存的引用进行操作, 这样就避免了在 Java堆和 Native 堆中来回复制数据, 因此在一些场景中可以显著提高性能。

五、Java8中永久代改变

在Java8中,永久代被移除。新增了“元数据”区。这个元数据被放在了直接内存中。元数据区存放的是类的元数据信息。原来放置在永久代的字符串常量池,静态变量则被分配到Java堆中。这样可以加载多少类的元数据就不再由
MaxPermSize 控制, 而由系统的实际可用空间来控制。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值