Java虚拟机(JVM)大全
发布日期:2021-05-10 01:19:00 浏览次数:19 分类:精选文章

本文共 2991 字,大约阅读时间需要 9 分钟。

1. Java 内存区域与内存溢出异常

Java虚拟机(JVM)管理内存划分为几个关键区域,每个区域都有其特定的用途和特点。了解这些区域有助于我们更好地理解内存管理和内存溢出异常的处理机制。

1.1 运行时数据区域

根据《Java虚拟机规范(Java SE 7版)》,JVM的内存布局主要包括以下几个部分:

  • 程序计数器(Program Counter)

    • 储存当前线程执行的位置,一条字节码指令所在的位置。
    • 字节码解释器通过改变计数器来选择下一条指令进行执行。
    • 线程私有,仅供当前线程使用。
    • 如果线程处于执行Native方法,本地方法栈的计数器值为Undefined。
  • 虚拟机栈(VM Stack)

    • 每个线程拥有私有栈,栈帧用于存储局部变量、操作数栈、动态链接和方法出口等信息。
    • 栈帧在线程进入方法时被创建,方法执行结束后出栈。
    • 局部变量表存储基本类型、对象引用和返回地址。
    • 栈溢出异常(StackOverflowError)和内存溢出异常(OutOfMemoryError)是该区域可能出现的异常。
  • 本地方法栈(Native Stack)

    • 存储本地方法(Native方法)的执行上下文。
    • 与虚拟机栈类似,也可能发生栈溢出和内存溢出异常。
  • Java堆(Heap)

    • 最大的一块内存区域,存储对象实例和数组。
    • 使用线程共享的分配缓冲区(Thread Local Allocation Buffer - TLAB)来优化内存分配。
    • 内存溢出异常(OutOfMemoryError)可能发生在堆内存不足或无法扩展时。
  • 方法区(Method Area)

    • 存储已加载的类信息、常量池、静态变量和编译后的代码。
    • 区域共享,主要由类加载器负责管理。
  • 运行时常量池(Runtime Constant Pool)

    • 方法区的一部分,用于存储编译期生成的字面量和符号引用。
    • 可能出现内存溢出异常(OutOfMemoryError)当内存有限且无法申请时。
  • 直接内存(Direct Memory)

    • JVM以外的堆外内存区域。
    • 通过Native方法直接分配内存,比如使用DirectByteBuffer。
    • 内存溢出异常(OutOfMemoryError)会受到本地内存限制影响。

  • 1.2 HotSpot虚拟机对象探秘

    1.2.1 对象的创建

    • 对象的创建涉及到类加载、内存分配和初始化等步骤。
    • new指令时,虚拟机会检查参数是否可以在常量池中定位到符号引用。
    • 如果符号引用对应的类已经加载、解析和初始化,则分配内存,划分 Pointer Collision(指针碰撞)或 Empty List(空闲列表)进行内存分配。
    • 处理线程私有分配缓冲区(Thread Local Allocation Buffer - TLAB)以避免并发内存分配问题。
    • 对象初始化后填充对象头,记录类元数据、哈希码、GC老化年龄等信息。

    1.2.2 对象的内存布局

    • HotSpot虚拟机中的对象布局分为三个部分:
    • 对象头(Header)
      • 包含Mark Word存储运行时数据(如哈希码、GC年龄、锁状态等)。
      • 类指针(Pointer)指向对象所属的类。
      • 对于数组对象,额外存储数组长度。
    • 实例数据(Instance Data):存储类定义的字段,如基本类型变量和对象引用。
    • 对齐填充(Padding):确保对象大小是8字节的整数倍。

    1.2.3 对象的访问定位

    • 可通过句柄访问或直接指针访问 objects。
    • 句柄访问:reference存储句柄地址,GC时只需更新指针即可保持稳定性。
    • 直接指针访问:reference直接存储对象地址,访问速度更快。
    • 适用于不同GC策略和对象生存状态,需根据场景选择。

    1.3 实战内容

    (待填充)


    2. 垃圾回收器与内存分配策略

    2.1 概述

    • 垃圾回收器主要负责老年代和新生代的内存管理。
    • 新生代垃圾回收频繁,效率较高;老年代垃圾回收效率较低,但存活率高。
    • 垃圾回收的核心任务是判断对象的存活状态,并进行不必要对象的垃圾回收。

    2.2 对象是否存活

    • 引用计数法:维护对象的引用次数,但无法处理循环引用。
    • 可达性分析法:基于GC Roots(如栈中的引用、类静态属性、常量引用等)进行分析。
    • 引用类型:包括强引用、弱引用、软引用和虚引用。

    2.2.4 离散垃圾回收

    • 步骤包括标记和清除,堆中的垃圾回收采取标记-清除或标记-整理算法。
    • 此外,还伴随一定的筛选步骤,确保尽可能减少不必要的回收。

    2.3 垃圾回收算法

  • 标记-清除算法:直接标记不可达的对象并清除。缺点是效率低下、内存碎片较多。
  • 复制算法:将堆划分为Eden和两个Survivor区。
    • 一次性回收Eden空间,存活对象复制到另一Survivor区。
    • 处理Survivor区时,采用分代回收策略,避免频繁扩展老年代。
    • 大对象直接进入老年代,长期存活的对象逐渐被移动到老年代。
  • 2.4 HotSpot的垃圾回收算法实现

    (待填充)

    2.5 垃圾回收器

    • Serial Collector:单线程垃圾回收,适用于内存受限或单核处理器系统。
    • ParNew Collector:多线程垃圾回收,主要处理新生代垃圾回收。
    • Parallel Scavenge Collector:基于复制算法的多线程垃圾回收,兼顾内存使用和收集速度。
    • CMS Collector:基于标记-清除算法,优化了内存标记效率。
    • G1 Collector:面向高并发服务端应用,支持分代收集和空间整合。

    3. Java内存模型与线程

    3.1 Java内存模型

    • Java内存模型隐藏了底层硬件和操作系统的内存访问差异。
    • 主内存与工作内存之间通过锁、unlock、read、write等操作进行交互。

    3.1.2 对于volatile型变量的特殊规则

    • volatile变量确保所有线程可见,但操作并不原子性。
    • 需要插入内存屏障(memory barriers)来保证内存一致性。

    3.1.3 对于long和double型变量的特殊规则

    • 64位数据类型的读写操作可分为两次32位操作,允许虚拟机实现不保证原子性。

    3.1.4 原子性、可见性与有序性

    • 原子性:由JMM保证基本数据类型操作的原子性。
    • 可见性:通过volatile、synchronize、final等关键字保证可见性。
    • 有序性:通过happens-before规则确保操作有序性。

    4. 线程安全与锁优化

    (待填充)


    5. 类文件结构

    (待填充)


    6. 虚拟机类加载机制

    6.1 类加载时机

    • 类的生命周期分为7个阶段:加载、验证、准备、初始化、使用、终止、卸载。
    • 类初始化发生在以下情况:新、getstatic、putstatic、invokestatic、反射调用以及主类初始化。

    6.2 类加载过程

  • 加载:通过类加载器获取二进制流并转化为方法区内存结构。
  • 验证:确保字节流符合JVM规范,确保元数据完整性。
  • 准备:为类变量分配内存并初始化为零。
  • 解析:将符号引用转为直接引用。
  • 初始化:运行时执行类构造器(init方法)。
  • 6.3 类加载器

    • 类加载器基于双亲委派机制,叶子类加载器负责查找和加载类。
    • 常见的类加载器包括启动类加载器、扩展类加载器、程序类加载器以及线程上下文加载器。

    (待填充部分请结合具体内容补充完善)

    上一篇:【转】我在北京已经几年了
    下一篇:内核线程、轻量级进程、用户线程的区别和联系

    发表评论

    最新留言

    能坚持,总会有不一样的收获!
    [***.219.124.196]2025年04月14日 05时12分03秒