最全JVM面试知识整理

JVM面试知识整理

一、Java内存模型

  1. Java内存模型
  • 线程私有的:程序计数器、虚拟机栈、本地方法栈
  • 线程共享的:堆、方法区、直接内存(非运行时数据区)

JDK1.8前:

在这里插入图片描述

JDK1.8后:

在这里插入图片描述

  1. 程序计数器
  • 程序计数器是Java运行时数据区中唯一不可能发生内存溢出异常的内存空间。程序计数器主要有两方面的功能:虚拟机字节码解释器能够通过改变程序计数器实现流程控制的功能,如顺序执行、选择、循环、异常处理等。程序计数器还能够保存当前线程的执行位置,当线程被切换回来的时候就能够知道上次执行到哪了
  • 如果JVM执行的是java方法,那么程序计数器保存的是下一行指令的位置,如果JVM执行的是native方法,那么程序计数器保存的是Undefined
  • 程序计数器私有化的目的是为了在线程被切换回来的时候能够继续从正确开始执行
  1. 虚拟机栈
  • 在一个方法被执行的同时会有一个栈帧被创建用来保存局部变量表、操作数栈、动态链接以及方法返回信息等。方法从调用到执行结束的过程,就是一个栈帧从入栈到出栈的过程
  • 局部变量表主要用来存放在编译时就可以确定的各种数据类型,对象引用以及方法返回地址等信息
  • Java虚拟机栈可能会出现两种异常:StackOverFlowError或OutOfMemoryError
    • StackOverFlowError:如果虚拟机栈内存大小不支持动态扩展,那么当线程请求栈深度大于当前虚拟机栈允许的最大深度的时候,就会报StackOverFlowError异常
    • OutOfMemoryError:如果虚拟机栈内存大小支持动态扩展,那么当线程请求栈时内存用完了,此时就无法再进行动态扩展了,就会报OutOfMemoryError异常
  • 每一次方法调用都有一个栈帧入栈,每一次方法调用结束,都有一个栈帧出栈,Java方法结束有两种方式,执行return语句或者抛出异常,这两种结束方式都会使栈帧出栈
  1. 本地方法栈

本地方法栈和虚拟机栈十分类似,区别在于虚拟机栈为JVM执行Java方法服务,而本地方法栈为虚拟机执行native方法服务

  • 堆占用虚拟机内存中最大的一块内存空间,堆和方法区被所有的线程所共有,堆随着虚拟机的启动而创建。堆主要用来存放新创建的对象,几乎所有的对象和数组都保存在堆中
  • 堆是垃圾回收机制的主要管理区域,因此堆又被称为GC堆,现在的垃圾回收机制主要都是使用分代回收算法,因此堆又被分为新生代和老年代,新生代又可以划分为Eden区、From Survivor区、To Survivor区,采用这种实现方式是为了更好地进行回收内存和分配内存
  • 在Java中,堆被分为新生代和老年代,新生代和老年代地内存大小比例为1:2,新生代又被分为Eden区、From Survivor区和To Survivor区,这三个区域的大小比例为8:1:1,采用实现方式是因为经过统计测算,发现当内存占用98%的时候就需要进行内存清理了,而我们不可能只预留2%的内存空间,在只剩2%内存空间的时候再进行内存清理就已经来不及了,因此我们采取的策略是预留10%的内存空间,同时在进行垃圾回收的时候,我们还需要预留一块内存空间用来保存依旧存活的对象,因此又分配10%的内存空间,因此三个区域的比例是8:1:1。同时两个Survivor区域是交替使用的,任意时刻总有一个Survivor区是空闲的,用来保存依旧存活的对象,因此新生代的使用率永远只有9/10。一般来说,新创建的对象都会在Eden区中分配内存空间,在进行一次垃圾回收之后,如果该对象依旧存活,并且能够保存在Survivor区中,那么该对象会进入其中一个空闲的Survivor区,然后该对象的年龄加1,JVM会对之前使用过的内存进行清理。之后没经历过一次垃圾回收,该对象的年龄就会加1,当它的年龄达到默认阈值15的时候,该对象就会进入老年代。可以通过参数Max’T’e’nuringThreshold参数来设置对象进入老年代的年龄阈值。当然,也不一定全是这样,如果一个对象需要占用一整块连续的存储空间,那么该对象就会直接进入老年代
  1. 方法区
  • 堆和方法区被所有的线程所共有,方法区主要用来保存已加载的类信息、常量、静态成员变量、即时编译器编译后的代码等信息
  • 在HotSpot虚拟机中方法区又被称为永久代,永久代是HotSpot虚拟机对方法区的实现。在JDK1.8之前,可以通过参数-XX:PermSize-XX:MaxPermSize设置永久代的初始大小和最大大小。在JDK1.8中,永久代被移除,取而代之的是元空间,可以通过参数-XX:MetaspaceSize-XX:MaxMetaspaceSize设置元空间的初始大小和最大大小
  1. 为什么要将永久代替换为元空间
  • 因为永久代保存在虚拟机内存中,其大小受限于虚拟机内存的大小,容易出现内存溢出异常,而元空间保存在直接内存中,其大小受限于直接内存的大小,不容易出现内存溢出异常。可以通过参数-XX:MetaspaceSize来设置元空间的最大大小,如果不设置的话,它的默认值是unlimited,表示只受限于系统可用内存的大小,可以通过参数-XX:MetaspaceSize设置元空间的初始大小,如果不设置的话,那么Metaspace会根据应用程序的需求动态地调整
  • 其实当初Oracle有两个虚拟机,分别是HotSpot和JRocket,Oracle打算在HotSpot的基础上将HotSpot和JRocket结合在一起,而JRocket是不存在永久代这个概念的,其实也不需要永久代这个概念。在JDK1.6开始Permgen开发团队就准备将永久代放在直接内存中,到JDK1.8才将永久代这个概念移除
  1. 运行时常量池
  • Java中常量池有两种状态,分别是静态常量池和运行时常量池。静态常量池就是Class文件中的常量池,它包含了字符串字面量、类、方法等信息,运行时常量池指的是在JVM完成类加载后,将静态常量池保存到虚拟机内存的方法区中,这就是运行时常量池
  • 在JDK1.7中,Java已经将运行时常量池从方法区中移除,在堆中分配了一块内存空间用来保存运行时常量池

在这里插入图片描述

  1. 直接内存
  • 在Java虚拟机规范中Java内存模型是不包括直接内存的,也就是说直接内存不属于Java运行时数据区,但是我们仍然频繁使用直接内存,原因在于NIO。NIO全称是New Input/Output,它是支持基于缓冲流的,基于通道的IO方式,它可以直接调用Native函数库在堆外分配直接内存,然后可以通过堆上的DirectByteBuffer对象对直接内存进行引用和操作。使用这种实现方式能够避免在Java堆和Native堆之间来回复制数据
  • 直接内存的内存大小不受限于Java堆的大小,也不受限于虚拟机内存的大小,只受限于系统可用内存以及处理器寻址空间的大小

二、对象的创建

  1. 说一说Java对象的创建过程

在这里插入图片描述

  • 类加载检查:当虚拟机遇到一条new指令时,首先会检查这个指令的参数是否能够在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已经被加载、连接和初始化过,如果没有,仅需要进行相应的类加载过程
  • 分配内存:在类加载检查通过后,虚拟机会为新创建的对象分配内存空间,分配内存空间的大小在类加载完成后就已经确定了,分配内存空间其实就是在堆中划分一块内存,用来存放新创建的对象。分配内存的方式有两种,分别是指针碰撞和空闲列表,使用哪种内存分配方式取决于Java堆是否规整,而Java堆是否规整取决于所使用垃圾回收算法是标记-清除算法还是标记-整理算法,或者是复制算法

在这里插入图片描述

内存分配的并发问题:Java采用两种方法来解决内存分配的并发问题:

  • CAS+失败重试:CAS是乐观锁的一种实现方式,乐观锁是指进行操作的时候都不加锁,并且假设不会发生冲突,当因冲突而失败的时候就重试,知道成功为止。虚拟机采用CAS加失败重试的方式保证更新操作的原子性

  • TLAB:虚拟机为每一个线程在Eden区中分配一块TLAB内存空间,每次为线程中的对象分配内存的时候,都在TLAB中分配内存空间。如果对象大于TLAB或者TLAB剩余空间为零的时候,就使用CAS+失败重试的方式分配内存空间

  • 初始化零值:在分配了内存空间之后,虚拟机会对分配到的内存空间初始化为零值(不包括对象头),采用这种实现方式后对象的实例字段不需要赋初值就可以直接使用,Java程序能够获得这些字段的数据类型对应的零值

  • 设置对象头:初始化零值后,虚拟机还需要对对象进行一些必要的设置,如这个对象属于哪个类,类的元数据信息在哪里找到,对象的哈希码,对象的GC分代年龄等信息,这些信息都存放在对象的对象头中。此外,根据虚拟机当前状态的不同,比如是否使用偏向锁等,对象头的设置也会不同

  • 执行init()方法:执行完上面的步骤后,从JVM的层面来看,对象已经创建完成,但是从程序的角度来看,对象还没有创建完成,所有字段都还是零值,还没有执行init()方法,因此,一般执行完new指令后会继续执行init()方法,将对象按照程序员的意愿初始化,这样一个对象才算创建成功

  1. 说一说对象的访问定位方式

Java程序通过栈上的reference来访问堆上的具体对象,对象的访问方式由虚拟机的实现方式决定,目前主要的访问方式有:

  • 使用句柄:如果使用句柄的话,Java堆会分配一块内存空间用来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中存储的是对象的实例数据和类型数据的地址信息

在这里插入图片描述

  • 直接指针:如果使用直接指针的话,那么堆中对象的实例数据区就必须考虑如何放置访问对象类型数据的相关信息,而reference中存储的就是对象的地址

在这里插入图片描述

使用这两种访问方式各有优势,使用句柄的好处是reference中保存的就是稳定的句柄地址,在对象被移动的时候只会改变句柄中的实例数据地址,而reference本身不需要修改。使用直接指针访问方式的好处是访问速度快,因为它节省了一次指针定位的开销

  1. 说一说堆内存中对象分配的基本策略

见堆

  1. Minor GC和Full GC有什么区别
  • 新生代GC(Minor GC):Minor GC就是指新生代垃圾回收,Minor GC进行地非常频繁,回收速度也很快
  • 老年代GC(Major GC):Major GC就是指老年代垃圾回收,一次Major GC至少伴随着一次Minor GC,但也并非绝对是这样,一般情况下,Major GC要比Minor GC慢十倍以上
  1. 如何判断对象已经死亡

在进行垃圾回收之前,必须判断对象是否已经死亡,Java中判断对象是否死亡有两种方式:

  • 引用计数法:给对象添加一个引用计数器,每当有一个地方引用这个对象,计数器就加1,每当引用失效的时候,计数器就减1,在任意时刻当引用计数器为0的时候就说明这个对象不能再被使用了,即这个对象死亡
  • 可达性分析:使用一系列被称为GC Roots的对象作为起点,从这些节点开始向下搜索,搜索走过的路径被称为引用链,当一个对象到GC Roots没有任何引用链相连的话,就说明这个对象是不可达的,即这个对象死亡
  1. 说一说强引用、软引用、弱引用、虚引用

在JDK1.2之前,Java对引用的定义是:如果reference存储的数据是另一块内存的起始地址,那么就称这块内存是一个引用。在JDK1.2中,Java对引用的概念进行了扩充并且将引用分为了四类:强引用、软引用、弱引用、虚引用

  • 强引用(StrongReference):现在我们使用的引用一般都是强引用,具有强引用的对象不会被系统垃圾回收机制回收。当内存空间不足的时候,Java虚拟机就算抛出OutOfMemoryError异常,也不会回收具有强引用的对象所占用的内存空间来解决内存不足的问题
  • 软引用(SoftReference):如果一个对象只具有软引用,那么在系统内存空间充足的时候,系统垃圾回收机制不会回收在对象占用的内存空间,当内存空间不足的时候,系统垃圾回收机制就会回收该对象占用的内存空间。如果对象没有被垃圾回收,那么程序依旧可以使用该对象,软引用可以用于对内存敏感的告诉缓存
    软引用可以和引用队列(ReferenceQueue)联合使用,当软引用引用的对象被回收后,系统可以将该软引用加入到对应的引用队列中
  • 弱引用(WeakReference):弱引用和软引用类型,他们的区别在于,当垃圾回收线程扫描其所管理的区域时,如果发现一个对象只具有弱引用,那么就会立即回收该对象,不管当前内存空间是否充足。但是因为垃圾回收线程的优先级都很低,垃圾回收线程不一定在第一时间就能够发现只具有弱引用的对象
    弱引用也可以与引用队列联合使用,当弱引用引用的对象被回收后,系统可以将该弱引用加入到对应的引用队列中
  • 虚引用(PhantomReference):如果一个对象只具有虚引用,那么和没有引用没有什么区别,这个对象随时可能被回收掉。虚引用主要用于跟踪对象被垃圾回收的状态。
    虚引用与弱引用和软引用不同的的是,虚引用必须和引用队列联合使用,当垃圾回收机制准备回收一个对象占用的内存空间之前,如果发现这个对象有虚引用,就会将虚引用加入到对应的引用队列中,程序可以通过查看虚引用是否加入到引用队列中,了解到对象是否将要被垃圾回收。如果程序发现虚引用已经被加入到引用队列中,就可以在对象被回收之前采取必要的行动

在程序中一般很少使用弱引用和虚引用,使用软引用比较多,因为使用软引用可以加快JVM对垃圾内存的回收速度,维护系统安全,防止内存溢出等问题的产生

  1. 如何判断一个常量是废弃常量

运行时常量池中回收的主要是废弃的常量,如果一个常量没有任何引用指向它,那么这个常量就是废弃常量。在常量池中存在字符串常量“ABC”,如果没有任何String类型的引用指向它,那么这个字符串常量就是废弃常量,如果发生垃圾回收的话,这个废弃常量就会被回收掉

  1. 如何判断一个类是无用的类

方法区中回收的主要是无用的类,判断一个类是无用的类需要满足三个条件:

  • 该类的所有实例都已经被回收掉
  • 该类的类加载器已经被回收掉
  • 该类对应的java.lang.Class对象没有在任何地方被引用,在任何地方都不能够通过反射机制调用该类的方法

无用的类只是可以被回收,并不会像对象那样只要无用就一定会被回收

三、垃圾回收

  1. 垃圾回收算法有哪些,特点是什么
  • 标记-清除算法:分为两个阶段,分别是标记和清除,先对所有需要进行垃圾回收的对象进行标记,然后再对所有标记的对象进行清除。标记-清除算法是最基础的算法,后续的算法都是对它改进得到的,标记-清除算法存在两个问题:
    • 效率问题
    • 空间问题:标记-清除算法会产生大量的空间碎片
  • 复制算法:为了解决标记清除算法产生大量空间碎片的问题,Java引入了复制算法,复制算法将堆内存分为大小相等的两个内存块,每次只使用其中一个内存块,每当进行垃圾回收的时候,将依旧存活的对象复制到另一块内存中,然后对之前使用到的内存进行清理
  • 标记-整理算法:标记整理算法也分为两个阶段进行,先对所有需要进行垃圾回收的对象进行标记,然后将依旧存活的对象移动到一端,最后对端边界以外的内存进行清理
  • 分代回收算法:将堆分为新生代和老年代,根据代的特点选择使用不同的垃圾回收算法。比如,新生代可以使用复制算法,因为新生代中每次进行垃圾回收都有大量的对象死亡,使用复制算法,只需要复制少量的对象就能进行一次垃圾回收。而老年代因为老年代中对象的存活率比较高,并且没有额外的空间担保分配,因此适用于标记-清除算法或者标记-整理算法
  1. HotSpot虚拟机为什么要将堆分为新生代和老年代

HotSpot虚拟机将堆分为新生代和老年代是为了更好地进行内存分配和垃圾回收

  1. 常见的垃圾回收器有哪些
  • Serial收集器:Serial收集器是最基本、历史最悠久的收集器,它是一个单线程收集器,也就是说Serial收集器使用单个收集器线程来完成垃圾回收的工作,同时在进行垃圾回收的时候,会暂停所有其他正在工作的线程,这也被称为“Stop The World”
    Serial收集器相比于其他收集器的单线程的优势是高效简单,因为Serial收集器没有多线程交互的开销,因此它的单线程回收效率很高。对于工作在client模式下的虚拟机来说是一个不错的选择
  • ParNew收集器:ParNew收集器其实就是Serial收集器的多线程版本,除了使用多线程进行垃圾回收之外,它的其他行为和Serial收集器完全一样。
    ParNew收集器是工作在Server模式下的虚拟机的首要选择,只有Serial和ParNew收集器能够CMS收集器配合使用
  • Parallel Scavenge收集器:Parallel Scavenge收集器主要关注吞吐量,因此它适用于注重吞吐量或CPU资源的场合。Parallel Scavenge提供了一些参数供用户选择合适的等待时间以及最大的吞吐量,可以使用参数UseParallelGCUseParallelOldGC来设置使用Parallel收集器和老年代串行或并行
  • Serial Old收集器:Serial Old收集器是Serial收集器的老年代版本,它主要有两个功能:在JDK1.5之前与Parallel Scavenge收集器联合使用、作为CMS收集器的后备方案
  • Parallel Old收集器:Parallel Scavenge收集器的老年代版本,在注重吞吐量或CPU资源的场合,可以使用Parallel Scavenge和Parallel Old收集器
  • CMS(Concurrent Mark Sweep)收集器:CMS收集器是一种以用户等待时间最小为目标的收集器,因此它适用于注重用户体验的场合。
    CMS收集器是HotSpot虚拟机第一款真正意义上的并行收集器,它能够实现垃圾回收线程和用户线程同时运行。CMS是一种标记-清除算法,它的垃圾回收过程分为四个步骤:
    • 初始标记:暂停其他的所有线程,记录与GC Root直接相连的所有对象
    • 并发标记:启动所有的GC线程和用户线程,同时启动一个闭环结构用来记录所有的可达对象,但是这个步骤结束后闭环结构不一定能够记录下所有的可达对象,因为用户线程会不断更新引用域,GC线程无法保证可达性分析的实时性。因此该算法还会记录所有发生引用域更新的地方
    • 重新标记:重新标记的作用是修正在并发标记中因为用户线程不断更新引用域导致对象标记发生改变的记录,这一步花费的时间比初始标记长,但是远小于并发标记
    • 并发清除:启动所有的用户线程,同时GC线程会清理所有未标记的区域

CMS收集器的优点是:可以并发收集,用户等待时间短,缺点是:会产生大量的空间碎片、无法清除浮动数据、对CPU资源敏感

  • G1(Garbage-First)收集器: G1收集器是一款面向服务器的收集器,它主要针对拥有多个CPU和大量内存的计算机。它在保证用户等待时间的同时,还能够具有较高的吞吐量
  • G1收集器具有一下特点:
    • 并行与并发:G1收集器能利用多个CPU来缩短“Stop The World”的等待时间,它能够让以并发的方式让原本应该等待的Java程序继续执行
    • 分代回收:虽然G1收集器可以单独地实现对堆内存的管理,但是它依然保留了分代的概念
    • 标记整理:G1整体上来看是基于标记-整理算法来实现的,从局部上来看是基于复制算法来实现的
    • 可预测的停顿:G1和CMS收集器都能够缩短用户线程的等待时间,但是与CMS不同的是,G1收集器还能够建立可预测的停顿时间模型
  • G1收集器的回收过程可以分为四个步骤:初始标记、并发标记、最终标记、筛选回收
  • G1收集器维护了一个优先列表,可以在允许的停顿时间内,选择最具有回收价值的Region,使用这种实现方式使G1收集器在有限时间内都能够保持较高的回收率

四、类加载过程

  1. 说一说类加载过程,类的生命周期
  • 类的生命周期包括了:加载、验证、准备、解析、初始化、使用、卸载,一共7个阶段,类加载过程包括了加载、连接和初始化,其中连接包括验证、准备和解析
  • 其中加载、验证、准备、初始化、卸载这五个阶段的顺序是确定的,类的加载过程必须按照这个顺序开始,而解析阶段在某些情况下可以在初始化阶段之后才开始,这是为了支持Java语言的运行时绑定
  1. 类初始化时机

Java虚拟机规范规定了有且只有六种情况必须立即对类进行初始化:

  • 遇到new、getstatic、putstatic、invokestatic这四条字节码指令时,如果类型没有进行初始化,就需要先进行初始化操作。能够生成这四条指令的场景有:
    • 使用new关键字创建实例
    • 读取或设置一个类型的静态字段
    • 调用一个类型的静态方法的时候
  • 使用java.lang.reflect包的方法对类型进行反射调用的时候,如果类型没有进行初始化,就需要先进行初始化操作
  • 当初始化类的时候,如果发现其父类还没有进行初始化,就需要先对其父类进行初始化操作
  • 当虚拟机启动的时候,用户需要指定一个要执行的主类,虚拟机会先初始化这个主类
  • 当使用JDK1.7新增的动态语言的时候,如果方法句柄对应的类没有初始化,就需要先进行初始化操作
  • 当接口中定义了JDK1.8新增的默认方法,如果这个接口的实现类发生了初始化,那么该接口要在其之前被初始化

上面这六种行为被称为对类的主动引用,除此之外,所有引用类型的方式都不会触发初始化,被称为被动引用,被动引用包括:

  • 通过子类引用父类的静态字段,不会导致子类初始化
  • 通过数组定义引用类,不会触发此类的初始化,该过程会对数组类进行初始化,数组类时一个由虚拟机自动生成的、直接继承自Object的子类
  • 常量在编译阶段存入调用类的常量池中,本质上没有直接引用到定义常量的类,不会导致定义常量的类的初始化
  1. 类加载过程

(1)加载

加载过程主要完成三件事:

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

(2)验证

确保Class文件的字节流中包含的信息符合当前虚拟机的要求,不会危害虚拟机自身的安全

  • 文件格式验证
  • 元数据验证
  • 字节码验证
  • 符号引用验证

(3)准备

为类变量分配内存并设置初始值,使用的是方法区的内存。
实例变量不会在这个阶段分配内存,它会在对象实例化时随着对象一起分配到Java堆中

(4)解析

将常量池内的符号引用替换为直接引用

(5)初始化

在初始化阶段,Java虚拟机才真正开始执行类中编写的Java程序代码,其实就是执行类构造器< clinit >()方法的过程。< clinit >()方法由编译器自动收集类中所有类变量的赋值操作和静态语句块中的语句合并产生,编译器收集的顺序由语句在源文件中出现的顺序决定

  1. 类加载器分类

对于任意一个类,都必须由加载它的类加载器和这个类本身一起共同确立这个类在Java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间

  • 启动类加载器(Bootstrap ClassLoader):负责加载存放在< JAVA_HOME >\lib目录下或者-Xbootclasspath参数指定路径下的类库
  • 扩展类加载器(ExtensionClassLoader):负责加载< JAVA_HOME >\lib\ext目录下或者java.ext.dirs系统变量指定路径下的类库
  • 应用程序类加载器(Application ClassLoader):负责加载当前应用classpath下的所有类库
  1. 说一说什么是双亲委派模型,双亲委派模型原理

双亲委派模型是指:在类加载的时候,先判断当前类是否被加载过,如果已经被加载就直接返回,如果没有被加载就执行相应的类加载过程,如果一个类加载器收到了类加载请求,它首先不会自己去加载这个类,而是把请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层的启动类加载器中,只有当父加载器无法完成这个加载请求时,加载器才会调用自己的findClass()方法去加载。当父类加载器为null时,表示使用启动类加载器作为父类加载器,这就是双亲委派模型,类加载器之间的父子关系是通过组合实现的,而不是通过继承来实现的

在这里插入图片描述

6.使用双亲委派模型的好处

使用双亲委派模型能够能够保证程序的稳定运行,避免类的重复加载,因为类是根据它的全限定名和它的类加载器作为唯一标识的,同时也保证了Java的核心API不被篡改

  1. 如何破坏双亲委派模型
  • 继承ClassLoader类并且重写loadClass()方法,因为双亲委派模型是在loadClass()方法中实现的,重写loadClass()方法就能打破双亲委派模型,想要保留双亲委派模型的话,可以重写findClass()方法来完成加载
  • 使用线程上下文类加载器,它其实是一种父类加载器去请求子类加载器完成类加载的行为
  • OSGi的模块化热部署也破坏了双亲委派模型

查漏补缺

  1. 什么时候会进行新生代垃圾回收,什么时候会进行老年代垃圾回收
  2. 对象头是什么
  3. minor gc、major gc、full gc
    sLoader类并且重写loadClass()方法,因为双亲委派模型是在loadClass()方法中实现的,重写loadClass()方法就能打破双亲委派模型,想要保留双亲委派模型的话,可以重写findClass()方法来完成加载
  • 使用线程上下文类加载器,它其实是一种父类加载器去请求子类加载器完成类加载的行为
  • OSGi的模块化热部署也破坏了双亲委派模型

查漏补缺

  1. 什么时候会进行新生代垃圾回收,什么时候会进行老年代垃圾回收
  2. 对象头是什么
  3. minor gc、major gc、full gc
  4. 哪些对象可作为GC Root
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值