【深入理解JVM(一)】:内存区域与内存异常

本文详细介绍了JVM的内存区域,包括程序计数器、虚拟机栈、本地方法栈、堆和方法区,以及各种内存异常如堆溢出、栈溢出和方法区溢出的情况。文章还探讨了Java对象的分配策略、垃圾回收机制和性能优化的初步概念。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

JVM 系列文章目录

第一篇:内存区域与内存异常
第二篇:对象揭秘与堆内存分配策略
第三篇:垃圾回收
第四篇:类加载机制
第五篇:性能优化(上)


一、JVM是什么?

  JVM是Java Virtual Machine(Java虚拟机)的缩写,是一种规范,正如名字一样,它是一个虚构出来的计算机。java程序有个很重要的特性就是可以跨平台,正是通过JVM来实现的,你甚至可以理解为它是一个程序和平台解耦的中间件。

  执行流程:
在这里插入图片描述

二、内存区域

1. HotSpot VM 与 JRockit VM

  不同JVM版本内存区域也存在着差异,介绍运行时数据区域之前先提一嘴虚拟机的两个巨头。

  • HotSpot VM:最初由一家小公司设计,后来被Sun公司收购,从JDK1.3开始成为了默认虚拟机。
  • JRockit VM:由BEA公司发行,被誉为世界上最快的JVM。

  而Sun和BEA先后被Oracle公司收购,这样Oracle就拥有了两款非常优秀的虚拟机,于是便宣布在JDK8中完成两款虚拟机的整合,这也直接导致了JDK8和JDK7内存区域的差异化。

2. 运行时数据区

在这里插入图片描述
程序计数器(pc寄存器):

  可以看做是当前线程所执行的字节码的行号指示器,Java本身就是多线程的,这就意味着可以确保多线程情况下的线程切换程序正常执行。此内存区域是JVM中唯一不会出现OOM的区域

虚拟机栈:

  存储当前线程运行方法所需的数据,指令、返回地址。虚拟机栈描述的是Java方法执行的内存模型,每个方法执行是都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每个方法从调用到执行完成的过程,就对应这一个栈帧从入栈到出栈的过程。可通过-Xss来设置栈内存大小。
  在Java虚拟机规范中,对这个区域规定了两种异常状况:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverFlowError异常;如果虚拟机栈动态扩展申请不到足够的内存,将抛出OutOfMemoryError异常。

在这里插入图片描述

  • 局部变量表:存放编译期可知的各种基本数据类型和对象引用。
  • 操作数栈:主要作为方法调用的中转站使用,用于存放方法执行过程中产生的中间计算结果。

  写段简单的代码:

public class JavaStackTest {
    public static void sub(int a) {
        a = a - 1;
    }
}

  javap -v 反编译成字节码文件。

在这里插入图片描述

  • 动态连接:主要服务一个方法需要调用其他方法的场景(多态)。

在这里插入图片描述

  • 返回地址:对应当前栈帧出栈的过程,在当前栈侦出栈后,就需要恢复上层方法栈侦里的局部变量表,操作数栈,此时会将出栈的栈侦的返回值压入上层方法的操作数栈中,将方法返回地址设置进pc寄存器,以便让执行引擎继续执行下去。如果方法执行过程中遇到异常,则不给上层方法调用者返回值。

本地方法栈:

  本地方法栈保存的是native方法的信息,当一个JVM创建的线程调用native方法后,JVM不再为其在虚拟机栈中创建栈帧,JVM只是简单地动态链接并直接调用native方法。

堆:

  存放对象实例,是JVM所管理内存最大的一块区域,也是GC的主要区域,可通过-Xms或-XX:InitialHeapSize设置初始堆内存,默认大小是电脑物理内存大小的1/64,-Xmx或-XX:MaxHeapSize设置最大堆内存,默认大小是电脑物理内存大小的1/4。

  • 堆内存分为年轻代(Young Generation)和老年代(Old Generation),可通过-Xmn来设置年轻代内存大小,或者-XX:NewRatio来设置老年代和年轻代比例,默认值是2,也就是新生代比老年代等于1比2。
  • 年轻代又分为Eden和Survivor区,Survivor区由两个一样的From区和To区组成,我们一般称为S0区和S1区。可通过-XX:SurvivorRatio来设置Eden区和Survivor区比例,默认值是8,也就是Eden区比S0区比S1区等于8比1比1。
  • 初始化的对象会先放到Eden区,Eden满了会触发YGC,存活对象放入S0或者S1区(复制算法,后续会细讲),每经过一次YGC,Survivor区的对象年龄加1,达到晋升年龄的最大值或者容量不足时,放入老年代,老年代满了会触发FGC。可通过-XX:TargetSurvivorRatio设置Survivor区使用率,-XX:MaxTenuringThreshold设置晋升年龄的最大值。

方法区:

  用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等。说到这里,很多人对方法区、永久代和元空间的区别产生疑问,我来简单介绍下:方法区是JVM的一种规范,类似于我们的接口,而永久代和元空间是java7和java8的不同实现。前文提过HotSpot VM与JRockit VM两款虚拟机,Oracle在Java8中将两款虚拟机整合,而JRockit VM压根就没有永久代,所以Java8中直接移除了永久代用元空间来替代。那么他们具体有什么区别呢?

  • 存储位置:永久代是在 Java 堆中的一个特殊区域(和老年代相连),而元空间是在本地内存中的。

  • 大小调整:永久代的大小是有限制的,并且必须在启动时指定,字符串常量池存在永久代中,容易出现性能问题和内存溢出。而元空间可以根据需要自动调整大小。

  • 垃圾收集:永久代使用 Java 堆的垃圾收集器进行垃圾回收,会触发FGC,而且回收效率低,而元空间使用本地内存的垃圾收集器。


三、内存异常

1. Java堆溢出

  Java堆用于存储对象实例,只要不断创建对象,并且保证 GC Roots 到对象之间有可达路径来避免垃圾回收机制来清楚这些对象,当达到最大堆容量后就会发生内存溢出异常,java.long.OutOfMemoryError: Java heap space

  要解决这个区域的异常,一般手段是先通过内存映像分析工具对Dump出来的堆转储快照进行分析,重点确认内存中的对象是否必要,来分清到底出现了内存泄露(Memory Leak)还是内存溢出(Memory Overflow)。

  如果是内存泄露,可进一步通过工具(Java自带的VisualVM就很好用)查看对象到 GC Roots 的引用链。找到泄露对象是通过怎样的路径与 GC Roots 关联并导致垃圾收集器无法回收的。掌握了泄露对象的类型信息及 GC Roots 引用链的信息,就可以比较精准的定位出泄露代码的位置。

  如果不存在泄露,也就是说内存中的存活对象是正常的,那就检查下堆信息的配置(-Xms和-Xmx),适当调大堆内存,再从代码检查,优化对象生命周期以减少内存消耗。

2. 虚拟机栈和本地方法栈溢出

  关于虚拟机栈和本地方法栈,Java虚拟机规范中描述了两种异常:

  • 如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverFlowError异常。
  • 如果虚拟机栈动态扩展申请不到足够的内存,将抛出OutOfMemoryError异常。

  怎么理解呢?减少栈内存容量、定义大量的局部变量(增加栈帧大小)或者死递归(增加栈帧数量),栈帧入栈时栈空间不足,就会抛出StackOverFlowError异常;把栈内存设的很大,这样每新增一个栈都会申请很大的内存,而内存是有限的,当新增一个栈申请不到内存时就会抛出OutOfMemoryError异常。

3. 方法区和运行时常量池溢出

  由于运行时常量池是方法区的一部分,所以会受到方法区内存的限制,当常量池无法再申请到内存时会抛出OutOfMemoryError。

  • java7中永久代抛OutOfMemoryError: PermGen space异常。
  • java8中元空间抛OutOfMemoryError: Metaspace异常。

  java7中方法区内存溢出很常见,因为一个类被垃圾收集回收掉,判定条件比较苛刻。在经常动态生成大量class文件的应用中,需特别注意类的回收情况。比如大量使用CJLib动态代理或大量使用JSP(JSP第一次运行需编译为Java类)等。

4. 本机直接内存溢出

  由DirectMemory导致的内存溢出,一个明显的特征是在 Heap Dump 文件中不会看见明显的异常,如果 OOM 后 Dump 文件很小,而程序中又直接或间接使用了 NIO,那就很可能是这方面的原因了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

砍光二叉树

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值