深入理解Java虚拟机(一)Java内存区域与内存溢出异常

前言

1.Java代码为什么可以跨平台?

  因为Java程序编译之后的代码不是能被计算系统直接运行的代码,而是一种“中间码”-----字节码。这种字节码不是纯二进制的字节码,而是基于Unicode的字节码,它不依赖于特定的计算机硬件架构而存在。然后不同的硬件系统装有不同的Java虚拟机(JVM),有JVM把字节码“翻译”成所对应硬件平台能够执行的代码。总的来说,Java之所以能跨平台运行,是因为JVM可以跨平台安装。
在这里插入图片描述

2. Java中是如何管理内存的?

Java的内存管理就是对象内存的分配释放问题。
分配: 内存的分配是由程序完成的,我们需要通过关键字new为每个对象申请内存空间(基本类型除外),所有的对象都是在堆中分配空间的。
释放: 对象的释放时由JVM中的**垃圾回收机制(GC)**决定和执行的。这样的操作虽然简化了人类的工作,然后加重了JVM的工作。因为为了能够正确的释放对象,GC必须监控每一个对象的运行状态,包括对象的申请、引用、被引用、复制等,GC都需要进行监控。

3. 总结

  通过上面的两个问题,我们知道了Java跨平台的原因就是因为Java虚拟机可以跨平台安装,Java源文件经过编译生成的.class字节码文件,字节码文件由Java虚拟机解释运行。在这里体现了Java虚拟机的重要性。
  在Java开发中,我们不像在C/C++,需要自己写free/delete来释放申请的内存空间,在Java中内存是交给虚拟机管理的,Java虚拟机提供了我们的便利性,但是也是由于我们把内存的控制权交给了虚拟机,如果我们写的程序如果出现了内存泄漏和或一出问题,应该怎么解决?这将成为一个极大的困难,这就让我们必须了解虚拟机的内存管理。

1. JVM概念

JVM: Java Virtual Machine,意为Java虚拟机。(在全文中为了统一,我会统一将Java虚拟机简称为JVM)。
虚拟机: 通过软件模拟的具有完整硬件功能的、运行在一个完全隔离的环境中的完整计算机系统。(我的理解:通过软件模拟完整的硬件环境。
常见的虚拟机: JVM、VMware、Virtual Box
JVM与其他两个虚拟机的区别:

  • VMware与Virtual Box是通过软件模拟物理CPU的指令集,物理系统中会有很多的寄存器。
  • JVM是通过软件模拟Java字节码的指令集,JVM主要保留了PC寄存器,其他寄存器都进行了裁剪,JVM是一台被定制过的现实当中不存在的计算机。

2. 运行时数据区域

  JVM会在执行Java程序的过程中把它管理的内存划分为若干个不同的数据区域。这些数据区域各有各的用处,各有各的创建与销毁时间,有的区域随着JVM进程的启动而存在,有的区域则依赖用户线程的启动和结束而创建于与销毁。JVM所管理的内存将会包括一下几个运行时数据区域。
Java运行时数据区
通过上图我们可以发现JVM所管理的内存包含了一下区域:
线程私有区域:

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

线程共享区域:

Java堆方法区运行时常量池

 在这里我们先点名一下,运行时常量池在JDK1.7时在方法区中,JDK1.7后移动到堆中。

2.1 程序计数器(线程私有)
1. 概述

程序计数器时一块比较小的内存区域,可以看作是当前线程所执行的字节码的行号指示器

2. 作用

通过改变计数器的值选取下一条需要执行的字节码指令。(分支、循环、跳转、异常处理、线程恢复等)基础功能都依赖与其完成。但这个仅仅只是概念模型,虚拟机可能会通过一些更高效的方法去实现。

3. 特点
  • 无内存溢出: 如果当前线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是一个Native方法,这个计数器值为空。程序计数器内存区域是唯一一个在JVM规范中没有规定任何OOM(Out Of Memory Error)情况的区域。
  • 线程私有:
    由于JVM多线程是通过线程轮流切换并分配处理器执行时间的方法来实现,因此在任何一个确定的时刻,一个处理器都只会执行一条线程中的指令。因此为了切换线程后能恢复到正确的执行位置,每条线程都需要独立的程序计数器,每条线程之间的计数器互不影响,独立存储。
2.2 Java虚拟机栈(线程私有)
1. 概述与存储

虚拟机栈描述的是Java方法执行的内存模型。每个方法从调用直至执行完成的过程,就对应一个栈帧在虚拟机栈中入栈和出栈的过程。
  当线程执行一个方法是,就会随着创建一个对应的栈帧,并将建立的栈帧压栈;当方法执行完成之后,便会将栈帧出栈;因此,线程当前执行的方法所对应的栈帧必将定位于Java栈的顶部。
在这里插入图片描述

2. 作用

  存储局部变量表、操作数栈、动态链接、方法返回地址 和 一些额外的附加信息。
我们之前一直将的栈区域实际上就是这里的虚拟机栈中的局部变量表部分。

3. 局部变量表

  用于存储方法中的局部变量。对于基本数据类型的变量,直接存储它的值;对于引用类型的变量,则存的是指向它对象的引用。局部变量表的大小在编译时就确定好了,在执行期间不会改变局部变量表的大小。

4. 特点
  • 线程私有
  • 生命周期与线程相同
5. 产生的异常

此区域一共会产生以下两种异常:

  • 如果线程请求的栈深度大于虚拟机所允许的深度,将会抛出StackOverFlowError异常。例如:递归无出口。
  • 虚拟机在动态扩展是无法申请到足够的内存,会抛出OOM(OutOfMemoryError)异常。
2.3 本地方法栈(线程私有)

  本地方法栈与虚拟机栈的作用完全一样,它俩的区别就是本地方法栈为虚拟机使用的Native方法服务,虚拟机栈为JVM执行的Java方法服务。
  作用与虚拟机栈完全一样,异常也与本地虚拟机栈一样,抛出StackOverFlowError和OutOfMemoryError异常。
  在虚拟机规范中,对本地方法栈中使用的语言,使用方法的数据结构没有强制规定,因此虚拟机可以自由的实现它,甚至在HotSpot虚拟机中,本地方法栈与虚拟机栈是同一块内存区域。

2.4 Java堆(线程共享)
1. 概念与存储

   Java堆是JVM所管理的最大内存区域。 Java堆这块内存区域存放的都是对象实例。JVM规范中说到:“所有对象的实例以及数组都要在堆上分配。
  Java堆时垃圾回收管理的主要区域,因此很多时候称之为“GC堆”。根据JVM规范规定的内容,Java堆处于物理上不连续的内存空间中,只要逻辑上时连续的就可以。

2. 作用

此区域唯一的目的就是存放对象实例

3. 特点
  1. 所有线程共享的一块区域。
  2. 在JVM虚拟机启动时创建。
4. 异常
  • 如果堆中没有足够的内存完成实例分配并且堆也无法扩展时,将会抛出OOM异常。
2.5 方法区(线程共享)
1. 概述

  方法区于Java堆一样,是各个线程共享的内存区域。Java虚拟机规范将方法区描述为堆的一个逻辑部分,他有一个别名叫做Non-Heap(非堆),目的是与Java堆区分开。方法也被称为“永久代”(JDK8已经被元空间取代)。
  永久代并不意味着数据进入方法区就永久存在,此区域的内存回收主要是针对常量池的回收以及对类型的卸载。

2. 作用

它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

3. 特点

线程共享。

4. 异常

JVM规范规定:当方法区无法满足内存分配需求时,将抛出OOM异常。

2.6 运行时常量池

 cc运行时的常量池时方法区的一部分,存放字面量与符号引用。
 Java 语言并不要求常量池一定只有编译期才能产生,也就是并非预置入 Class 文件中常量池的内容后才能进入方法区的运行时常量池,运行期间也可以将新的常量放入池中,这种特性用的比较广泛的便是 String 类的 intern() 方法。
字面量: 字符串(JDK1.7后移动到堆中)、final常量、基本数据类型的值。
符号引用: 类和结构的完全限定名、字段的名称和描述符、方法的名称和描述符。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Java内存溢出异常(OutOfMemoryError)不能被捕获。这是因为当内存溢出时,Java虚拟机(JVM)无法提供足够的内存来分配新的对象,因此无法继续运行。在这种情况下,JVM不会将异常传递给catch块,而是直接终止程序。因此,Java内存溢出异常是无法被捕获的。 ### 回答2: Java内存溢出异常(OutOfMemoryError)是一种严重的错误,它表示Java虚拟机无法为应用程序分配足够的内存空间。这种异常无法被普通的异常处理机制捕获和处理。 当Java应用程序运行时,Java虚拟机会将内存分为堆(Heap)和栈(Stack)两个部分。堆用于存储对象实例,而栈用于存储方法调用和局部变量。当应用程序试图创建一个新的对象实例或调用方法时,Java虚拟机会在堆或栈上分配相应的内存空间。 如果应用程序需要创建的对象过多,或者递归调用的层次太深导致栈空间耗尽,就会发生内存溢出异常。此时,Java虚拟机无法分配更多的内存空间,导致应用程序无法运行。 由于内存溢出异常不属于普通的异常类型(Throwable),因此无法被try-catch块捕获。尽管可以使用try-catch语句来捕获其他异常,但内存溢出异常会导致应用程序直接崩溃,无法再执行进一步的操作。在发生内存溢出异常时,通常会打印相关的错误信息,并且无法通过捕获和处理该异常来修复应用程序。 为了解决内存溢出异常,通常需要对应用程序进行优化,如减少对象的创建和引用、释放无用的对象、调整堆栈的大小等措施,以提高内存的使用效率和性能。 ### 回答3: Java中的内存溢出异常(OutOfMemoryError)一般情况下是无法被捕获的。内存溢出是指程序在申请内存时,无法获取到所需的内存空间而导致的异常。这种异常通常发生在堆内存空间不足以满足程序的需求时,例如创建过多的对象或者加载过大的数据。 由于内存溢出异常涉及到底层内存管理,是由Java虚拟机抛出的致命错误,无法通过传统的方式捕获和处理。一旦内存溢出发生,Java虚拟机将无法继续执行程序,直接导致程序崩溃。 但是,我们可以通过一些手段来预防和避免内存溢出异常的发生。例如,合理管理和释放对象的内存,避免创建过多且无用的对象;增加JVM堆内存的大小,确保程序有足够的内存空间;使用较新的JDK版本,其中对内存管理的优化可能有助于减少内存溢出的风险。 此外,还可以通过监控和分析程序的内存使用情况来及时发现潜在的内存溢出问题,并进行优化和调整。例如,使用一些内存分析工具,如VisualVM、Eclipse Memory Analyzer等,来检查程序的内存占用情况和对象泄漏情况,以及查找内存使用过多的地方。 总之,虽然无法直接捕获Java内存溢出异常,但通过合理的内存管理和优化,可以提高程序的稳定性和性能,减少内存溢出的风险。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值