Java 面试宝典

一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是面试题,也是你 Java 知识点的扫盲贴。

  • 回答Java对象的创建过程分为5个步骤:类加载检查:在创建对象之前,JVM需求确保该对象的类已经加载并且被初始化了。分配内存:类加载检查通过后,JVM就会为新建的对象分配内存。对象所需的内存大小在类加载阶段就已经知道了。初始化零值:在为对象分配内存后,JVM会自动将这块内存初始化为零值,比如数值类型初始化为0,引用类型初始化为null。设置对象头:每个对象都需要知道它的类型信息,因此JVM会为每个对象设置对象头信息。对象头包含两部分内容,第一部分是用于存储对象自身的运行时数据,如hashcode、GC分代年龄、锁状态标志、线程持有的锁等;第二部分是类型指针,指向它对应的类元数据,JVM用这个确定其属于哪个类的实例。调用构造方法:最后一步,JVM会通过new指令调用对象的构造方法。详解类加载检查在创建对象之前,JVM需要确保该对象的类已经完成了加载。在这个过程中,JVM会去检查这个类是否在常

    2024-10-26
    阅读(9)
  • 回答实现热部署功能,就是在不停止应用的前提下更新应用程序代码获配置。实现这个功能主要涉及如下几个方面:自定义类加载器:实现一个自定义类加载器,能够从指定路径加载类。监控类文件的变化:使用文件监控工具来检测类文件的变化。重新加载新类:在文件变化时,卸载旧类并重新加载新类。更新引用:确保更新所有对该类的引用。详解自定义类加载器为了实现热部署功能,我们需要自定义一个类加载器,该类加载器能够在检测到类发生变化时重新加载类。publicclassHotDeployClassLoaderextendsClassLoader{privateStringclassPath;publicHotDeployClassLoader(StringclassPath){this.classPath=classPath;//指定类文件的存放目录}@OverrideprotectedClass<?>find

    2024-10-26
    阅读(8)
  • 回答自定义类加载器可以让我们在应用程序中实现特定的类加载逻辑,例如从特定路径加载类、动态加载类、实现热部署等。要自定义一个类加载器只需要两步即可:继承ClassLoader重写findClass()。在这个方法内我们可以自定义如何查找和加载类的字节码。需要注意的是,如果不想打破双亲委派模型,我们就只需要重写findClass()。但如果想破坏双亲委派模型,则我们需要重写整个loadClass()。但是,在实际应用过程中,如果没有特殊需求,Java官方推荐重写findClass(),而不是重写整个loadClass()。这样我们即可以按照自己的想法来加载类,也能保证自定义的类加载器符合双亲委派机制。下面是一个自定义类加载器的简单例子:自定义类加载器publicclassCustomClassLoaderextendsClassLoader{privateStringclassPath;pub

    2024-10-26
    阅读(10)
  • 回答不确定!因为垃圾收集器的回收机制由JVM控制,而不是由程序员控制,JVM会根据一些策略和算法来决定何时进行垃圾回收。一种情况是,在垃圾收集器执行过程中,我们将一个对象的引用设置为null,但是由于垃圾回收器没有检测到(该对象未被正确标记),JVM仍然认为该对象是存活对象,所以不会在本次GC中将其回收,那么该对象就会变成“浮动垃圾”。只有在下一次垃圾回收时,垃圾收集器才会检测到该对象不可达,并将其回收。还有一点,垃圾收集器并不是在我们每次设置引用为null时就执行回收,它通常是在堆内存不足时触发,或者由JVM决定需要释放内存时执行。如果我们每设置一个null就执行一次,那性能得多低?

    2024-10-26
    阅读(9)
  • 回答在JVM中,类加载器负责将类的字节码加载到内存中,主要有三类:启动类加载器(BootstrapClassLoader):负责加载Java的核心类库,它并不是Java类,是JVM的一部分,用C++实现的。它通常加载存放在<JAVA_HOME>/jre/lib目录下的或者被-Xbootclasspath指定的路径中的并且文件名是被虚拟机识别的文件。扩展类加载器(ExtensionClassLoader):由Java实现,是sun.misc.Launcher$ExtClassLoader的实例。负责加载<JAVA_HOME>\lib\ext目录中或被java.ext.dirs系统变量所指定的路径的类库,这些类库被视为标准核心库的扩展。应用程序类加载器(ApplicationClassLoader):由Java实现,是sun.misc.Launcher$AppClass

    2024-10-26
    阅读(11)
  • 回答Java中类的生命周期主要包括加载、链接、初始化、使用和卸载五个阶段,其中连接又分为验证、准备和解析三个步骤。加载:加载是将类的字节码文件(.class文件)加载到JVM中的过程。链接:链接分为验证、准备、解析三个步骤:验证:主要是验证加载字节码是否合法,不会破坏JVM的安全性。这个过程包括检查类文件的结构、数据类型的准确性等。准备:在这个步骤,JVM会为类的静态变量分配内存空间并初始化为默认值。对于非静态变量,JVM是在对象实例化时才分配内存空间。解析:这个步骤主要就是把Class文件中、常量池中的符号引用转换为直接引用。主要解析的是类或接口、字段、类方法、接口方法、方法类型、方法句柄等符号引用。初始化:在这个阶段,类的静态初始化块和静态变量会被执行和初始化。使用:一旦类被初始化,就可以被实例化或调用。卸载:当没有任何实例引用一个类,并且这个类在内存中不再被使用时,JVM的垃圾回收机

    2024-10-26
    阅读(12)
  • 回答这个面试题主要考查的是你对你们系统的熟悉程度以及垃圾回收器的优劣点和使用场景。比如你可以这样回答:我们线上使用的G1收集器,因为我们的系统使用的对内存有32GB,同时对GC的停顿时间有严格要求,而恰好G1收集器能够很好地处理大堆内存,且它提供了可预测的GC停顿时间,能够在指定的停顿时间目标内回收垃圾,并且能够有效处理大内存堆的碎片问题。该问题没有标准答案,根据自己应用系统的实际情况来阐述即可。扩展常见收集器的使用场景如下表格:收集器收集类型收集算法优点缺点使用场景Serial-单线程-新生代复制算法-实现简单-单线程,不能利用多核CPU-停顿时间长-小型应用-单核CPU,低停顿要求ParNew-多线程-新生代复制算法-支持多线程并行GC,适合多核CPU,与CMS兼容-停顿时间较长配合CMS使用的多线程新生代收集器ParallelScavenge-多线程-新生代复制算法-高吞吐量,适合多

    2024-10-26
    阅读(10)
  • 回答主要原因还是CMS自身存在一些局限性。一、内存碎片问题由于CMS采用的是标记-清除算法,所以它不会对内存进行压缩或整理。这意味着在垃圾回收后,堆内存中可能会产生大量的内存碎片。随着时间的推移,JVM可能会因为内存碎片问题无法找到足够大的连续内存块来为新对象分配内存,导致大对象分配失败。此时,JVM可能需要触发一次FullGC,而CMS默认采用SerialOld收集器来执行FullGC,而SerialOld收集器是单线程收集器,回收性能低下,这就会导致长时间的STW。当然,针对这个情况,CMS也提供了一些参数来调优:-XX:+UseCMSCompactAtFullCollection:在进行FullGC之前进行一次内存整理二、浮动垃圾问题由于CMS的并发清除阶段是与应用线程同时进行的,应用程序在垃圾回收过程中会继续分配新对象。而这些新对象在本次GC周期中可能没有被标记为垃圾,因此这些对象

    2024-10-26
    阅读(13)
  • 回答CMS收集器是一款以获取最短回收停顿时间为目标的收集器,它用于老年代的垃圾回收,整个回收过程分为初始标记、并发标记、重新标记、并发清除四个阶段。各个阶段的主要工作如下:初始标记阶段:该阶段,CMS会暂停应用程序,并标记所有从GCRoots直接可达的对象。因为只需要标记根对象,所以这个阶段的停顿时间非常短。并发标记阶段:该阶段,CMS和应用程序会同时运行,CMS从初始标记阶段标记的根对象出发,开始对整个对象图进行遍历和标记,标记从GCRoots可达的对象。重新标记阶段:由于在并发标记阶段,收集器与应用程序同时进行,应用程序在运行过程中可能会对对象图进行修改,所以需要一个重新标记阶段来处理这些在并发标记期间发生变化的引用关系。在这个阶段,JVM会再次暂停应用程序,CMS会重新扫描一些需要标记的对象,确保标记准确性。并发清除阶段:在这一阶段,CMS会清除那些在标记阶段判断为垃圾的对象,回收其

    2024-10-26
    阅读(10)
  • 回答在JVM中,常见的垃圾收集器主要分为两类:低延迟型和高吞吐量型。主要分为如下集中:SerialGC(串行垃圾收集器)单线程垃圾收集器。该垃圾收集器使用单线程回收垃圾,所以它在执行的会停止应用程序(即STW)。很明显,该垃圾收集器已经不符合我们现有的应用了,当然它还是有一些应用场景的,比如单核处理器或者客户端应用。ParallelGC(并行垃圾收集器)并行垃圾回收器,使用多个线程进行垃圾回收,能够提供较好的吞吐量,适用于高吞吐量的服务端应用。CMSGC(ConcurrentMark-Sweep,并发标记清除收集器)并发标记清除收集器,其目标是减少老年代垃圾回收的停顿时间,主要使用标记-清除算法,允许应用线程和垃圾收集线程并发执行。整个垃圾回收阶段分为四个阶段:初始标记(STW)、并发标记(不需要暂停应用)、重新标记(STW)、并发清除(不需要暂停应用)。但是由于CMS不会进行压缩,会产生

    2024-10-26
    阅读(13)
  • 回答要排查FullGC频繁执行的原因,我们首先需要了解哪些情况会触发FullGC,主要情况有如下几种:老年代不足元空间不足显示调用System.gc()大对象直接进入老年代空间分配担保机制失败CMS时出现promotionfailed和concurrentmodefailure详情情况请阅读:什么情况下会触发FullGC?一般排查步骤大致如下:首先我们需要下掉一台服务器的流量,保护好现场,然后把其他服务器重启,恢复服务。然后就是找运维要dump文件和GC日志文件分析了。一、检查JVMGC日志通过分析GC日志,我们可以看到触发FullGC的条件,如下:12.345:[FullGC(AllocationFailure)[PSYoungGen:1280K->0K(1536K)][ParOldGen:8192K->6144K(8192K)]9472K->6144K(9728K),

    2024-10-26
    阅读(9)
  • 回答通常FullGC是JVM为了回收整个堆内存而进行的一次全局性的垃圾回收操作,它会暂停所有的应用线程(STW),所以对应用程序的性能会有比较大的影响。以下是几个能够触发FullGC的常见场景。一、老年代不足这是最直观的表现了。当堆内存中的老年代被占满时,JVM会触发FullGC来尝试回收老年代的对象。二、元空间不足如果元空间不足,也会触发FullGC。导致元空间不足的原因主要有:加载了大量的类,导致元空间不足大量的动态代理类或反射生成的类没有及时卸载详细情况参考:遇到过元空间溢出吗?什么情况会造成元空间溢出?三、显示调用System.gc()应用程序或者第三方库主动调用System.gc()。需要注意的是这种显示调用的方式只是建议JVM去执行FullGC,但是虚拟机不一定真正去执行。同时,在实际应用中,一般都不建议使用该方法,而是交给虚拟机去管理。可以通过-XX:+DisableExpl

    2024-10-26
    阅读(14)
  • 回答YoungGC执行时间过长就意味着新生代GC的效率不高。新生代的垃圾回收算法一般都是选择复制算法,复制算法分为标记和复制两个阶段,如果GC执行时间过程则可以向这两个方向去考虑。标记阶段执行时间过长在应用层首先排查是否有类重写了finalize(),如果有类重写了该方法,则需要考虑其他的方式来实现该业务逻辑了。复制阶段执行时间过长复制阶段过长一般情况下就是存活的对象太多了,我们可以降低它们的晋升阈值(-XX:MaxTenuringThreshold=15),让这些对象晋升到老年代,减轻年轻代的压力。查看是否GC执行的线程数不够,如果是线程数不够,则可以适当提高GC执行线程数(-XX:ParallelGCThreads=4),一般建议设置为CPU核心数的一半到全部。关于YoungGC的排查,实际情况会更加复杂,大明哥推荐两篇文章:https://heapdump.cn/article/16

    2024-10-26
    阅读(9)
  • 回答执行过于频繁是没有一个特定的标准的,它跟应用服务器的各项指标、应用的负载都存在一定的关系。大明哥认为一台4C8G的服务器,YoungGC的执行频率应该在8~10秒,即一分钟执行10次左右都属于正常现象。如果2~3秒就执行一次,则是过于频繁。YoungGC执行过于频繁无外乎两个原因:应用程序创建对象过快新生代内存设置太小如果新生代内存的设置问题,则可以通过调整-Xmn、-XX:SurvivorRatio等参数设置来解决问题。如果参数设置正常,则就需要借助一些工具(如MAT)来分析dump文件进一步排查。在排查过程着重关于排名前几的Java对象。扩展直接看GClog是很费神,而且还不直观,大明哥推荐一款超级好用的GC日志可视化分析工具GCeasy,通过它,我们可以很方便地看到JVM各个分代的内存使用情况、垃圾回收次数、垃圾回收的原因、垃圾回收占用的时间、吞吐量等,这些指标在我们进行JVM调

    2024-10-26
    阅读(10)
  • 回答JVM中有四种常见的垃圾回收算法:标记-清除算法标记-清除算法分为“标记”和“清除”两个阶段,它通过从GCRoots开始标记所有存活的对象,然后遍历堆清除所有未标记的对象。但是该算法可能会产生内存碎片。该算法通常用于老年代垃圾回收,因为老年代对象存活率较高,且不需要频繁回收。复制算法复制算法将内存区域分为两块,每次只使用其中一块内存。标记存活对象后,JVM会将这些存活对象复制到另一块内存去,然后清空原来的内存块。该算法的优点是内存回收后,没有内存碎片,但是它浪费了一半的内存块,同时,如果存活的对象较多,则复制的开销会很大。所以它适用于新生代垃圾回收,因为新生代的对象生命周期较短,大部分对象很快就会被回收掉。标记-整理算法标记-整理算法结合标记-清除算法和复制算法,它的标记阶段和标记-清除算法一致,但是它标记后不是立刻清除,而是将所有存活对象压缩到内存的一端,然后直接清除掉剩余的部分。它

    2024-10-26
    阅读(6)
  • 回答一、内存溢出内存溢出是指JVM在运行时无法分配足够的内存给新建的对象,致程序抛出OutOfMemoryError异常。导致内存溢出的原因有很多,大致有如下几个:堆内存溢出:这是最常见的OOM了。产生堆内存溢出的原因有蛮多,比如创建大量的大对象、应用程序中使用了大量的缓存且没有及时释放等等。栈内存溢出:栈内存溢出一般都是发生在递归调用过深或者创建了大量的线程。元空间溢出:元空间溢出一般都是发生在我们应用程序通过动态代理(如CGLib、Javassist)生成大量类时。直接内存溢出:堆外内存溢出,比如我们在应用程序中大量使用DirectByteBuffer,超过了-XX:MaxDirectMemorySize设置的限制。二、内存泄漏内存泄漏是指应用程序中不再使用的对象仍然被引用,导致它们无法被垃圾回收器回收,从而导致内存溢出。

    2024-10-26
    阅读(10)
  • 回答元空间溢出一般都是由于应用程序加载过多的类信息引起的。大致情况有如下几种:类加载过多如果我们在应用程序中大量使用动态代理、字节码生成库(如Javassist或CGLIB),频繁生成新类,则可能会耗尽元空间。反射和动态代理一样,如果我们在应用程序中大量使用反射,则也会导致元空间溢出。比如在循环中使用反射。类加载器泄露如果我们自定义的类加载器没有被及时回收,或者引用了被加载的类,则也会造成元空间溢出。在实际生产过程中,大明哥遇到过反射和自定义类加载器而导致的元空间溢出。关于元空间的溢出排查,这两篇文章不错,各位小伙伴可以参考参考:https://juejin.cn/post/7069775402851368967https://heapdump.cn/article/2152817

    2024-10-26
    阅读(12)
  • 回答三色标记算法是一种用于垃圾回收的标记-清除算法,主要用于解决对象的可达性问题。它通过将对象分为三种颜色来追踪其状态,分别是白色、灰色和黑色:白色:还没扫描过的对象,标记为白色。同时,也可表达为对象不可达,GC将会回收该颜色的对象灰色:扫描过该对象,但还没完全扫描过它全部引用的对象,标记为灰色黑色:扫描过该对象,且已该对应的引用已全部扫描完成,标记为黑色详解为什么引入三色标记算法在面试题JVM是如何判断对象是否存活?中,大明哥讲到JVM判断对象是否存活的算法有两种:引用计数法和可达性分析算法,JVM主要采用可达性分析算法,但是可达性分析算法整个过程都需要STW,大大影响应用的整体性能,同时,它还存在误标记的问题。为了解决对象的可达性问题和效率问题,引入三色标记算法。三色标记算法三色标记放将对象分为三种颜色:白色:还没扫描过的对象,标记为白色。如果在标记阶段结束后,对象依然为白色,则表明其

    2024-10-26
    阅读(8)
  • 回答Java对象主要是在堆中分配,但是堆内存是线程共享的,当多个线程同时申请堆内存时,则可能会产生竞争关系,那如何保证内存分配的线程安全呢?有两种解决方案:使用CAS。失败重试,即申请内存失败后再次重新申请,但是这种方案在高并发场景下性能低下。TLAB。JVM采用TLAB策略来保证分配内存时的线程安全。TLAB,即Thread-LocalAllocationBuffer,是为每个线程分配内存的一种机制,其目的是提高JVM内存分配的效率,减少在多线程环境下的竞争。其工作原理是:每个线程在Java堆中预先分配一小块内存,这块内存是该线程的私有线程。当该线程需要给对象分配内存时,就直接在自己这块"私有"内存中分配,当这部分内存区域用完之后,再重新分配新的"私有"内存。这样,每个线程在其私有的TLAB中分配内存,就可以避免多个线程在Java堆内存中争夺锁,从

    2024-10-26
    阅读(11)
  • 回答Java提供了四种引用,分别为强引用、软引用、弱引用和虚引用。Java通过这四种引用来控制对象的行为。一、强引用强引用是我们最常用的引用类型,也是我们最能观察的引用类型。例如:Useruser=newUser();Integerinteger=newInteger("100");Stringstr="skjava.com"如果一个对象具有强引用,则它不会被垃圾回收期回收。即使当前内存空间不足,也不会回收它,而是抛出OutOfMemoryError错误,使程序异常终止。如果要中断一个对象的强引用关系,直接给他赋值null即可,不过在实际开发过程中,都是禁止这样的操作的,因为会产生不可意料的错误。二、软引用在Java中使用java.lang.ref.SoftReference来申明软引用,即:SoftReference<String>so

    2024-10-26
    阅读(7)
  • 回答finalize()是java.lang.Object类中的一个方法,它的主要作用是在垃圾回收器回收对象之前,执行一些释放资源的操作。具体来说,就是当垃圾回收器确定一个对象不会被再次使用时,会去尝试调用该对象的finalize(),以便该对象在回收之前执行一些必要的清理操作。但是,在实际应用过程中,一般都是不推荐使用finalize()。扩展在面试题JVM是如何判断对象是否存活?中,大明哥详细列出了finalize()方法的执行过程,就直接引用了:1、可达性分析后的初次标记在可达性分析中,如果一个对象与GCRoot之间没有引用链连接,JVM会进行一次标记,标记这些不可达的对象。然而,这并不意味着这些对象会马上被回收。在这一阶段,JVM会进一步判断该对象是否有必要执行finalize()方法。2、判断是否执行finalize()方法每个对象默认都有finalize()方法,但这个方法只有

    2024-10-26
    阅读(17)
  • 回答GCRoots是可达性分析算法的起点,那有哪些对象可以作为GCRoots呢?栈帧中的局部变量和参数这类对象属于上下文中的对象。当线程在执行方法时,它会将方法打包成一个栈帧压入到栈中去执行,方法里用到的局部变量会存放到栈帧的本地变量表中。只要方法还在执行,还没有出栈,就以为这些本地变量表中的对象还会被访问,GC就不能回收。所以,这类对象可以作为GCRoots。例如,一个方法中定义的局部变量的引用,如果该方法正在执行中,那么这些引用的对象会被认为是存活的:publicvoidtest(){Objectobj=newObject();//局部变量obj引用的对象可能作为GCRoots}方法区常量池引用的对象常量池中的对象是全局的,它在整个应用程序运行期间是有效的,所以,作为GCRoots也不过分。例如,字符串常量"skjava.com"存在于常量池中,而常量池中的引用也会

    2024-10-26
    阅读(6)
  • 回答在JVM中,有两种常见的算法来判断Java对象是否存活:引用计数法引用计数法就是通过维护一个引用计数器来跟踪对象的引用数量。每当有一个地方引用该对象时,其计数器就+1。当引用失效时,计数器就-1。当对象的引用计数为0时,表明该对象不再被使用,可以回收了。引用计数法的优点就在于它易于理解,而且实现起来比较容易。但是它无法解决循环引用的场景。例如,两个对象互相引用,且它们都没有被其他地方引用,由于引用计数算法的缺陷会导致这两个对象无法被回收。可达性分析算法由于引用计数法的缺陷,目前主流的虚拟机都使用可达性分析算法来判断对象是否存活。可达性分析算法是从一些被称之为"GCRoots"的对象开始,沿着对象之间的引用链进行遍历。如果一个对象能通过这些引用链访问到,则该对象是存活的;否则,该对象被认为是不可达的,可以被回收。扩展可达性分析算法可达性分析算法的核心思路是:通过一系列

    2024-10-26
    阅读(10)
  • 回答在GC过程中,JVM会暂停应用程序的非GC线程的执行,去专注执行GC操作,这个暂停的阶段就是StopTheWorld,简称STW。详解STW会有什么影响?由于STW会暂停应用程序,则它会对应用程序的性能产生影响。系统响应时间增多。当发生STW时,所有的应用线程都会被暂停,整个应用的执行会暂时停止,直到GC完成。这段暂停时间会直接影响到应用的响应时间,尤其是在高并发的系统中,用户请求可能会出现较长时间的延迟。性能抖动。STW并不是固定时间发生的,它是由内存使用情况、GC的调优参数等多方面因素来决定的,所以STW会带来不确定的暂停时间,会导致系统出现性能抖动情况。吞吐量下降。当发生STW时,所有应用线程都会被暂停,这就意味着在这段时间内,应用程序是无法执行任何业务相关的工作。如果系统频繁地进入STW,则会严重减少应用程序的正常运行时间,导致系统的吞吐量下降。为什么需要STW虽然STW会带来

    2024-10-26
    阅读(7)
  • 回答指针碰撞指针碰撞是一种内存分配技术。我们知道绝大多数Java对象是在Java堆中分配(发生逃逸分析除外),我们假定Java堆中的内存是绝对规整的,已分配内存和未分配内存是互相隔开的,中间由一个指针作为分界点,即该指针的左边全部都是已被使用的内存,右边是空闲的内存。当需要分配内存时,JVM只需要将指针向前(右)移动(也可称之为“碰撞”)相应的大小,然后将内存块分配给对象。如下:指针碰撞的优点就在于它分配内存效率高,因为它只需要简单地移动下指针就可以了,同时,也没有内存碎片问题,因为它每次分配内存都是连续的。缺点就在于它需要堆内存绝对规整,适合复制算法。所以指针碰撞通常用于新生代的垃圾收集器中。空闲列表与指针碰撞不一样,空闲列表不需要连续的内存空间。它维护一个指针,列表中的每一个元素都指向内存中空闲的内存块。每当需要分配内存时,JVM会通过这个列表找到一块足够大的内存块,然后将它分配出去。

    2024-10-26
    阅读(17)
  • 回答Java对象的内存布局包括三个部分:对象头、实例数据和对齐填充。对象头:这是对象的元数据区域,包括两个主要部分:MarkWord:存储对象的运行时信息,如哈希码、GC分代年龄、锁状态等。它是动态变化的,根据对象的状态不同而变化。类型指针(ClassPointer):指向对象对应的类的元数据,帮助JVM定位方法表等信息。实例数据:存储对象的实际字段值。JVM按照字段声明的顺序来存储这些数据,同时考虑字段类型的大小。例如,int占4字节,long占8字节。JVM可能会对数据进行对齐优化,以提高访问效率。对齐填充:为了确保内存访问的高效性,JVM会对对象的大小进行内存对齐。通常对象的大小会被调整为8字节或16字节的倍数,这样可以使下一个对象从对齐的内存地址开始,减少访问时的开销。详解对象头对象头分为两个主要部分:MarkWord和类型指针。MarkWordMarkWord用于存储对象自身的运

    2024-10-26
    阅读(12)
  • 回答当新生代发生一次GC后,JVM会将存活的对象复制到另外一个空闲的Survivor区,但如果该Survivor区没有足够的空间来容纳所有的存活对象,JVM通常会采取如下措施:提前晋升到老年代在默认情况下,只有那些年龄已经达到阈值(默认15)的对象才会被晋升到老年代,但如果在复制对象到Survivor区时,发现Survivor区无法容纳这些对象,那么这些对象将会直接晋升到老年代。详情请阅读:JVM年轻代到年老代的晋升过程的判断条件是什么呢?对象什么时候会进入老年代?发生FullGC当Survivor不够,且老年代也接近满时,JVM会触发FullGC,对整个堆进行垃圾回收,包括新生代和老年代。如果回收后仍然没有足够的空间,就会抛出OutOfMemoryError。空间分配担保机制JVM还有一种机制叫做空间分配担保。在执行MinorGC之前,JVM会检查老年代的可用空间是否大于年轻代中所有对象

    2024-10-26
    阅读(11)
  • 回答空间担保是JVM的一种保护机制,它主要确保新生代经过MinorGC,老年代有足够的空间来存放所存活的对象。我们知道,新生代内存区域分为三个区:Eden区、Survivor0区和Survivor1区。新建的对象直接在Eden区分配,当Eden区满了后,JVM会触发一次MinorGC,MinorGC后,JVM会将Eden区和一个Survivor区中存活的对象复制到另外一个Survivor区去,如果该Survivor区的内存不足以容纳全部的存活对象,则JVM会将一部分对象存放到老年代,那如果老年代也不够呢,怎么办?JVM提供了空间分配担保机制,在进行MinorGC之前,JVM会先检查老年代的最大可用连续空间是否大于新生代所有对象的总和。如果大于,则表明此次MinorGC是绝对安全的,因为就算新生代所有的对象全部进老年代,老年代也足够。如果小于,则JVM会先检查参数-X:HandlePromo

    2024-10-26
    阅读(7)
  • 回答1、大对象直接进入老年代为了避免一些大对象在Eden区和Survivor区频繁地复制,JVM会直接将这些大对象分配到老年代。那多大的对象才是大对象呢?可以通过-XX:PretenureSizeThreshold控制,超过这个大小的对象直接进入老年代。2、对象在年轻代经历多次GC后仍然存活年轻代分为三个区:Eden区、Survivor0区和Survivor1区,新建对象直接在Eden区分配。当Eden区满了后,JVM会触发一次MinorGC,存活的对象会被复制到Survivor0区。当下次触发MinorGC时,JVM会将存活的对象复制到Survivor1。若一个对象经历多次(默认15次)MinorGC后仍然存活,那么该对象会被晋升到老年代。3、动态年龄判断并不是所有的对象都需要经历15次MinorGC才会进入老年代。JVM有一个动态年龄判断机制,即若在Survivor空间中相同年龄所有对

    2024-10-26
    阅读(9)
  • 回答一、对象年龄JVM给每个都定义了一个对象年龄计数器。首先对象在新生代的Eden去分配,经历第一次MinorGC后仍然存活,并且能够被Survivor容纳的话,则它将被移动到Survivor区,同时对象的年龄设置为1。对象在Survivor取每经历一次MinorGC仍然能够存活的话,年龄就增加1,当它的年龄增加到一定程度(默认为15)时,就晋升到老年代。对象晋升老年代的年龄阈值可以通过-XX:MaxTenuringThreshold=<N>配置,但最大只能到15。二、Survivor区的占用情况如果新生代经历一个MinorGC后,Survivor区放不下所有幸存的对象,则一部分对象会直接晋升到老年代。三、动态年龄判定为了能更好的适应不同程序的内存状况,JVM并不总是要求对象的年龄达到指定的阈值才会进入老年代,它会动态调整年龄阈值,提前晋升一些对象。即若在Survivor空间中

    2024-10-26
    阅读(11)