JVM面试专题

文章目录

  • [JVM 内存模型及分区](#JVM 内存模型及分区)
    • [1. 堆区(Heap)](#1. 堆区(Heap))
    • [2. 栈区(Stack)](#2. 栈区(Stack))
    • [3. 方法区(Method Area)](#3. 方法区(Method Area))
    • [4. 本地方法栈(Native Method Stack)](#4. 本地方法栈(Native Method Stack))
    • [5. 程序计数器(Program Counter)](#5. 程序计数器(Program Counter))
  • 堆内存分区
    • [1. 新生代(Young Generation)](#1. 新生代(Young Generation))
    • [2. 老年代(Old Generation)](#2. 老年代(Old Generation))
    • 特点
  • 对象创建方法、内存分配与访问定位
    • [1. 对象创建方法:](#1. 对象创建方法:)
    • [2. 对象的内存分配:](#2. 对象的内存分配:)
    • [3. 对象的访问定位:](#3. 对象的访问定位:)
  • [GC 判定方法](#GC 判定方法)
    • [1. 引用计数法(Reference Counting)](#1. 引用计数法(Reference Counting))
    • [2. 引用链法(Reachability Analysis)](#2. 引用链法(Reachability Analysis))
  • [GC 的三种收集方法](#GC 的三种收集方法)
    • [1. 标记清除(Mark-Sweep)](#1. 标记清除(Mark-Sweep))
    • [2. 复制算法(Copying)](#2. 复制算法(Copying))
    • [3. 标记整理(Mark-Compact)](#3. 标记整理(Mark-Compact))
  • 优化收集方法的思路
  • [GC 收集器及特点](#GC 收集器及特点)
    • [1. 并行收集器(Parallel Collector)](#1. 并行收集器(Parallel Collector))
    • [2. 串行收集器(Serial Collector)](#2. 串行收集器(Serial Collector))
    • [3. CMS 收集器(Concurrent Mark-Sweep Collector)](#3. CMS 收集器(Concurrent Mark-Sweep Collector))
    • [4. G1 收集器(Garbage-First Collector)](#4. G1 收集器(Garbage-First Collector))
  • [CMS 收集器与 G1 收集器的特点](#CMS 收集器与 G1 收集器的特点)
  • 收集器优化思路
  • [Minor GC 和 Full GC 发生时机](#Minor GC 和 Full GC 发生时机)
    • [1. Minor GC(YGC)](#1. Minor GC(YGC))
    • [2. Full GC(FGC)](#2. Full GC(FGC))
    • 总结
  • 常用的内存调试工具
    • [1. jmap](#1. jmap)
    • [2. jstack](#2. jstack)
    • [3. jconsole](#3. jconsole)
    • [4. jhat](#4. jhat)
    • [5. Eclipse Memory Analyzer Tool (MAT)](#5. Eclipse Memory Analyzer Tool (MAT))
  • 类加载过程
    • [1. 加载(Loading)](#1. 加载(Loading))
    • [2. 验证(Verification)](#2. 验证(Verification))
    • [3. 准备(Preparation)](#3. 准备(Preparation))
    • [4. 解析(Resolution)](#4. 解析(Resolution))
    • [5. 初始化(Initialization)](#5. 初始化(Initialization))
    • [6. 使用(Using)](#6. 使用(Using))
    • [7. 卸载(Unloading)](#7. 卸载(Unloading))
  • 判断对象存活的方法
    • [1. 引用计数法](#1. 引用计数法)
    • [2. 可达性分析算法(引用链法)](#2. 可达性分析算法(引用链法))
  • 对象回收的过程
  • [简述 java 垃圾回收机制?](#简述 java 垃圾回收机制?)
  • [Java 中的垃圾收集方法](#Java 中的垃圾收集方法)
    • [1. 标记-清除算法](#1. 标记-清除算法)
    • [2. 复制算法](#2. 复制算法)
    • [3. 标记-整理算法](#3. 标记-整理算法)
    • [4. 分代收集](#4. 分代收集)
    • 总结
  • [Java 类加载机制](#Java 类加载机制)
    • [1. 加载(Loading)](#1. 加载(Loading))
    • [2. 校验(Verification)](#2. 校验(Verification))
    • [3. 解析(Resolution)](#3. 解析(Resolution))
    • [4. 初始化(Initialization)](#4. 初始化(Initialization))
    • 总结
  • 类加载器双亲委派模型机制
    • [1. 委派机制](#1. 委派机制)
    • [2. 加载顺序](#2. 加载顺序)
    • [3. 安全性](#3. 安全性)
    • 优点
    • 总结
  • 类加载器及其类型
    • [1. 启动类加载器(Bootstrap ClassLoader)](#1. 启动类加载器(Bootstrap ClassLoader))
    • [2. 扩展类加载器(Extension ClassLoader)](#2. 扩展类加载器(Extension ClassLoader))
    • [3. 系统类加载器(System Class Loader)](#3. 系统类加载器(System Class Loader))
    • [4. 用户自定义类加载器](#4. 用户自定义类加载器)
    • 总结
  • [Java 内存分配与回收策略以及 Minor GC 和 Major GC](#Java 内存分配与回收策略以及 Minor GC 和 Major GC)
  • 总结

JVM 内存模型及分区

JVM(Java Virtual Machine)内存模型将内存划分为不同的区域,主要包括堆区、栈区、方法区、本地方法栈和程序计数器。每个区域都有不同的作用和存储内容。

1. 堆区(Heap)

堆区主要用于存储对象实例和数组,以及成员变量。堆区的对象是在运行时动态分配的。在堆区中存储的内容包括:

  • 初始化的对象
  • 所有的对象实例和数组

2. 栈区(Stack)

栈区采用栈的数据结构,每个方法调用都会创建一个栈帧,并将其压入栈中。栈帧中存储了局部变量表、操作数栈、方法出口等信息。在栈区中存储的内容包括:

  • 局部变量表:存放方法中的参数和局部变量
  • 操作数栈:用于执行操作指令的临时存储区域
  • 方法出口:记录方法执行完后的返回地址

3. 方法区(Method Area)

方法区主要用于存储类信息、常量池、静态变量等数据。方法区是各个线程共享的,存储的内容包括:

  • 类信息:包括类的结构、方法和字段等信息
  • 常量池:存放字符串常量、类名、方法名等
  • 静态变量:存放类的静态字段和常量

4. 本地方法栈(Native Method Stack)

本地方法栈为本地方法(Native Method)服务,用于执行本地方法的调用和执行。与栈区类似,但是专门为本地方法服务。

5. 程序计数器(Program Counter)

程序计数器用于记录当前线程执行的行号或指令地址,保证线程切换后能够恢复到正确的执行位置。

堆内存分区

堆内存分为新生代和老年代。在 Java 8 中,永久代被元空间(Metaspace)取代。新生代主要包括 Eden 区和 Survivor 区(包括 from 区和 to 区),而老年代则是存放长期存活的对象。

1. 新生代(Young Generation)

新生代是对象被创建的地方,经过一段时间后,如果对象还存活,则会晋升到老年代。新生代包含以下区域:

  • Eden 区:初始时,大多数对象都会被分配到 Eden 区。当 Eden 区内存满时,会触发年轻代垃圾回收(Young GC)。
  • Survivor 区:Survivor 区一般有两个,分别称为 from 区和 to 区。存活的对象会被移动到 Survivor 区。在每次垃圾回收后,存活的对象会从一个 Survivor 区复制到另一个 Survivor 区,同时清空原来的 Survivor 区。这个过程叫做 Minor GC(Young GC)。

2. 老年代(Old Generation)

老年代主要存放长期存活的对象,包括经过多次垃圾回收后仍然存活的对象以及大对象。当新生代满时,会触发一次 Full GC(Major GC),即对整个堆进行垃圾回收,包括新生代和老年代。

特点

  • 新生代特点:由于大部分对象很快就会被回收,因此新生代采用了复制算法,即将存活的对象复制到另一个 Survivor 区,减少了碎片化的情况。Eden 区在每次垃圾回收后都会被清空,同时整理内存。
  • 老年代特点:存放长期存活的对象,因此采用标记清除算法或标记整理算法进行垃圾回收。当堆内存不够时,会触发 Full GC,对整个堆进行回收。

对象创建方法、内存分配与访问定位

在 Java 中,创建对象的过程涉及到对象的内存分配和访问定位。下面是关于这些过程的详细说明:

1. 对象创建方法:

使用 new 关键字创建对象是最常见的方式,其语法为:

java 复制代码
ClassName objectName = new ClassName();

这将在堆内存中分配一块新的内存空间来存储对象,并返回该对象的引用。

2. 对象的内存分配:

对象的内存分配通常发生在 Java 虚拟机的堆内存中。当使用 new 关键字创建对象时,JVM 将会在堆内存中为该对象分配一块连续的内存空间。分配的内存空间足够存储对象的所有实例变量,并且会自动初始化为默认值。

3. 对象的访问定位:

在 Java 中,通过对象的引用来访问对象。当使用 new 创建对象时,会返回一个指向该对象的引用。通过这个引用,可以访问对象的属性和方法。引用是对象在内存中的地址,通过它可以在堆内存中定位到对象的实际数据。

java 复制代码
ClassName objectName = new ClassName(); // 创建对象并获取引用
objectName.method(); // 访问对象的方法
objectName.field = value; // 访问对象的属性并赋值

GC 判定方法

GC(Garbage Collection)是 Java 中用于自动管理内存的机制。它通过检测和回收不再使用的对象来释放内存空间。在 Java 中,有两种常见的 GC 判定方法:

1. 引用计数法(Reference Counting)

引用计数法是一种简单的 GC 判定方法,其思想是对对象进行引用计数。当有一个地方引用了对象时,该对象的引用计数加一;当引用失效时,引用计数减一。当引用计数为 0 时,表示该对象不再被引用,可以被回收。然而,Java 的 JVM 并不采用这种方式,因为它无法处理相互循环引用的情况,如 A 引用 B,B 引用 A。

2. 引用链法(Reachability Analysis)

引用链法是 Java 虚拟机常用的 GC 判定方法。它通过一组称为 GC ROOT 的对象作为起点,对所有存活对象进行可达性分析。如果存在一条从 GC ROOT 对象到某个对象的引用链,则说明该对象是可达的,即在程序中仍然有用,不应该被回收;反之,如果不存在这样的引用链,则说明该对象已经不再被程序所引用,可以被回收。

GC ROOT 对象包括静态变量引用的对象、本地变量引用的对象(当前活动线程的栈帧中的对象)、方法区中类静态属性引用的对象等。通过这种方式,Java 虚拟机可以准确地确定哪些对象是可回收的,从而进行垃圾回收。

GC 的三种收集方法

在 Java 中,常见的 GC 收集方法包括标记清除、标记整理和复制算法。下面是它们的原理与特点以及应用场景的详细说明:

1. 标记清除(Mark-Sweep)

  • 原理与特点:标记清除算法分为两个阶段,首先标记所有活动对象,然后清除所有未标记的对象。这种方法简单直接,但会产生内存碎片。

  • 应用场景:适用于堆内存中的老年代,因为老年代中存放的对象生命周期较长,不需要频繁地进行内存整理。

2. 复制算法(Copying)

  • 原理与特点:复制算法将堆内存分为两块大小相同的区域,通常称为 Eden 区和 Survivor 区。对象首先被分配到 Eden 区,当 Eden 区满时,存活的对象会被复制到另一个 Survivor 区,并清空原来的 Eden 区。经过多次垃圾回收后存活的对象会被移到老年代。

  • 应用场景:主要用于新生代的垃圾回收,因为新生代中对象的生命周期短,频繁进行垃圾回收。

3. 标记整理(Mark-Compact)

  • 原理与特点:标记整理算法首先标记所有活动对象,然后将所有存活的对象向堆的一端移动,然后清理掉堆尾端的内存空间。这种方法消除了内存碎片,但需要额外的移动操作。

  • 应用场景:适用于老年代,因为老年代中对象的生命周期较长,且需要减少内存碎片化的问题。

优化收集方法的思路

针对当前的 GC 收集方法,可以考虑以下优化思路:

  1. 增量式 GC:将 GC 过程分解为多个小步骤执行,与程序执行交错进行,减少单次 GC 导致的停顿时间,提高系统的响应性。

  2. 并发 GC:在程序执行的同时,进行垃圾回收操作,减少 GC 导致的停顿时间,提高系统的吞吐量。

  3. 区域化 GC:将堆内存划分为多个区域,针对不同的区域采用不同的 GC 策略,根据对象的特性和生命周期进行灵活的垃圾回收操作。

  4. 增量标记整理:将标记整理算法分解为多个小步骤执行,与程序执行交错进行,减少单次 GC 导致的停顿时间,提高系统的响应性。

GC 收集器及特点

Java 中常见的 GC(Garbage Collection)收集器有并行收集器、串行收集器、CMS(Concurrent Mark-Sweep)收集器和 G1(Garbage-First)收集器。

1. 并行收集器(Parallel Collector)

并行收集器使用多个线程进行垃圾回收,以提高回收效率。在进行 GC 时,会停止服务一段时间。它适用于多核 CPU 和需要较短停顿时间的应用场景。

2. 串行收集器(Serial Collector)

串行收集器使用单个线程进行垃圾回收,适用于单核 CPU 或者对停顿时间要求不高的应用场景。在次要回收时,串行收集器会使用多线程来执行。

3. CMS 收集器(Concurrent Mark-Sweep Collector)

CMS 收集器基于"标记---清除"算法实现,它允许在主程序运行的同时进行垃圾回收,以减少停顿时间。但是,它可能会产生碎片,而且需要经过多次标记才能完成清除。

4. G1 收集器(Garbage-First Collector)

G1 收集器是一种基于"标记---整理"和"复制"算法的混合型收集器。它将堆分割为多个区域,每个区域可以根据垃圾回收的需要进行收集。它既可以减少停顿时间,又可以避免产生过多的碎片。

CMS 收集器与 G1 收集器的特点

  • CMS 收集器

    • 允许在主程序运行的同时进行垃圾回收,以降低停顿时间。
    • 使用"标记---清除"算法,可能产生内存碎片。
    • 需要多次标记才能完成清除。
  • G1 收集器

    • 将堆分割为多个区域,采用分代收集算法。
    • 综合了"标记---整理"和"复制"算法,可以根据垃圾回收的需要进行区域化收集。
    • 在整体上采用"标记---整理"算法,而在局部(两个 Region 之间)上采用"复制"算法。

收集器优化思路

若要优化 GC 收集器,可以考虑以下方面:

  • 改进算法:针对应用场景选择合适的垃圾回收算法,如并行、串行、标记---清除或标记---整理。
  • 提高并行度:增加并发线程数以提高垃圾回收效率。
  • 内存分配优化:调整堆大小、调优年轻代和老年代比例等来减少垃圾回收频率。
  • 碎片整理:减少或避免内存碎片化,提高内存利用率。

Minor GC 和 Full GC 发生时机

在 Java 中,Minor GC(Minor Garbage Collection)和 Full GC(Full Garbage Collection)是两种不同类型的垃圾回收事件,它们的发生时机不同:

1. Minor GC(YGC)

Minor GC 发生在新生代内存不足时,也称为 Young GC(YGC)。它主要针对新生代进行垃圾回收,清理不再被引用的对象。当 Eden 区或 Survivor 区空间不足时,就会触发 Minor GC。在 Minor GC 中,存活的对象会被复制到另一个 Survivor 区,同时清空原来的 Survivor 区和 Eden 区,从而为新对象腾出空间。

2. Full GC(FGC)

Full GC 发生在整个堆内存不足时,它会清理整个堆内存,包括新生代和老年代。Full GC 会停止应用程序的运行,因为它需要对整个堆进行垃圾回收。Full GC 可能会由多种原因触发,例如老年代内存不足、永久代(在 Java 8 中为 Metaspace)内存不足、系统显式调用垃圾回收等。

总结

  • Minor GC 主要发生在新生代内存不足时,用于清理新生代的垃圾对象。
  • Full GC 发生在整个堆内存不足时,清理整个堆内存,包括新生代和老年代。
  • Minor GC 的发生不会停止应用程序的运行,而 Full GC 可能会导致应用程序的停顿。

常用的内存调试工具

在 Java 开发中,有多种内存调试工具可供使用,其中包括:

1. jmap

jmap 是 Java Virtual Machine Memory Map 的缩写,用于查看 Java 进程的内存映射信息。通过 jmap 命令可以查看 Java 堆中的对象信息、类信息、堆转储快照等。

2. jstack

jstack 可以打印 Java 进程中各个线程的堆栈信息,帮助定位线程死锁、死循环等问题。通过 jstack 命令可以获取 Java 进程中每个线程的堆栈轨迹、线程状态等信息。

3. jconsole

jconsole 是 Java 自带的监控和管理工具,提供了 GUI 界面,可以实时监控 Java 应用程序的运行状态,包括内存使用情况、线程状态、类加载情况等,还可以执行垃圾回收、线程转储等操作。

4. jhat

jhat 是 Java Heap Analysis Tool 的缩写,用于分析 Java 堆转储快照文件。它能够将堆转储文件加载到内存中,并通过 web 页面进行分析和浏览。

5. Eclipse Memory Analyzer Tool (MAT)

MAT 是 Eclipse 的一款内存分析工具,用于分析 Java 应用程序的内存使用情况。它可以帮助开发人员定位内存泄漏、大对象占用等问题,并提供了丰富的分析功能和图形化界面。

类加载过程

Java 类加载过程包括加载、验证、准备、解析、初始化等阶段,以及使用和卸载阶段。

1. 加载(Loading)

加载阶段是将类的字节码文件加载到内存中,并生成对应的 Class 对象。这个过程通常是通过类加载器(ClassLoader)来完成的,根据类的全限定名在类路径中查找并加载对应的字节码文件。

2. 验证(Verification)

验证阶段是确保被加载的类文件符合 Java 虚拟机规范,包括文件格式校验、元数据验证、字节码校验等。主要目的是防止恶意代码对 Java 虚拟机的安全漏洞。

3. 准备(Preparation)

准备阶段是为类的静态变量分配内存并设置默认初始值。在这个阶段,不会执行类的任何 Java 代码,而是为静态变量分配内存空间,将其初始化为默认值(零值)。

4. 解析(Resolution)

解析阶段是将类中的符号引用转换为直接引用,即将类、字段、方法的引用转化为直接指针或偏移量。这个阶段会将类加载阶段生成的符号引用替换为直接引用。

5. 初始化(Initialization)

初始化阶段是执行类的初始化代码,包括静态变量的赋值和静态代码块的执行。在这个阶段,JVM 会按照程序员指定的顺序执行类的静态初始化代码。

6. 使用(Using)

使用阶段是指在程序运行过程中使用已加载和初始化的类。

7. 卸载(Unloading)

卸载阶段是指当类加载器不再引用某个类时,或者类的所有实例都已被回收时,类被卸载(GC)。这个阶段由 Java 虚拟机自动管理,当类不再被引用时,会被卸载并释放内存空间。

判断对象存活的方法

判断对象是否存活有两种主要方法:

1. 引用计数法

引用计数法是一种简单的对象存活判定方法,每个对象都有一个引用计数器。当有一个地方引用该对象时,计数器加一;当引用失效时,计数器减一。当对象的计数器为零时,表示该对象没有被引用,即为"死对象",可以被垃圾回收。然而,引用计数法无法解决循环引用的问题,因此主流的虚拟机没有采用这种算法。

2. 可达性分析算法(引用链法)

可达性分析算法从一组称为 GC Roots 的对象开始,向下搜索引用链。如果一个对象到 GC Roots 没有任何引用链相连,则说明该对象不可达,即为"死对象",可以被垃圾回收。在 Java 中,作为 GC Roots 的对象包括虚拟机栈中引用的对象、方法区类静态属性引用的对象、方法区常量池引用的对象以及本地方法栈 JNI 引用的对象等。

对象回收的过程

  1. 第一次标记与筛选:当对象不可达 GC Roots 时,会进行第一次标记并进行筛选,判断是否有必要执行 finalize() 方法。如果对象没有覆盖 finalize() 方法或者已被虚拟机调用过,则认为是没必要的。

  2. finalize() 方法执行:若对象有必要执行 finalize() 方法,则将其放入一个称为 F-Queue 的队列中,并触发一个 Finalize() 线程去执行。该线程是低优先级的,并且虚拟机不会承诺一直等待它运行完。若 finalize() 执行缓慢或者发生死锁,可能导致 F-Queue 队列一直等待,影响内存回收系统的稳定性。

  3. 第二次标记与回收:GC 对处于 F-Queue 中的对象进行第二次标记,之后移除"即将回收"集合,等待回收。

Java 的垃圾回收机制是一种自动内存管理机制,程序员不需要显式释放对象的内存,而是由 Java 虚拟机(JVM)负责执行。以下是简述 Java 垃圾回收机制的要点:

  1. 自动执行:Java 的垃圾回收是自动执行的,程序员无需手动释放对象的内存。垃圾回收由虚拟机的垃圾回收线程负责执行,通常是在虚拟机空闲或者当前堆内存不足时触发执行。

  2. 低优先级线程:垃圾回收线程是一个低优先级线程,在正常情况下不会主动执行,只有在特定条件下才会触发执行。这保证了垃圾回收不会影响程序的正常运行。

  3. 扫描未引用对象:垃圾回收线程负责扫描内存中的对象,找出那些没有被任何引用的对象。如果一个对象没有被任何活动对象引用,那么它将被判定为垃圾对象。

  4. 回收垃圾对象:一旦垃圾回收线程确定了哪些对象是垃圾对象,它会将这些对象添加到要回收的集合中。随后,垃圾回收器会执行相应的垃圾回收算法,对这些垃圾对象进行回收释放内存空间。

  5. 内存回收算法:Java 的垃圾回收器采用了不同的内存回收算法,如标记-清除算法、复制算法、标记-整理算法等,来回收不同内存区域的垃圾对象。

  6. 内存管理优化:Java 虚拟机的垃圾回收机制对内存管理进行了优化,包括分代垃圾回收、并发垃圾回收、增量垃圾回收等技术,以提高垃圾回收的效率和性能。

简述 java 垃圾回收机制?

Java 的垃圾回收机制是一种自动内存管理机制,程序员不需要显式释放对象的内存,而是由 Java 虚拟机(JVM)负责执行。

以下是简述 Java 垃圾回收机制的要点:

  1. 自动执行:Java 的垃圾回收是自动执行的,程序员无需手动释放对象的内存。垃圾回收由虚拟机的垃圾回收线程负责执行,通常是在虚拟机空闲或者当前堆内存不足时触发执行。

  2. 低优先级线程:垃圾回收线程是一个低优先级线程,在正常情况下不会主动执行,只有在特定条件下才会触发执行。这保证了垃圾回收不会影响程序的正常运行。

  3. 扫描未引用对象:垃圾回收线程负责扫描内存中的对象,找出那些没有被任何引用的对象。如果一个对象没有被任何活动对象引用,那么它将被判定为垃圾对象。

  4. 回收垃圾对象:一旦垃圾回收线程确定了哪些对象是垃圾对象,它会将这些对象添加到要回收的集合中。随后,垃圾回收器会执行相应的垃圾回收算法,对这些垃圾对象进行回收释放内存空间。

  5. 内存回收算法:Java 的垃圾回收器采用了不同的内存回收算法,如标记-清除算法、复制算法、标记-整理算法等,来回收不同内存区域的垃圾对象。

  6. 内存管理优化:Java 虚拟机的垃圾回收机制对内存管理进行了优化,包括分代垃圾回收、并发垃圾回收、增量垃圾回收等技术,以提高垃圾回收的效率和性能。

总之,Java 的垃圾回收机制通过自动扫描和回收不再被引用的对象,实现了内存的自动管理和释放,帮助程序员简化了内存管理的工作。

Java 中的垃圾收集方法

Java 中的垃圾收集方法主要包括标记-清除、复制算法、标记-整理以及分代收集等。

1. 标记-清除算法

  • 标记-清除算法是最基础的垃圾收集算法之一,其思想是首先标记出要被回收的对象,然后统一进行回收。
  • 缺点包括效率较低(标记和清除的效率都较低)以及会产生大量不连续的内存碎片,导致后续程序分配内存时可能触发额外的 GC 动作。

2. 复制算法

  • 复制算法通过将可用内存按容量划分为两部分,每次只使用其中一部分,当一部分内存用完时,将存活的对象复制到另一部分,然后清除原部分内存中的对象。
  • 改进的复制算法将内存划分为 Eden 区、两个 Survivor 区,采用分代收集的方式,解决了内存代价过高的问题。

3. 标记-整理算法

  • 标记-整理算法的目的是解决标记-清除算法产生的内存碎片问题。它在清除对象时,将存活对象移动到内存的一端,然后清除边界以外的对象,从而避免了内存碎片化。

4. 分代收集

  • 分代收集是现代虚拟机垃圾收集的主流方式,将堆内存分为新生代和老年代。
  • 在新生代中,采用复制算法,因为对象生命周期短,每次回收都会有大量对象死去,适合复制算法的特点。
  • 老年代中的对象存活率较高,适合采用标记-整理或标记-清除算法。

总结

Java 中的垃圾收集方法包括标记-清除、复制算法、标记-整理以及分代收集等。每种方法都有其适用的场景和优缺点,理解这些方法有助于选择合适的垃圾收集策略,并优化 Java 应用程序的性能和内存利用率。

Java 类加载机制

Java 类加载机制是 Java 虚拟机将描述类的数据从 Class 文件加载到内存,并对数据进行校验、解析和初始化的过程,最终形成可以被虚拟机直接使用的 Java 类型的过程。

1. 加载(Loading)

加载阶段是类加载过程的第一步,主要负责将类的二进制数据读入到内存中,并生成一个代表该类的 Class 对象。这个过程主要包括:

  • 通过类的全限定名获取类的二进制字节流。
  • 将字节流代表的静态存储结构转化为方法区的运行时数据结构。
  • 在内存中生成一个代表该类的 Class 对象,作为方法区中类的数据访问入口。

2. 校验(Verification)

校验阶段是类加载过程的第二步,主要负责对加载的类进行各种校验,确保被加载的类符合 Java 虚拟机规范的要求。校验阶段主要包括:

  • 文件格式校验:检查字节码文件是否符合 Class 文件格式规范。
  • 元数据校验:对类的元数据信息进行校验,如是否有父类、是否继承了 final 类等。
  • 字节码校验:对字节码的执行进行校验,以防止恶意代码执行。

3. 解析(Resolution)

解析阶段是类加载过程的第三步,主要负责将常量池中的符号引用替换为直接引用的过程。解析阶段主要包括:

  • 类或接口的解析:将符号引用解析为对应的直接引用。
  • 字段解析:解析字段的地址。
  • 方法解析:解析方法的地址。

4. 初始化(Initialization)

初始化阶段是类加载过程的最后一步,主要负责对类进行初始化,为静态变量赋初始值,执行静态代码块等。初始化阶段在必要时会触发类的初始化,包括:

  • 执行类的静态初始化块(static{})。
  • 为静态变量赋初始值。
  • 调用类的静态方法。

总结

Java 类加载机制包括加载、校验、解析和初始化四个阶段,每个阶段都有特定的任务和作用,保证了类的正确加载和初始化,从而使得 Java 程序能够顺利执行。

类加载器双亲委派模型机制

类加载器双亲委派模型是 Java 中的一种类加载机制,它规定了类加载器的层级关系和加载方式,确保了类的加载过程是有序和安全的。

1. 委派机制

  • 当一个类加载器收到加载请求时,它不会自行加载这个类,而是将加载请求委派给其父类加载器。
  • 父类加载器会按照同样的方式进行处理,即继续将加载请求委派给其父类加载器,直到达到最顶层的启动类加载器(Bootstrap ClassLoader)。

2. 加载顺序

  • 类加载器在接收到加载请求后,会按照双亲委派模型的规则,先让父类加载器尝试加载这个类。
  • 如果父类加载器无法加载该类(即父类加载器的搜索范围内找不到该类),则反馈给子类加载器,由子类加载器尝试加载该类。

3. 安全性

  • 类加载器双亲委派模型保证了类的加载过程是有序和安全的,防止了同名类被重复加载,避免了类的冲突和安全漏洞。

优点

  • 分层加载:通过层级关系,不同的类加载器可以加载不同位置的类,实现了类加载的分层管理。
  • 安全可靠:由于类加载器在加载过程中严格按照双亲委派模型的规则,保证了类的唯一性和安全性。

总结

类加载器双亲委派模型规定了类加载器的层级关系和加载顺序,保证了类的加载过程是有序、安全且可靠的。这种机制在 Java 中被广泛应用,有效地管理了类加载的过程,提高了系统的稳定性和安全性。

类加载器及其类型

类加载器是实现通过类的权限定名获取该类的二进制字节流的代码块。在 Java 中,主要有以下四种类加载器:

1. 启动类加载器(Bootstrap ClassLoader)

  • 用于加载 Java 核心类库,无法被 Java 程序直接引用。
  • 是虚拟机的一部分,通常由 C++ 编写,不继承自 java.lang.ClassLoader。
  • 负责加载 Java 运行时所必需的类,如 java.lang 包等。

2. 扩展类加载器(Extension ClassLoader)

  • 用于加载 Java 的扩展库,即 Java 虚拟机提供的扩展库目录中的类。
  • 通常由 Java 编写,是 sun.misc.Launcher$ExtClassLoader 类的实例。
  • 可通过 java.ext.dirs 系统属性指定扩展库的目录。

3. 系统类加载器(System Class Loader)

  • 也称为应用类加载器,是加载 Java 应用程序类的默认类加载器。
  • 根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。
  • 可以通过 ClassLoader.getSystemClassLoader() 方法获取。

4. 用户自定义类加载器

  • 可通过继承 java.lang.ClassLoader 类的方式实现自定义的类加载器。
  • 用户可以根据需求实现自己的类加载器,如网络加载器、动态编译器加载器等。

总结

类加载器是 Java 虚拟机的重要组成部分,负责将类的二进制字节流加载到内存中。Java 中存在启动类加载器、扩展类加载器、系统类加载器以及用户自定义类加载器等多种类型的类加载器,每种加载器都有特定的加载范围和加载顺序,用于满足不同场景下的类加载需求。

Java 内存分配与回收策略以及 Minor GC 和 Major GC

Java 内存分配与回收策略主要包括对象优先在堆的 Eden 区分配、大对象直接进入老年代以及长期存活的对象直接进入老年代等。

  1. 对象优先在堆的 Eden 区分配

    • Java 对象的内存分配通常优先在堆的 Eden 区进行。Eden 区是新生代的一部分,用于存放新创建的对象。
    • 大多数对象在创建时会直接被分配在 Eden 区,因为它们的生命周期较短,很快就会被回收。
  2. 大对象直接进入老年代

    • 对于较大的对象,如大数组等,虚拟机可能直接将它们分配到老年代,避免在新生代频繁进行垃圾回收。
    • 这样做的目的是为了防止在新生代中产生大量的内存碎片,影响后续对象的分配。
  3. 长期存活的对象直接进入老年代

    • 对象的年龄会根据其经历过的 Minor GC 的次数进行统计。如果对象经过多次 Minor GC 后仍然存活,那么会被晋升到老年代。
    • 这样做的目的是为了减少 Minor GC 的频率,提高垃圾回收的效率。
  4. Minor GC 和 Major GC

    • Minor GC 通常发生在新生代的 Eden 区,用于回收新生代的内存空间。在 Eden 区对象的生存期很短,因此 Minor GC 的频率较高,但回收速度也较快。
    • Major GC(或称为 Full GC)通常发生在老年代,主要用于回收老年代的内存空间。一般情况下,触发老年代 GC 的时候不会同时触发 Minor GC,但可以通过配置在 Full GC 之前进行一次 Minor GC,以加快老年代的回收速度。

总结

Java 内存分配与回收策略通过对象优先在 Eden 区分配、大对象直接进入老年代以及长期存活的对象直接进入老年代等方式,有效管理内存分配和回收。Minor GC 和 Major GC 分别用于回收新生代和老年代的内存空间,以保持 Java 应用程序的性能和稳定性。

相关推荐
王中阳Go4 小时前
字节跳动的微服务独家面经
微服务·面试·golang
懒洋洋大魔王9 小时前
7.Java高级编程 多线程
java·开发语言·jvm
只吹45°风9 小时前
JVM-类加载器的双亲委派模型详解
jvm·类加载器·双亲委派
宇宙李10 小时前
2024java面试-软实力篇
面试·职场和发展
孙小二写代码11 小时前
[leetcode刷题]面试经典150题之1合并两个有序数组(简单)
算法·leetcode·面试
珊珊而川12 小时前
【浏览器面试真题】sessionStorage和localStorage
前端·javascript·面试
markzzw12 小时前
我在 Thoughtworks 被裁前后的经历
前端·javascript·面试
五味香12 小时前
C++学习,动态内存
java·c语言·开发语言·jvm·c++·学习·算法
无名之逆12 小时前
计算机专业的就业方向
java·开发语言·c++·人工智能·git·考研·面试
爱棋笑谦13 小时前
二叉树计算
java·开发语言·数据结构·算法·华为od·面试