JVM知识点总结

什么是JVM内存结构?

img

jvm将虚拟机分为5大区域,程序计数器、虚拟机栈、本地方法栈、java堆、方法区;

  1. 程序计数器(Program Counter): 程序计数器是一块较小的内存区域,它用于指示当前线程执行的字节码指令的地址。在多线程环境下,每个线程都有自己独立的程序计数器,确保线程切换后能够正确恢复执行。
  2. Java虚拟机栈(Java Virtual Machine Stack): 每个Java方法在执行时都会创建一个栈帧,用于存储方法的局部变量、操作数栈、方法返回值等。栈帧随着方法的调用和返回而入栈和出栈。栈的大小可以在JVM启动时设置,如果线程请求的栈深度超过这个限制,会抛出StackOverflowError。
  3. 本地方法栈(Native Method Stack): 与Java虚拟机栈类似,但本地方法栈为本地(Native)方法服务。本地方法是使用其他语言(如C、C++)编写的,可以通过Java Native Interface(JNI)被Java程序调用。
  4. 堆(Heap): 堆是Java虚拟机中最大的一块内存区域,用于存放对象实例。所有通过关键字new创建的对象都存储在堆中。堆的大小可以在启动时或运行时调整,如果堆中没有足够的空间来创建新对象,会触发垃圾回收来清理无用的对象以腾出空间。
  5. 方法区(Method Area): 方法区用于存储已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。在Java 8及之前的版本,方法区是永久代(PermGen Space)。但在Java 8及以后的版本,永久代被元空间(Metaspace)所取代。

heap 和stack 有什么区别?

  1. 堆(Heap):
    • 用途:堆是用于存放Java程序中创建的对象实例的区域。所有通过new关键字创建的对象都存储在堆中。
    • 特点:堆是一个动态分配的区域,它的大小可以在JVM启动时或运行时进行调整。Java堆是所有线程共享的,因此它是多线程共享的数据区域。
    • 垃圾回收:由于堆存放了大量的对象,JVM需要进行垃圾回收来回收不再使用的对象以释放内存空间。垃圾回收器会自动管理堆内存的释放和整理。
  1. 栈(Stack):
    • 用途:栈用于存放线程的执行上下文和局部变量。每个线程在执行时,都会创建一个对应的栈帧,用于存储方法的局部变量、操作数栈、方法返回值等。
    • 特点:栈是一个后进先出(Last-In-First-Out,LIFO)的数据结构。每个方法调用都会在栈上创建一个栈帧,方法返回后该栈帧会被销毁。栈是线程私有的,因此它是线程安全的。
    • 生命周期:栈的生命周期与线程的生命周期相同。当线程结束时,其对应的栈也会被销毁。

主要区别:

  • 数据类型:堆用于存储对象实例,而栈用于存储方法调用的局部变量和执行上下文。
  • 大小和分配方式:堆是动态分配的,大小可以调整,而栈是固定大小的,由系统自动分配和管理。
  • 线程共享与私有:堆是所有线程共享的,而栈是线程私有的。
  • 生命周期:堆的生命周期取决于对象的引用,当对象不再被引用时,垃圾回收器会将其回收。栈的生命周期与线程的生命周期相同,当线程结束时,其对应的栈也会被销毁。
  • 数据结构:堆是一个复杂的数据结构,因为它需要动态管理和垃圾回收。而栈是一个简单的数据结构,遵循后进先出原则。

什么情况下会发生栈内存溢出?

栈内存溢出(StackOverflowError)是指当线程的栈空间不足以支持方法调用的深度时发生的错误。每当一个方法被调用,JVM都会在栈上为该方法分配一个栈帧(stack frame),栈帧包含方法的局部变量、操作数栈和方法返回值等信息。当方法调用的嵌套层次过深,栈帧占用的空间超过了栈的限制,就会导致栈内存溢出。

栈内存溢出的主要原因是递归调用(Recursive Call)或者某个方法调用自身(自递归)。当递归没有正确的终止条件或者终止条件设计不当时,很容易导致栈内存溢出。

以下是一些导致栈内存溢出的常见情况:

  1. 无限递归:方法在其自身内部调用自身,并且缺乏适当的终止条件。例如:
javaCopy code
public void recursiveMethod() {
    recursiveMethod(); // 无限递归
}
  1. 递归深度过大:虽然递归本身是有限的,但当递归的层数很大时,栈空间可能不足。这通常发生在递归算法未经优化或输入数据规模过大的情况下。
  2. 大对象引起的递归:某个方法调用时创建了一个特别大的对象,导致栈帧的空间不够容纳过多的方法调用。
  3. 多线程递归调用:多个线程之间相互调用,形成递归调用链。

避免栈内存溢出的方法包括:

  • 合理设计递归算法,确保递归有正确的终止条件。
  • 使用循环而不是递归来解决问题。
  • 如果必须使用递归,确保递归的深度不会过大,尽量避免过多的嵌套调用。
  • 增加JVM的栈大小(通过调整-Xss参数),但这并不是解决根本问题的最佳方法,因为栈空间是有限的。

当发生栈内存溢出时,通常会抛出StackOverflowError异常,并且程序会终止运行。

如何判断一个对象是否存活?

  1. 引用计数: 引用计数是一种简单的垃圾回收算法,它通过在对象上记录被引用的次数来判断对象是否存活。每当一个对象被引用时,其引用计数加一;当引用失效或被释放时,计数减一。当对象的引用计数为零时,表示该对象不再被引用,可以被垃圾回收器回收。不过,引用计数算法有一个明显的问题:无法解决循环引用问题。例如,对象A引用对象B,对象B引用对象A,虽然它们的引用计数都不为零,但实际上它们已经不再被任何其他对象引用,应该被回收,但由于引用计数不为零,它们将无法被回收。
  2. 可达性分析: Java虚拟机采用的主要垃圾回收算法是"可达性分析"。这是一种基于"根对象"(Root Object)的算法。根对象可以是类加载器(ClassLoader)、静态变量、JNI(Java Native Interface)引用等。通过从根对象开始遍历所有的引用关系,能够找到所有能够被直接或间接访问到的对象,这些对象被称为"可达对象"(Reachable Object)。如果一个对象不可达,即无法通过任何引用链访问到它,那么该对象被认为是不再存活的,即可以被垃圾回收。可达性分析算法解决了引用计数算法的循环引用问题,并且能够准确判断对象是否存活。在Java中,垃圾回收器会根据对象的存活状态定期或根据内存情况进行垃圾回收操作。当对象不再存活时,垃圾回收器会将其标记为可回收,并在合适的时机进行垃圾回收,回收其所占用的内存空间。这样,Java程序就可以免去手动管理内存的负担,而是由垃圾回收器自动处理不再使用的对象。

强引用、软引用、弱引用、虚引用是什么,有什么区别?

  1. 强引用(Strong Reference): 强引用是最常见的引用类型,它会使对象保持存活,只要强引用存在,对象就不会被垃圾回收器回收。例如:
Object obj = new Object(); // 强引用,obj指向一个对象实例
  1. 软引用(Soft Reference): 软引用允许对象在内存不足时被垃圾回收器回收。当系统内存足够时,软引用不会被回收;但当系统内存不足时,垃圾回收器会尝试回收软引用对象,从而释放更多内存。可以使用java.lang.ref.SoftReference类来创建软引用。例如:
SoftReference<Object> softRef = new SoftReference<>(new Object());
  1. 弱引用(Weak Reference): 弱引用的生命周期更短暂,当垃圾回收器运行时,无论系统内存是否充足,弱引用都会被回收。可以使用java.lang.ref.WeakReference类来创建弱引用。弱引用通常用于实现内存敏感的高速缓存或者对象的临时引用。例如:
WeakReference<Object> weakRef = new WeakReference<>(new Object());
  1. 虚引用(Phantom Reference): 虚引用是最弱的一种引用类型,它在任何时候都可能被垃圾回收器回收。虚引用的主要作用是跟踪对象是否被垃圾回收。可以使用java.lang.ref.PhantomReference类来创建虚引用。虚引用必须与引用队列(ReferenceQueue)一起使用,当对象被垃圾回收时,它将被添加到引用队列中,可以用于进行后续处理。例如:
ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
PhantomReference<Object> phantomRef = new PhantomReference<>(new Object(), referenceQueue);

区别:

  • 影响垃圾回收:强引用可以阻止对象被垃圾回收,而软引用、弱引用和虚引用允许对象在一定条件下被回收。
  • 回收策略:软引用和弱引用允许对象在内存不足时被回收,软引用有更高的优先级,会在内存不足时最后被回收。虚引用必须与引用队列配合使用,并且在被回收时会被添加到引用队列中,用于进行后续处理。
  • 生命周期:强引用的生命周期最长,只有在没有引用时才会被回收;软引用的生命周期较长,但会在内存不足时被回收;弱引用的生命周期较短,当没有强引用时就会被回收;虚引用的生命周期最短,即使有强引用存在,也会在任何时候被回收。

被引用的对象就一定能存活吗?

不一定,看 Reference 类型,弱引用在 GC 时会被回收,软引用在内存不足的时候,即 OOM 前会被回收,但如果没有在 Reference Chain 中的对象就一定会被回收。

Java中的垃圾回收算法有哪些?

  1. 标记-清除算法(Mark and Sweep):
    • 过程:
      • 标记阶段:从根对象开始,通过可达性分析标记所有存活对象。
      • 清除阶段:清理未标记的对象,回收它们的内存空间。
    • 优点:不需要移动对象。
    • 缺点:产生大量的内存碎片,效率较低。
  1. 复制算法(Copying Algorithm):
    • 过程:
      • 将堆空间分为From区和To区。
      • 当From区满时,将存活对象复制到To区,然后清空From区。
    • 优点:回收效率高,不产生内存碎片。
    • 缺点:浪费一半的空间。
  1. 标记-压缩算法(Mark-Compact):
    • 过程:
      • 标记阶段:从根对象开始,通过可达性分析标记所有存活对象。
      • 压缩阶段:将存活对象压缩到堆的一端,然后清理堆的另一端。
    • 优点:解决了内存碎片问题。
    • 缺点:压缩过程较复杂,效率相对较低。
  1. 分代收集算法(Generational Collection):
    • 过程:将堆分为新生代和老年代,根据对象生命周期采用不同的算法。
    • 优点:充分利用对象的特点提高垃圾回收效率。
    • 缺点:增加了算法的复杂性。
  1. 增量垃圾回收算法(Incremental Garbage Collection):
    • 过程:将垃圾回收过程分为多个阶段,每次回收一小部分对象。
    • 优点:减少长时间停顿,提高系统响应性。
    • 缺点:增加了垃圾回收器的实现复杂度。
  1. 并发垃圾回收算法(Concurrent Garbage Collection):
    • 过程:允许垃圾回收器和应用程序同时执行。
    • 优点:减少垃圾回收对系统性能的影响。
    • 缺点:增加了垃圾回收器的实现复杂度,可能会降低吞吐量。

img

有哪几种垃圾回收器,各自的优缺点是什么?

  1. Serial收集器(Serial Garbage Collector):
    • 单线程的垃圾回收器,适用于单核CPU或小内存环境。
    • 使用复制算法(Copying Algorithm)来回收新生代,标记-压缩算法来回收老年代。
    • 优点:简单高效,在单核环境中表现良好。
    • 缺点:垃圾回收过程会造成应用程序暂停。
  1. Parallel收集器(Parallel Garbage Collector):
    • 多线程的垃圾回收器,适用于多核CPU环境。
    • 使用复制算法来回收新生代,标记-压缩算法来回收老年代。
    • 优点:并行回收能充分利用多核CPU,回收效率较高。
    • 缺点:垃圾回收过程会造成应用程序暂停。
  1. CMS收集器(Concurrent Mark-Sweep Garbage Collector):
    • 并发垃圾回收器,用于回收老年代。
    • 使用标记-清除算法,标记阶段与应用程序并发执行,尽量减少停顿时间。
    • 优点:减少了垃圾回收对应用程序的影响,响应性较好。
    • 缺点:由于并发执行,垃圾回收线程与应用程序竞争CPU资源,可能影响应用吞吐量。
  1. G1收集器(Garbage-First Garbage Collector):
    • 分代垃圾回收器,适用于大内存环境。
    • 将堆划分为多个区域(Region),根据需求优先回收垃圾最多的区域。
    • 优点:在大内存环境中表现较好,较低的停顿时间。
    • 缺点:垃圾回收过程较为复杂。

详细说一下CMS的回收过程?CMS的问题是什么?

CMS(Concurrent Mark-Sweep)是一种并发垃圾回收器,主要用于回收Java堆中的老年代。与传统的垃圾回收器不同,CMS采用了标记-清除(Mark-Sweep)算法,并且在标记阶段与应用程序并发执行,以尽量减少垃圾回收对应用程序的影响。

CMS回收过程包括以下几个阶段:

  1. 初始标记阶段:
    • CMS开始时,会暂停所有的应用线程(Stop-the-World),以进行初始标记阶段。
    • 在初始标记阶段,只标记直接与根对象有关的对象,并标记老年代中的存活对象。
  1. 并发标记阶段:
    • 在初始标记阶段完成后,CMS会与应用程序并发执行,继续标记其他存活对象,即从根对象开始进行可达性分析,标记出所有存活对象。
    • 应用程序可以继续执行,与垃圾回收线程并发执行。
  1. 重新标记阶段:
    • 在并发标记阶段中,由于应用程序继续执行,对象可能会发生变化,新增的对象可能与老年代中的存活对象发生引用关系。
    • 为了保证准确性,CMS需要在应用程序暂停时进行重新标记,这个阶段的暂停时间较短。
  1. 并发清除阶段:
    • 在重新标记阶段完成后,继续与应用程序并发执行,执行标记-清除操作,清除所有未标记的对象。
    • 清理后,老年代将被整理,以便更好地利用连续的内存空间。
  1. 并发重置阶段:
    • 在清除阶段完成后,最后一次与应用程序并发执行,完成垃圾回收过程。

CMS的问题包括:

  1. 并发标记和应用程序并发执行冲突:
    • 在并发标记阶段和并发清除阶段,CMS与应用程序并发执行。这样做可以减少垃圾回收对应用程序的影响,但也会导致垃圾回收线程与应用程序竞争CPU资源,可能影响应用程序的吞吐量。
  1. 空间碎片问题:
    • CMS使用标记-清除算法,不进行内存整理。清除阶段只是简单地回收未标记的对象,不会进行内存的整理操作。这可能导致老年代出现大量的空间碎片,从而影响垃圾回收效率,甚至可能触发Full GC。
  1. 并发模式失败:
    • 如果在并发标记或并发清除阶段,老年代的对象发生了太多的变化,导致标记-清除操作的对象数量过多,CMS会放弃并发执行,切换到串行模式执行。这样就会导致较长的停顿时间,影响应用程序的响应性能。
  1. 吞吐量降低:
    • 由于CMS需要与应用程序并发执行,其回收效率相对于一些其他回收器(如G1)可能会稍低,从而导致应用程序的吞吐量降低。

虽然CMS在减少垃圾回收停顿时间方面表现出色,但由于上述问题,它不适合在所有场景下使用。对于大内存、响应性要求高的应用程序,可以考虑使用其他垃圾回收器,如G1。

详细说一下G1的回收过程?G1的问题是什么

G1(Garbage-First)是一种分代垃圾回收器,主要用于回收Java堆中的老年代,也可以用于新生代的回收。G1回收过程相对于传统的垃圾回收器具有一定的复杂性和特殊性。下面详细描述G1回收过程:

\1. 初始标记阶段(Initial Marking):

  • G1开始时,会暂停所有的应用线程(Stop-the-World),以进行初始标记阶段。
  • 在初始标记阶段,只标记直接与根对象有关的对象,并标记老年代中的存活对象。
  • 这个阶段的停顿时间较短,只会标记出与根对象直接可达的对象。

\2. 并发标记阶段(Concurrent Marking):

  • 在初始标记阶段完成后,G1会与应用程序并发执行,并继续进行标记操作,标记出所有存活对象。
  • 这个阶段的标记操作是基于分代的增量并发标记,因此它可以和应用程序一起并发执行,减少了停顿时间。

\3. 最终标记阶段(Final Marking):

  • 并发标记阶段可能由于应用程序的运行而导致对象发生变化,新增的对象可能与老年代中的存活对象产生引用关系。
  • 为了保证准确性,G1需要在应用程序暂停时进行最终标记,重新标记那些在并发标记阶段发生变化的对象。

\4. 筛选回收阶段(Live Data Counting and Evacuation):

  • 在最终标记阶段完成后,G1会进行回收计划,根据存活对象的信息和GC目标,确定回收哪些区域的对象。
  • G1将堆空间划分为多个区域(Region),每个区域可能是Eden、Survivor或Old区域。
  • 根据回收计划,G1会选择一部分区域进行回收,并执行对象的迁移,将存活对象从一个区域复制到其他区域。
  • G1的回收过程使用复制算法来回收新生代区域,使用标记-压缩算法来回收老年代区域。

\5. 清理阶段(Cleanup):

  • 在筛选回收阶段完成后,G1会进行清理操作,清理那些未标记的对象,并释放它们所占用的内存空间。

\6. 转发阶段(Forwarding):

  • 在清理阶段完成后,G1还需要处理一些引用关系,这个阶段称为转发阶段。
  • G1会更新指向被复制对象的引用,使其指向复制后的对象。

\7. 完成阶段(Concurrent Cleanup):

  • 最后一个阶段是完成阶段,在此阶段,G1会进行一些收尾工作,完成垃圾回收的整个过程。
  • 完成阶段与应用程序并发执行,从而减少垃圾回收对应用程序的影响。

G1的问题包括:

  1. 占用额外的内存空间:
    • 为了实现分代回收,G1需要维护各个区域的信息,这就会占用一定的内存空间。
  1. 垃圾回收时间不稳定:
    • G1的回收过程是增量的,具体回收时间难以预测。虽然平均来说停顿时间相对较短,但在某些情况下可能会有较长的停顿时间,导致应用程序的响应性降低。
  1. 回收效率可能较低:
    • G1回收过程涉及多次回收阶段,可能会导致回收效率较传统的回收器(如CMS)略低。
  1. 转发阶段可能引起的短暂停顿:
    • 在转发阶段,G1需要更新对象的引用,这可能导致短暂的停顿。

虽然G1在大内存环境和对响应性能要求较高的场景中表现出色,但由于上述问题,它不适合在所有场景下使用。选择合适的垃圾回收器应该根据应用程序的特点和性能需求来决定。

JVM中一次完整的GC是什么样子的?

一次完整的垃圾回收(GC)指的是Java虚拟机执行一次包括新生代和老年代的垃圾回收的过程。在Java虚拟机中,进行一次完整的GC可以分为以下几个步骤:

  1. 新生代GC(Young Generation GC):
    • 首先,执行一次新生代GC。新生代是年轻的对象的存放区域,通常使用复制算法进行回收。
    • 在新生代GC过程中,将新生代分为Eden区和两个Survivor区(通常是一个From区和一个To区)。
    • 首先,将Eden区和From区中的存活对象复制到To区,同时清空Eden区和From区。
    • 接着,将Eden区和To区交换角色,使得To区成为下一次GC的From区。
    • 这样,新生代GC完成后,存活的对象会被复制到To区,并且年龄为一的对象会被晋升到老年代。
  1. 老年代GC(Old Generation GC):
    • 当老年代中的对象占用的空间达到一定阈值时,执行一次老年代GC。老年代是存放生命周期较长的对象的区域。
    • 老年代GC的算法通常使用标记-压缩或标记-清除算法。
    • 首先,执行初始标记阶段,暂停所有应用线程,标记直接与根对象有关的对象。
    • 然后,在并发标记阶段,并发地继续标记所有存活对象。
    • 接着,在重新标记阶段,暂停应用线程,重新标记在并发标记阶段可能发生变化的对象。
    • 最后,在清理阶段,清理未标记的对象,并进行内存压缩或清除。
  1. 补充:元空间GC(Metaspace GC):
    • 如果使用的是G1垃圾回收器或者垃圾回收器支持元空间的自动内存管理,则可能会执行元空间的GC。
    • 元空间是用于存放类元数据和常量池的区域,其大小是动态的,并且不在Java堆中,而是位于本地内存(Native Memory)。
    • 元空间GC主要用于回收未使用的类元数据和常量池,以释放不再使用的类相关的内存。

一次完整的垃圾回收过程可能包括以上步骤,具体的回收过程和算法取决于所使用的垃圾回收器以及虚拟机的配置。不同的垃圾回收器有不同的优势和特点,选择合适的垃圾回收器需要根据应用程序的特点和性能需求来决定。

Minor GC 和 Full GC 有什么不同呢?

Minor GC(Young Generation GC)和Full GC(Old Generation GC)是Java虚拟机中两种不同类型的垃圾回收过程,它们主要针对不同代(Generation)的内存区域进行回收。

\1. Minor GC(Young Generation GC):

  • Minor GC主要针对新生代进行垃圾回收。新生代是存放年轻对象的区域,通常使用复制算法进行回收。
  • 在Minor GC过程中,Eden区和Survivor区(通常是一个From区和一个To区)中的存活对象会被复制到另一个Survivor区,同时清空Eden区和From区。
  • Minor GC的目标是尽快回收那些生命周期较短的对象,只涉及到新生代的一部分区域。

\2. Full GC(Old Generation GC):

  • Full GC主要针对老年代进行垃圾回收。老年代是存放生命周期较长的对象的区域,通常使用标记-清除或标记-压缩算法进行回收。
  • Full GC涉及到整个堆(包括新生代和老年代),它的目标是尽量回收更多的内存空间,并进行内存整理,以避免内存碎片化。

区别:

  • Minor GC只回收新生代的内存区域,发生频率通常较高,但回收的对象量相对较少,停顿时间短。
  • Full GC回收整个堆的内存区域,发生频率较低,但回收的对象量相对较多,停顿时间较长。
  • Minor GC通常是并发执行的,即与应用程序线程同时执行,因此对应用程序的影响较小。
  • Full GC通常需要暂停所有应用程序线程(Stop-the-World),即停止所有应用程序线程的执行,直到垃圾回收完成。这样的停顿时间较长,可能会影响应用程序的响应性能。
  • Minor GC和Full GC的频率和触发时机取决于具体的垃圾回收器以及虚拟机的配置和运行状态。通常情况下,Full GC发生时可能伴随着很长的停顿,因此尽量减少Full GC的频率对于应用程序的性能是有益的。

介绍下空间分配原则

空间分配原则是指在Java虚拟机中,为对象分配内存空间时遵循的一些规则和策略,旨在提高内存的利用效率和垃圾回收的效率。以下是空间分配的一些原则:

  1. 对象优先在Eden区分配:
    • 大多数情况下,对象在新生代的Eden区分配。Eden区的空间相对较大,并且对象的生命周期较短,因此适合存放新创建的对象。
  1. 大对象直接进入老年代:
    • 如果一个对象的大小超过了新生代Eden区的剩余空间大小,或者为数组对象且大小超过了预设阈值,那么该对象会直接被分配到老年代中。
  1. 长期存活的对象进入老年代:
    • 对象的存活时间是衡量对象是否存放在新生代还是老年代的重要依据。经过一次Minor GC后仍然存活的对象会被晋升到老年代。
  1. 动态对象年龄判定:
    • 在新生代发生Minor GC时,如果Survivor区中相同年龄所有对象大小的总和大于Survivor区的一半,那么年龄大于等于该年龄的对象会直接进入老年代。
  1. 空间分配担保:
    • 在发生Minor GC前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果条件成立,Minor GC可以确保是安全的。
  1. 逃逸分析:
    • 逃逸分析是JVM的优化技术之一,用于分析对象的作用域是否局限在方法内部,如果是,那么对象可能不会被分配在堆上,而是分配在栈上或者进行标量替换优化。
  1. 栈上分配:
    • 在JVM的某些情况下,可以将对象分配在栈上而不是堆上,从而避免垃圾回收的开销。

这些空间分配原则有助于优化内存的使用,减少垃圾回收的频率和停顿时间,从而提高Java应用程序的性能和效率。虽然空间分配原则在JVM中起着重要作用,但JVM实现的具体策略和优化技术可能因不同的虚拟机版本和垃圾回收器而异。

什么是类加载?类加载的过程?

类加载是Java虚拟机(JVM)将类的字节码加载到内存中,并转换成JVM能够理解的数据结构,并生成相应的Java类对象的过程。类加载是Java程序运行的重要环节,它负责在程序运行时动态加载需要的类文件,使得Java程序能够动态地加载、链接和初始化类。

img

类加载的过程可以分为以下几个步骤:

  1. 加载(Loading):
    • 查找并加载类的字节码文件。类的字节码可以来自文件、网络、数据库等。加载后的字节码被存放在JVM的方法区中。
  1. 验证(Verification):
    • 确保加载的字节码符合JVM规范,不会损害JVM的安全性和稳定性。验证包括文件格式验证、元数据验证、字节码验证和符号引用验证。
  1. 准备(Preparation):
    • 为类的静态变量分配内存,并设置默认初始值(零值)。这些变量被存放在JVM的方法区中。
  1. 解析(Resolution):
    • 将类、接口、方法和字段的符号引用解析为直接引用。直接引用是指向方法区中具体位置的指针,可以是指向方法、字段等的引用。
  1. 初始化(Initialization):
    • 对类进行初始化,执行类构造器()方法,初始化类的静态变量和静态代码块。如果一个类含有父类,会优先初始化父类。
  1. 使用(Using):
    • 当类加载完成后,该类就可以被使用了。可以创建类的实例对象,调用类的方法等。
  1. 卸载(Unloading):
    • 如果一个类加载后不再被引用,JVM会将该类卸载,释放内存空间。

类加载是Java虚拟机的重要功能,它使得Java具备了动态加载类和实现动态链接的能力。类加载机制还支持Java的热部署和动态扩展等特性,让Java应用程序能够更加灵活和高效地运行。

什么是类加载器,常见的类加载器有哪些?

类加载器(Class Loader)是Java虚拟机(JVM)的一个子系统,负责将类的字节码加载到内存中,并生成对应的Java类对象。类加载器在Java程序运行时动态地加载需要的类文件,使得Java具备了动态加载类和实现动态链接的能力。

常见的类加载器有以下几种:

  1. 启动类加载器(Bootstrap Class Loader):
    • 是JVM的内置类加载器,负责加载Java的核心类库,如java.lang包中的类。由C++编写,不是Java类,是JVM的一部分。
  1. 扩展类加载器(Extension Class Loader):
    • 负责加载Java的扩展类库,位于JRE的lib/ext目录下的JAR文件中的类。它的父类加载器是启动类加载器。
  1. 应用程序类加载器(Application Class Loader):
    • 也称为系统类加载器,负责加载用户自定义的类,通常是位于classpath(类路径)下的类。它的父类加载器是扩展类加载器。
  1. 自定义类加载器(Custom Class Loader):
    • 开发者可以通过继承java.lang.ClassLoader类实现自定义的类加载器。自定义类加载器可以根据特定需求来加载类,比如从网络或数据库中加载类。

Java的类加载器采用了委托模型(Delegation Model),即类加载器在加载类时会首先委托给其父类加载器尝试加载,只有当父类加载器无法加载时,才由当前类加载器自己加载。这样的层级关系可以保证类的加载安全性,避免类的重复加载,同时也方便了类加载器的扩展和定制。

什么是双亲委派模型?为什么需要双亲委派模型?

双亲委派模型是Java类加载器的一种工作机制,用于保证类的加载安全性和避免类的重复加载。它是Java类加载器采用的一种层级结构,在类加载的过程中,类加载器首先将加载请求委派给它的父类加载器尝试加载,只有在父类加载器无法加载时,才由当前类加载器自己来加载。

工作原理如下:

  1. 当一个类加载器收到加载请求时,首先不会自己尝试加载该类,而是将加载请求委派给父类加载器。
  2. 父类加载器也会按照同样的规则继续向上委派加载请求,直至最顶层的启动类加载器(Bootstrap Class Loader)。
  3. 如果最顶层的启动类加载器无法加载类,会将加载请求逐级向下传递给下一级的扩展类加载器(Extension Class Loader)。
  4. 扩展类加载器仍然无法加载时,将加载请求传递给下一级的应用程序类加载器(Application Class Loader)。
  5. 应用程序类加载器如果仍然无法加载,则自己尝试加载类。

这样的层级结构形成了一种父子关系的类加载器链条,称为双亲委派模型。它的特点是从上往下加载类,即从启动类加载器开始,依次向下委派加载,直到某个类加载器加载成功为止。

为什么需要双亲委派模型?

  1. 安全性:双亲委派模型可以确保Java核心类库的安全性。Java核心类库由启动类加载器加载,这些类具有Java运行时环境的基础功能,保证了Java程序的稳定性和安全性。
  2. 避免类的重复加载:由于每个类加载器都有自己的命名空间,父加载器加载过的类在子加载器中不会再次加载,避免了类的重复加载。
  3. 灵活性和可扩展性:双亲委派模型允许开发者根据需要自定义类加载器,并且可以通过继承ClassLoader类来实现自己的加载逻辑。开发者可以在不影响Java核心类库的情况下,实现自己的类加载策略。

总的来说,双亲委派模型确保了类的加载安全和顺序,同时为Java类加载器的灵活扩展提供了支持。这是Java虚拟机运行Java应用程序的基本保障。

Java对象创建过程

  1. 类加载:
    • 当使用Java类创建对象时,首先需要确保类已经被加载到JVM中。类加载器负责将类的字节码加载到内存中,并生成对应的Class对象。
  1. 内存分配:
    • 在类加载后,JVM会为该类的对象分配内存空间。对象的内存分配通常发生在堆(Heap)中,堆是Java虚拟机中用于存放对象实例的内存区域。
  1. 初始化零值:
    • 在内存分配后,JVM会对对象的实例变量进行初始化,这些实例变量在Java类中定义,初始值为Java的零值,如int类型为0,引用类型为null,布尔类型为false等。
  1. 执行构造方法:
    • 在对象的内存空间分配并完成初始化零值后,JVM会调用对象的构造方法。构造方法是Java类中的特殊方法,用于初始化对象的实例变量和执行其他必要的初始化工作。
  1. 对象引用:
    • 在对象创建过程中,可能会涉及到对象之间的引用。如果对象A中包含一个对对象B的引用,那么在创建对象A时,需要先创建对象B,然后再将对象B的引用赋值给对象A。
  1. 对象初始化:
    • 对象创建完成后,会执行其它初始化代码块和实例方法。这些代码块和方法在类中定义,可以执行一些进一步的初始化工作,使对象处于合适的状态。

Java对象创建过程在实际运行中可能会受到一些优化,例如逃逸分析、栈上分配等,以提高对象的创建效率和内存使用效率。但以上列举的步骤是对象创建的基本流程,确保对象在内存中正确创建并初始化。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值