JVM五大内存模型

什么是JVM

JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一个虚构出来的计算机,有着自己完善的硬件架构,如处理器、堆栈等。

JVM的作用

Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码( 字节码 ),就可以在多种平台上不加修改地运行。

Java文件必须先通过一个叫javac的编译器,将代码编译成class文件,然后通过JVM把class文件解释成各个平台可以识别的机器码,最终实现跨平台运行代码。

JVM内存模型

JVM内存模型可以分为两个部分,堆和方法区是所有线程共有的,而虚拟机栈,本地方法栈和程序计数器则是线程私有的。

堆(Heap)

Java堆(Heap),是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。

可能存在的异常

  1. OutOfMemoryError:当堆中没有足够的内存分配给新的对象时抛出。这可能是由于堆空间不足、内存泄漏或对象过多等原因导致。

  2. StackOverflowError:当线程的调用栈空间超过其限制时抛出。这通常是由于无限递归或者方法调用层次过深引起的。

  3. HeapDumpOutOfMemoryError:当堆内存不够用时,JVM可能会生成一个堆转储文件(Heap Dump)以供分析。这通常发生在OutofMemoryError之后,用于帮助开发人员诊断内存问题。

  4. PermGen space/ Metaspace OutOfMemoryError:在Java 7及之前的版本中,常见于永久代(PermGen)内存区域不足的情况。而在Java 8及之后的版本中,永久代被元空间(Metaspace)所取代,因此抛出的错误会是Metaspace OutOfMemoryError。

  5. ConcurrentModeFailureException:在并发标记清除垃圾回收器(CMS)中,当GC线程无法跟上应用程序生成的垃圾量时,会抛出此异常。这可能是由于堆中对象数量过多或者垃圾回收器参数配置不合理导致。

方法区(Method Area)

方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

可能存在的异常

  1. OfMemoryError:当方法区没有足够的内存分配给新的类元数据、常量池等时抛出。这可能是由于方法区空间不足、加载过多的类或者大量的字符串常量等原因导致。
  2. PermGen space/ Metaspace OutOfMemoryError:在Java 7及之前的版本中,常见于永久代(PermGen)内存区域不足的情况。而在Java 8及之后的版本中,永久代被元空间(Metaspace)所取代,因此抛出的错误会是Metaspace OutOfMemoryError。
  3. ClassLoader相关异常:在方法区中,类加载器负责加载和链接类的过程。如果类加载器出现问题,可能会导致ClassNotFoundException、NoClassDefFoundError等异常。
  4. 频繁Full GC:如果方法区中存储的类元数据、常量池等对象无法被垃圾回收,则可能导致频繁进行Full GC(全局垃圾回收),从而影响应用程序的性能。

需要注意的是,在Java 8及之后的版本中使用元空间(Metaspace)替代了永久代(PermGen),因此一些特定于永久代的异常,如PermGen space OutOfMemoryError,在Java 8及之后的版本中不再出现。而Metaspace OutOfMemoryError则是对应的异常。

程序计数器(Program Counter Register)

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

在JVM的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。

分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。

JVM的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,为了各条线程之间的切换后计数器能恢复到正确的执行位置,所以每条线程都会有一个独立的程序计数器。

当线程正在执行一个Java方法,程序计数器记录的是正在执行的JVM字节码指令的地址;如果正在执行的是一个Natvie(本地方法),那么这个计数器的值则为空(Underfined)。

可能存在的异常

程序计数器可能存在的异常情况主要是线程相关的问题,例如:

  1. 线程死循环:如果线程陷入死循环,程序计数器会一直指向循环的字节码指令地址,不会发生变化。这可能导致程序无法继续执行其他逻辑。

  2. 线程切换错误:在多线程环境中,JVM会通过线程切换来实现并发执行。程序计数器记录了每个线程当前执行的位置,在线程切换时需要正确保存和恢复计数器的值。如果出现错误,可能导致线程执行位置错乱或者执行结果异常。

  3. 虚拟机实现错误:JVM的实现可能存在bug或者错误,导致程序计数器的行为异常。这种情况比较少见,但仍有可能发生。

虚拟机栈(JVM Stacks)

与程序计数器一样,Java虚拟机栈(Java Virtual Machine Stacks)也是线程私有的,它的生命周期与线程相同。

虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

可能存在的异常

  1. StackOverflowError:当线程的调用栈空间超过其限制时抛出。这通常是由于无限递归或者方法调用层次过深引起的。每个线程在JVM中都有一个对应的虚拟机栈,当栈空间耗尽时会抛出StackOverflowError。

  2. OutOfMemoryError:当虚拟机栈无法继续分配新的栈帧时抛出。每个线程在JVM中都有一个对应的虚拟机栈,用于保存方法调用的信息。如果创建的线程过多或者每个线程的栈帧太大,将消耗掉可用的虚拟机栈空间,导致无法分配新的栈帧,从而抛出OutOfMemoryError。

  3. StackOverflowError和OutOfMemoryError都是虚拟机栈相关的异常,但它们的原因和解决方法不同。StackOverflowError通常是由于方法调用层次过深或者无限递归引起的,需要检查代码逻辑并修复。而OutOfMemoryError则可能是由于线程数过多或者每个线程的栈帧太大,可以通过调整JVM参数来增加虚拟机栈的大小。

本地方法栈(Native Method Stacks)

本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务。

可能存在的异常

  1. StackOverflowError:当本地方法栈空间超过其限制时抛出。本地方法栈的大小是由JVM参数决定的,一般较小。如果本地方法递归调用层次太深或者本地方法栈帧太大,超出了栈空间限制,就会抛出StackOverflowError。

  2. OutOfMemoryError:当无法分配新的本地方法栈帧时抛出。本地方法栈的大小是有限的,如果创建的线程过多或者每个线程的本地方法栈帧太大,将耗尽可用的本地方法栈空间,导致无法分配新的栈帧,从而抛出OutOfMemoryError。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值