大明哥 大明哥

程序员、死磕 Java 创始人

死磕 Java 大明哥精心打造的 Java 进阶类系列教程,希望大家少走弯路。 更多死磕系列

  • Java现在发布的版本很快,每年两个,但是真正会被大规模使用的是3年一个的LTS版本。每3年发布一个LTS(Long-TermSupport),长期维护版本。意味着只有Java8,Java11,Java17,Java21才可能被大规模使用。每年发布两个正式版本,分别是3月份和9月份。在Java版本中,一个特性的发布都会经历孵化阶段、预览阶段和正式版本。其中孵化和预览可能会跨越多个Java版本。所以大明哥在介绍Java新特性时采用如下这种策略:每个版本的新特性,大明哥都会做一个简单的概述。单独出文介绍跟编码相关的新特性,一些如JVM、性能优化的新特性不单独出文介绍。孵化阶段的新特性不出文介绍。首

  • 又到年底了,又是好多小伙伴主动或者被动找工作的时候了,最近好多小伙伴,都问我有没有最新面试题,有!!肯定有!!我就把我看过的和整理过的面试题,以答案都整理好,整理了目前最新版的《互联网大厂面试题》,涵盖了所有的Java面试题,并且为了各位小伙伴更好地有针对性地刷题,大明哥按照技术类别将面试题分了类,包括:Java基础、Java并发、Spring、SpringBoot、设计模式、Redis、Kafka等等共25个分类,99个PDF文件。面试题:25个文件夹99个PDF,包括Java集合、JVM、多线程、并发编程、设计模式、Java、MyBatis、ZooKeeper、Dubbo、Elastics

    2023-12-06
  • 上一个死磕Java专栏【死磕NIO】(当然写的不是很好,争取今年将它重写一遍)是**【死磕Netty】**的铺垫,对于我们Java程序员而言,我们在实际开发过程一般都不会直接使用JavaNIO作为我们的网络编程框架,因为写出一套高质量的JavaNIO程序并不是一件容易的事,除了JavaNIO固有的复杂性和bug之外,作为NIO服务端,我们要处理的事情太多了,如网络闪断、客户端认证、消息编解码、半包读写,客户端一样也有很多复杂的事情要处理,所以如果我们对JavaNIO没有足够了解,没有足够的网络编程经验的话,利用JavaNIO来编写一个高性能的稳定网络编程框架并不是一件容易的事。所以我们一般都不

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

    2024-10-26
    JVM
  • 回答实现热部署功能,就是在不停止应用的前提下更新应用程序代码获配置。实现这个功能主要涉及如下几个方面:自定义类加载器:实现一个自定义类加载器,能够从指定路径加载类。监控类文件的变化:使用文件监控工具来检测类文件的变化。重新加载新类:在文件变化时,卸载旧类并重新加载新类。更新引用:确保更新所有对该类的引用。详解自定义类加载器为了实现热部署功能,我们需要自定义一个类加载器,该类加载器能够在检测到类发生变化时重新加载类。publicclassHotDeployClassLoaderextendsClassLoader{privateStringclassPath;publicHotDeployCla

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

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

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

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

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

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

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

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

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

    2024-10-26
    JVM
  • 回答通常FullGC是JVM为了回收整个堆内存而进行的一次全局性的垃圾回收操作,它会暂停所有的应用线程(STW),所以对应用程序的性能会有比较大的影响。以下是几个能够触发FullGC的常见场景。一、老年代不足这是最直观的表现了。当堆内存中的老年代被占满时,JVM会触发FullGC来尝试回收老年代的对象。二、元空间不足如果元空间不足,也会触发FullGC。导致元空间不足的原因主要有:加载了大量的类,导致元空间不足大量的动态代理类或反射生成的类没有及时卸载详细情况参考:遇到过元空间溢出吗?什么情况会造成元空间溢出?三、显示调用System.gc()应用程序或者第三方库主动调用System.gc()

    2024-10-26
    JVM
  • 回答YoungGC执行时间过长就意味着新生代GC的效率不高。新生代的垃圾回收算法一般都是选择复制算法,复制算法分为标记和复制两个阶段,如果GC执行时间过程则可以向这两个方向去考虑。标记阶段执行时间过长在应用层首先排查是否有类重写了finalize(),如果有类重写了该方法,则需要考虑其他的方式来实现该业务逻辑了。复制阶段执行时间过长复制阶段过长一般情况下就是存活的对象太多了,我们可以降低它们的晋升阈值(-XX:MaxTenuringThreshold=15),让这些对象晋升到老年代,减轻年轻代的压力。查看是否GC执行的线程数不够,如果是线程数不够,则可以适当提高GC执行线程数(-XX:Para

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

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

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

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

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

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

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

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

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

    2024-10-26
    JVM
  • 回答在JVM中,有两种常见的算法来判断Java对象是否存活:引用计数法引用计数法就是通过维护一个引用计数器来跟踪对象的引用数量。每当有一个地方引用该对象时,其计数器就+1。当引用失效时,计数器就-1。当对象的引用计数为0时,表明该对象不再被使用,可以回收了。引用计数法的优点就在于它易于理解,而且实现起来比较容易。但是它无法解决循环引用的场景。例如,两个对象互相引用,且它们都没有被其他地方引用,由于引用计数算法的缺陷会导致这两个对象无法被回收。可达性分析算法由于引用计数法的缺陷,目前主流的虚拟机都使用可达性分析算法来判断对象是否存活。可达性分析算法是从一些被称之为"GCRoots&qu

    2024-10-26
    JVM
  • 回答在GC过程中,JVM会暂停应用程序的非GC线程的执行,去专注执行GC操作,这个暂停的阶段就是StopTheWorld,简称STW。详解STW会有什么影响?由于STW会暂停应用程序,则它会对应用程序的性能产生影响。系统响应时间增多。当发生STW时,所有的应用线程都会被暂停,整个应用的执行会暂时停止,直到GC完成。这段暂停时间会直接影响到应用的响应时间,尤其是在高并发的系统中,用户请求可能会出现较长时间的延迟。性能抖动。STW并不是固定时间发生的,它是由内存使用情况、GC的调优参数等多方面因素来决定的,所以STW会带来不确定的暂停时间,会导致系统出现性能抖动情况。吞吐量下降。当发生STW时,所

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

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

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

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

    2024-10-26
    JVM
  • 回答1、大对象直接进入老年代为了避免一些大对象在Eden区和Survivor区频繁地复制,JVM会直接将这些大对象分配到老年代。那多大的对象才是大对象呢?可以通过-XX:PretenureSizeThreshold控制,超过这个大小的对象直接进入老年代。2、对象在年轻代经历多次GC后仍然存活年轻代分为三个区:Eden区、Survivor0区和Survivor1区,新建对象直接在Eden区分配。当Eden区满了后,JVM会触发一次MinorGC,存活的对象会被复制到Survivor0区。当下次触发MinorGC时,JVM会将存活的对象复制到Survivor1。若一个对象经历多次(默认15次)Mi

    2024-10-26
    JVM
  • 回答一、对象年龄JVM给每个都定义了一个对象年龄计数器。首先对象在新生代的Eden去分配,经历第一次MinorGC后仍然存活,并且能够被Survivor容纳的话,则它将被移动到Survivor区,同时对象的年龄设置为1。对象在Survivor取每经历一次MinorGC仍然能够存活的话,年龄就增加1,当它的年龄增加到一定程度(默认为15)时,就晋升到老年代。对象晋升老年代的年龄阈值可以通过-XX:MaxTenuringThreshold=<N>配置,但最大只能到15。二、Survivor区的占用情况如果新生代经历一个MinorGC后,Survivor区放不下所有幸存的对象,则一部分对

    2024-10-26
    JVM
  • 回答新生代GC算法使用的是复制算法,且新生代存放的对象都是生命周期比较短的对象,其特点就是每次GC后,存活的对象不会很多。在一次GC过程中,JVM将会:清理Eden区和一个Survivor区(例如S0)将Eden区中存活的对象以及一个Survivor区(S0)中存活的对象,复制到另一个Survivor区(S1)。为什么这里需要两个Survivor区呢?其核心目的优化GC过程中的拷贝过程、减少内存碎片,同时能够更好地支持对象的年龄和晋升。扩展我们知道新生代中的对象绝大多数都是朝生夕死,采取的GC的算法也是复制算法,复制算法好是好,但是它有一个缺点就是每次只能使用1/2的内存空间。如果我们将新生代

    2024-10-26
    JVM
  • 回答不一定。在大多数情况下,Java对象默认是在堆上分配的。但是,为了优化性能,JVM引入了一种名为逃逸分析(EscapeAnalysis)的技术。通过逃逸分析,JVM可以确定对象是否可以在方法中被局部使用而不逃逸到方法外部。如果一个对象只有一个方法使用,且不会逃逸处这个方法,那么JVM则会在栈上分配该对象,而不是在堆上。例如:publicclassEscapeAnalysisTest{publicvoidcreateObject(){Pointpoint=newPoint(1,2);System.out.println(point.x+","+point.y);}}cla

    2024-10-26
    JVM
  • 回答逃逸分析(EscapeAnalysis)是JVM的一项优化技术。JVM通过分析代码中对象的生命周期和作用域,判断某个对象是否会逃逸出某个作用域(如方法或者线程),如果JVM可以确定该对象不会逃逸,则可以做一些优化策略,例如栈上分配、标量替换、同步消除,从而提升程序的执行效率,降低内存分配和垃圾回收的负担。详解逃逸分析详解逃逸:如果一个对象在创建后能够在当前作用域之外被访问或引用,就称为这个对象“逃逸”了。逃逸有两种:方法逃逸:对象被方法外部使用,例如,将对象作为返回值或者传递给其他方法,这种情况下对象必须在堆上分配。线程逃逸:对象被其他线程访问,例如对象被赋值给一个静态变量或者实例变量,这

    2024-10-26
    JVM
  • 回答一个空的Object对象会占用16字节的内存空间。若启用压缩指针,空Object对象默认会占用12个字节,但是为了满足内存对齐的要求,JVM会将对象大小填充为8字节的倍数,所以占用16个字节若关闭压缩指针,空Object对象默认占用16个字节所以,无论是否启用压缩指针,一个空的Object对象在64位JVM中占16字节的内存。详解在HotSpot虚拟机中,一个Java对象在堆内存里面的内存布局是使用OOP结构来表示的,它主要分为三个部分:一、对象头在Java中每个对象都有一个对象头,对象头通常包括两个部分:MarkWord:用于存储对象的运行时数据,比如哈希码(HashCode)、GC标志

    2024-10-26
    JVM
  • 回答JVM在Java8中引入元空间(Metaspace)替代永久代(PermanentGeneration)直接的原因是因为Oracle收购了一家号称史上运行最快的Java虚拟机厂商JRockit,在Java8中需要合并Hotspot和JRockit的代码,而JRockit没有永久代。原文如下:ThisispartoftheJRockitandHotspotconvergenceeffort.JRockitcustomersdonotneedtoconfigurethepermanentgeneration(sinceJRockitdoesnothaveapermanentgeneration

    2024-10-26
    JVM
  • 回答JVM的运行时内存区域是Java虚拟机在运行Java程序时用来存储各种数据的地方。它主要分为如下几个部分:程序计数器(ProgramCounterRegister):程序计数器是一块很小的内存区域,它可以看作是当前线程所执行的字节码的行号指示器。Java虚拟机栈:每个线程都有一个独立的Java虚拟机栈,用于存储局部变量、操作数栈、动态链接、方法出口等信息。每当一个方法被调用时,都会创建一个新的栈帧(StackFrame),用于存储方法的局部变量和操作数栈等信息。当方法调用结束时,该栈帧就会销毁。本地方法栈:本地方法栈与Java虚拟机栈类似,只不过本地方法栈是为JVM使用的Native方法服

    2024-10-26
    JVM
  • 回答常量池是JVM的一部分,主要用于存储类和接口中的编译时常量以及运行时被解析的引用。它有如下两种:class文件常量池class文件常量池是指在Java代码编译成字节码文件(.class文件)时,JVM会将所有的字面量(如文本字符串、定义为final的常量值)和符号引用(如类和接口的全路径名、字段的名称和描述符、方法的名称和描述符等)存储在.class文件的常量池中。运行时常量池当类和接口被加载到JVM时,class文件常量池中的数据会被加载到运行时常量池中。运行时常量池位于JVM的方法区。在这个过程中,JVM会对这些常量和符号引用进行解析,将它们转换成直接引用。除此之外,运行时常量池还可以

    2024-10-26
    JVM
  • 在JVM的默认配置中,对象在新生代中的存活阈值默认为15。这个阈值决定了对象在被晋升到老年代之前可以在新生代中经历的GC次数。这个默认值15是经过长期实验和观察得到的一个结果,它可以保证垃圾回收效率的前提下尽可能地减少移动到老年代中的对象数量。如果个对象在新生代中存活足够长的时间(默认15次GC),那么它很可能是一个长期存活的对象,将其移动到老年代是有意义的。当然,我们也可以通过参数-XX:MaxTenuringThreshold来调整这个值,但是这个值不能小于等于0,设置为0就意味着对象在经历了一次MinorGC之后就会立刻进入老年代,这样就会降低新生代GC的效率和增加老年代GC的压力,从而

    2024-10-26
    JVM
  • 回答Tomcat要破坏双亲委派模型的原因主要是为了实现Web应用程序的隔离性。我们知道Tomcat可能会部署多个Web应用,如果多个应用程序使用了不同版本的相同类库,如果不打破双亲委派模型就会容易出错。Tomcat使用了自定义的类加载器,WebAppClassLoader。没有Web应用都有自己的WebAppClassLoader,该实例负责加载该Web应用程序的类,但这个类并不遵循双亲委派模型:在加载类时它会首先检查需要加载的类是否位于Web应用程序的类路径下的。如果是的话,它就不会委派给父类加载器,而是自己负责加载。详解Tomcat为什么需要打破双亲委派模型在Tomcat不是项目时,我们将

    2024-10-26
    JVM
  • 回答在JDBC4.0之后使用spi机制才会破坏双亲委派机制。java.sql.DriverManager在初始化时会调用ServiceLoader的load()去加载实现了java.sql.Driver接口的实现类。ServiceLoader会搜索当前类路径上所有JAR文件内的META-INF/services/java.sql.Driver文件,从这些文件中读取实现类的全限定名。假如我们加载到了MySQL的驱动程序,获取的实现类为com.mysql.jdbc.Driver,但是DriverManager位于rt.jar包,类加载器是启动类加载器,而com.mysql.jdbc.Driver位

    2024-10-26
    JVM
  • 回答问到双亲委派模型了,一般都会进一步问如何打破双亲委派模型!双亲委派模型就是当一个类加载器尝试加载某个类时,它首先会把这个任务委托给他的父类加载器。只有在父类加载器无法完成加载任务时,子类加载器才会尝试自己去加载这个类。这种机制可以确保Java核心库的类型安全,并避免类的重复加载。所以要打破这个双亲委派,我们就打破这个类加载的过程就行了。目前有两种比较好的方案:**自定义类加载器:**我们自定义一个类加载器,重写loadClass()方法,并不做双亲委派实现即可,比如我们先自己尝试加载类,而不是先委托给父类加载器。使用线程上下文类加载器:线程上下文类加载器(ThreadContextClas

    2024-10-26
    JVM
  • 回答双亲委派模型是Java类加载机制中的一个核心概念,在这个模型中,当一个类加载器尝试加载某个类时,它首先不会自己尝试去加载这个类,而是把这个请求委派给父类加载器去完成。只有当父类加载器无法完成这个加载请求时,子类加载器才会尝试自己去加载这个类。这个模型有两个优点:避免类的重复加载:当父加载器已经加载过某一个类时,子加载器就不会再重新加载这个类。保证Java核心库的安全:Java的核心API都是通过BootstrapClassLoader进行加载的,如果别人通过自定义的方式创建了一个同路径伪造的不安全的“Java核心”,例如:java.lang.Integer,类加载器通过向上委托,两个Int

    2024-10-26
    JVM
  • 回答在Java中我们可以通过如下几种方式来判断线程池中的任务是否已全部执行完成。使用isTerminated()方法使用ThreadPoolExecutor的getCompletedTaskCount()使用CountDownLatch扩展使用isTerminated()方法isTerminated()用来判断线程池是否结束,如果结束返回true。使用它时我们必须要在shutdown()方法关闭线程池之后才能使用,否则isTerminated()永不为true。shutdown():拒绝接受新任务,但会继续处理已提交的任务。shutdownNow():尝试停止所有正在执行的任务并返回未执行的任

    2024-08-04
  • 回答因为在try-catch中是无法捕获到子线程抛出的异常。主要原因是因为,每个线程都有自己独立的堆栈空间和执行上下文。由于它是独立的,所以当一个线程抛出异常后,这个异常只会沿着该线程的调用栈向上传播,它不会自动传播到启动它的主线程上去,所以也就不会被主线程的try-catch捕获。那子线程抛了异常要怎么处理呢?目前主流的处理方法有四个:子线程自己捕获异常,自己处理。这是一种最直接的方式。使用Thread.UncaughtExceptionHandler:UncaughtExceptionHandler是Thread的一个内部函数式接口,它用于处理由于线程抛出的未捕获异常而导致线程突然终止的情

    2024-08-04
  • 回答run()run()是Thread的一个实例方法,它实际上是线程执行的入口,但是它本身不会启动一个新的线程,它仅仅只是一个普通的方法调用。publicclassThreadTest{publicstaticvoidmain(String[]args){MyRunnablerunnable=newMyRunnable();runnable.run();}staticclassMyRunnableimplementsRunnable{@Overridepublicvoidrun(){System.out.println("Runningin"+Thread.currentT

    2024-08-04