【巨人的肩膀】JAVA面试总结(四)

、JVM

目录

  • 、JVM
    • 1、说一下JVM的主要组成部分及其作用
    • 2、什么是JVM内存结构(谈谈对运行时数据区的理解)
    • 3、堆和栈的区别是什么
    • 4、堆中存什么?栈中存什么?
    • 5、为什么不把基本类型放堆中呢?
    • 6、为什么要把堆和栈区分出来呢?栈中不是也可以存储数据吗
    • 7、什么情况下会发生栈内存溢出
    • 7、判断垃圾可以回收的算法有哪些(如何判断一个对象是否存活)
    • 8、强引用、软引用、弱引用、虚引用是什么,有什么区别?(谈谈堆Java中引用的了解)
    • 9、被引用的对象就一定能存活吗
    • 10、垃圾回收是从哪里开始的呢
    • 11、被标记为垃圾的对象一定会被回收吗
    • 12、谈谈对内存泄露的理解
    • 13、内存泄漏的根本原因是什么
    • 14、尽量避免内存泄漏的方法
    • 15、Java中垃圾回收算法有哪些
    • 16、为什么要采用分代收集算法
    • 17、什么是浮动垃圾
    • 18、什么是内存碎片?如何解决
    • 19、常见的垃圾收集器
    • 20、详细说一下CMS的回收过程,CMS的问题是什么
    • 21、谈谈你对G1收集器的理解
    • 22、JVM中一次完整的GC是什么样子的
    • 23、Minor GC 和 Full GC有什么不同呢?
    • 24、谈谈你对内存分配的理解?大对象怎么分配?
    • 25、介绍下空间分配担保原则
    • 26、Java中有哪些类加载器
    • 27、说说类加载器双亲委派模型
    • 28、列举一些你知道的打破双亲委派机制的例子,为什么要打破
    • 29、JVM中哪些是线程共享区
    • 30、一个对象加载到JVM,再到被GC清除,都经历了什么过程
    • 31、怎么确定一个对象到底是不是垃圾
    • 32、JVM中有哪些垃圾回收算法
    • 33、什么是STW
    • 34、说说对线程安全的理解
    • 35、对守护线程的理解
    • 33、什么是STW
    • 34、说说对线程安全的理解
    • 35、对守护线程的理解

1、说一下JVM的主要组成部分及其作用

  1. 类加载器(ClassLoader)
  2. 运行时数据区(Runtime Data Area)
  3. 执行引擎(Execution Engine)
  4. 本地库接口(Native Interface)

各组件的作用:首先通过类加载器(ClassLoader)会把 Java 代码转换成字节码,运行时数据区(Runtime Data Area)再把字节码加载到内存中,而字节码文件只是 JVM 的一套指令集规范,并不能直接交给底层操作系统去执行,因此需要特定的命令解析器执行引擎(Execution Engine),将字节码翻译成底层系统指令,再交由 CPU 去执行,而这个过程中需要调用其他语言的本地库接口(Native Interface)来实现整个程序的功能。

2、什么是JVM内存结构(谈谈对运行时数据区的理解)

【巨人的肩膀】JAVA面试总结(四)_第1张图片

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

  • 程序计数器:线程私有的,是一块很小的内存空间,作为当前线程的行号指示器,用于记录当前虚拟机正在执行的线程指令地址
  • 虚拟机栈:线程私有的,每个方法执行的时候都会创建一个栈帧,用于存储局部变量表、操作数、动态链接和方法返回等信息,当线程请求的栈深度超过了虚拟机允许的最大深度时,就会抛出StackOverFlowError
  • 本地方法栈:线程私有的,保存的是native方法的信息,当一个jvm创建的线程调用native方法后,jvm不会在虚拟机栈中为该线程创建栈帧,而是简单的动态链接并直接调用该方法
  • 堆:java堆是所有线程共享的一块内存,几乎所有对象的实例和数组都要在堆上分配内存,因此该区域经常发生垃圾回收的操作
  • 方法区:存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码数据。即永久代,在jdk1.8中不存在方法区了,被元数据区替代了,原方法区被分成两部分;1:加载的类信息,2:运行时常量池;加载的类信息被保存在元数据区中,运行时常量池保存在堆中

3、堆和栈的区别是什么

堆和栈(虚拟机栈)是完全不同的两块内存区域,一个是线程独享的,一个是线程共享的。。二者之间最大的区别就是存储的内容不同:堆中主要存放对象实例栈(局部变量表)中主要存放各种基本数据类型、对象的引用

栈解决程序的运行问题,即程序如何执行,或者说如何处理数据。堆解决的是数据存储的问题,即数据怎么放、放在哪儿。在 Java 中一个线程就会相应有一个线程栈与之对应,因为不同的线程执行逻辑有所不同,因此需要一个独立的线程栈。而堆则是所有线程共享的。

栈因为是运行单位,因此里面存储的信息都是跟当前线程(或程序)相关信息的。包括局部变量、程序运行状态、方法返回值等等;而堆只负责存储对象信息


  1. 申请方式

    • 栈:由系统自动分配。例如,声明在函数中一个局部变量 int b; 系统自动在栈中为 b 开辟空间
    • 堆:需要程序员自己申请,并指明大小
  2. 申请后系统的响应

    • 栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出
    • 堆:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。
  3. 申请效率的比较

    • 栈:由系统自动分配,速度较快。但程序员是无法控制的。【静态变量不入栈
    • 堆:由 new 分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便

4、堆中存什么?栈中存什么?

堆中存的是对象。栈中存的是基本数据类型和堆中对象的引用

5、为什么不把基本类型放堆中呢?

因为基本数据类型占用的空间一般是1~8个字节,需要空间比较少,而且因为是基本类型,所以不会出现动态增长的情况,长度固定,因此栈中存储就够了。如果把它存在堆中是没有什么意义的。

基本类型和对象的引用都是存放在栈中,而且都是几个字节的一个数,因此在程序运行时,它们的处理方式是统一的。

但是基本类型、对象引用和对象本身就有所区别了,因为一个是栈中的数据一个是堆中的数据。

6、为什么要把堆和栈区分出来呢?栈中不是也可以存储数据吗

  1. 从软件设计的角度看,栈代表了处理逻辑,而堆代表了数据。这样分开,使得处理逻辑更为清晰。分而治之的思想。这种隔离、模块化的思想在软件设计的方方面面都有体现
  2. 堆与栈的分离,使得堆中的内容可以被多个栈共享(也可以理解为多个线程访问同一个对象)。这种共享的收益是很多的。
  3. 栈因为运行时的需要,比如:保存系统运行的上下文,需要进行地址段的划分。由于栈只能向上增长,因此就会限制住栈存储内容的能力。而堆不同,堆中的对象是可以根据需要动态增长的,因此栈和堆的拆分,使得动态增长成为可能

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

  1. 栈是线程私有的,栈的生命周期和线程一样,每个方法在执行的时候就会创建一个栈帧,它包含局部变量表、操作数栈、动态链接、方法出口等信息,局部变量表又包括基本数据类型和对象的引用
  2. 当线程请求的栈深度超过了虚拟机允许的最大深度时,会抛出StackOverFlowError异常,方法递归调用肯可能会出现该问题

7、判断垃圾可以回收的算法有哪些(如何判断一个对象是否存活)

垃圾收集器在对堆区和方法区进行回收前,首先要确定这些区域的对象哪些可以被回收,哪些暂时还不能回收,这就要用到判断对象是否存活的算法。

  1. 引用计数法:给每一个对象设置一个引用计数器,当有一个地方引用该对象的时候,引用计数器就+1,引用失效时,引用计数器就-1。当引用计数器为0的时候,就说明这个对象没有被引用,也就是垃圾对象,等待回收
    • 缺点:无法解决循环引用的问题,当A引用B,B也引用A的时候,此时AB对象的引用都不为0,此时也就无法垃圾回收,所以一般主流虚拟机都不采用这个方法
  2. 可达性分析法:程序把所有的引用关系看作一张图,从一个节点 GC ROOT 开始,寻找对应的引用节点,找到这个节点以后,继续寻找这个节点的引用节点,当所有的引用节点寻找完毕之后,剩余的节点则被认为是没有被引用到的节点,即无用的节点,无用的节点将会被判定为是可回收的对象

在 Java 语言中,可作为 GC Roots 的对象包括下面几种:

  1. 虚拟机栈中引用的对象
  2. 方法区中类静态属性引用的对象
  3. 方法区中常量引用的对象
  4. 本地方法栈中 JNI(Native方法)引用的对象

8、强引用、软引用、弱引用、虚引用是什么,有什么区别?(谈谈堆Java中引用的了解)

无论是通过引用计数算法判断对象的引用数量,还是通过可达性分析算法判断对象的引用链是否可达,判定对象是否存活都与“引用”有关。在Java语言中,将引用又分为强引用、软引用、弱引用、虚引用 4 种,这四种引用强度依次逐渐减弱。

  • 强引用,就是普通的对象引用关系,如 String s = new String("xiaolin")
  • 软引用,用于维护一些可有可无的对象。只有在内存不足时,系统则会回收软引用对象,如果回收了软引用对象之后仍然没有足够的内存,才会抛出内存溢出异常
  • 弱引用:相比软引用来说,要更加无用一些,它拥有更短的生命周期,当 JVM 进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象
  • 虚引用是一种形同虚设的引用,在现实场景中用的不是很多,它主要用来跟踪对象被垃圾回收的活动

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

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

10、垃圾回收是从哪里开始的呢

栈是真正进行程序执行地方,垃圾回收是从栈开始。以栈为引用起点,我们可以找到堆中的对象,又从这些对象找到对堆中其他对象的引用,这种引用逐步扩展,最终以 null 引用或者基本类型结束,这样就形成了一颗以 Java 栈中引用所对应的对象为根节点的一颗对象树。

如果栈中有多个引用,则最终会形成多颗对象树。在这些对象树上的对象,都是当前系统运行所需要的对象,不能被垃圾回收。而其他剩余对象,则可以视为无法被引用到的对象,可以被当做垃圾进行回收

11、被标记为垃圾的对象一定会被回收吗

即使在可达性分析算法中不可达的对象,也并非是“非死不可”,这时候它们暂时处于“缓刑”阶段,要真正宣告一个对象死亡,至少要经历两次标记过程。

第一次标记:如果对象在进行可达性分析后发现没有与 GC Roots 相连接的引用链,那它将会被第一次标记

第二次标记:第一次标记后接着会进行一次筛选,筛选的条件是此对象是否有必要执行 finalize() 方法。在 finalize() 方法中没有重新与引用链建立关联关系的,将被进行第二次标记。二次标记成功的对象将真的会被回收,如果对象在 finalize() 方法中重新与引用链建立了关联关系,那么将会逃离本次回收,继续存活

12、谈谈对内存泄露的理解

在 Java 中,内存泄漏就是存在一些不会再被使用却没有被回收的对象,这些对象有下面两个特点:

  1. 这些对象是可达的,即在有向图中,存在通路可以与其相连
  2. 这些对象是无用的,即程序以后不会再使用这些对象

如果对象满足这两个条件,这些对象就可以判定为 Java 中的内存泄漏,这些对象不会被 GC 所回收,然而它却占用内存。

13、内存泄漏的根本原因是什么

长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄漏,尽管短生命周期对象已经不再需要,但是因为长生命周期持有它的引用而导致不能被回收,这就是 Java 中内存泄漏的发生场景。

14、尽量避免内存泄漏的方法

  1. 尽量不要使用 static 成员变量,减少生命周期
  2. 及时关闭资源
  3. 不用的对象,可以手动设置为 null

15、Java中垃圾回收算法有哪些

ava中有四种垃圾回收算法,分别是标记清除法、标记整理法、复制算法、分代收集算法

  1. 标记清除算法

    • 标记-清除算法采用从根集合(GC Roots)进行扫描,对存活的对象进行标记,标记完毕后,再扫描整个空间中未被标记的对象,进行回收。
    • 特点:标记-清除算法不需要进行对象的移动,只需对不存活的对象进行处理,在存活对象比较多的情况下极为高效,但由于标记-清除算法直接回收不存活的对象,因此会造成内存碎片
  2. 复制算法

    • 将内存按照容量大小分为大小相等的两块,每次只使用一块,当一块使用完了,就将还存活的对象移到另一块上,然后在把使用过的内存空间移除
    • 特点:不会产生空间碎片;内存使用率极低
  3. 标记-整理算法

    • 标记-整理算法是在标记-清除算法的基础上,又进行了对象的移动,因此成本更高,但是却解决了内存碎片的问题
  4. 分代收集算法

    • 根据内存对象的存活周期不同,将内存划分成几块,java虚拟机一般将内存分成新生代和老生代,在新生代中,有大量对象死去和少量对象存活,所以采用复制算法,只需要付出少量存活对象的复制成本就可以完成收集;老年代中因为对象的存活率极高,没有额外的空间对他进行分配担保,所以采用标记清理或者标记整理算法进行回收

16、为什么要采用分代收集算法

分代的垃圾回收策略,是基于这样一个事实:不同的对象的生命周期是不一样的。因此,不同生命周期的对象可以采取不同的收集方式,以便提高回收效率

在 Java 程序运行的过程中,会产生大量的对象,其中有些对象是与业务信息相关,比如 Http 请求中的 Session 对象、线程、Socket 连接,这类对象跟业务直接挂钩,因此生命周期比较长。但是还有一些对象,主要是程序运行过程中生成的临时变量,这些对象生命周期会比较短,比如:String 对象,由于其不变类的特性,系统会产生大量的这些对象,有些对象甚至只用一次即可回收。

在不进行对象存活时间区分的情况下,每次垃圾回收都是对整个堆空间进行回收,花费时间相对会长,所以分代垃圾回收采用分治的思想,进行代的划分,把不同生命周期的对象放在不同代上,不同代上采用最适合它的垃圾回收方式进行回收。

17、什么是浮动垃圾

由于在应用运行的同时进行垃圾回收,所以有些垃圾可能在垃圾回收进行完成时产生,这样就造成了“Floating Garbage”,这些垃圾需要在下次垃圾回收周期时才能回收掉。所以,并发收集器一般需要20%的预留空间用于这些浮动垃圾。

18、什么是内存碎片?如何解决

由于不同 Java 对象存活时间是不一定的,因此,在程序运行一段时间以后,如果不进行内存整理,就会出现零散的内存碎片。

碎片最直接的问题就是会导致无法分配大块的内存空间,以及程序运行效率降低。所以,在上面提到的基本垃圾回收算法中,“复制”方式和“标记-整理”方式,都可以解决碎片的问题。

19、常见的垃圾收集器

Serial GC 串行 工作线程暂停,一个线程进行垃圾回收 新生代 复制算法
Serial Old GC 串行 工作线程暂停,一个线程进行垃圾回收 老年代 标记-整算法
ParNew GC 并行 工作线程暂停,多个线程进行垃圾回收 新生代 复制算法 Serial GC的多线程版
CMS GC 并行 用户线程和垃圾回收线程同时执行 老年代 标记-清除算法 低暂停
Parallel GC 并行 工作线程暂停,多个线程进行垃圾回收 新生代 复制算法 和ParNew相比,能动态调整内存分配情况(JDK8默认)
Parallel Old GC 并行 工作线程暂停,多个线程进行垃圾回收 老年代 标记-整理算法 替代串行的Serial Old GC
G1 并行 用户线程和垃圾回收线程同时执行 整堆 分区算法 JDK9默认
ZGC 并行 用户线程和垃圾回收线程同时执行 整堆 分页算法

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

CMS(Concurrent Mark Sweep,并发标记清除) 收集器是以获取最短回收停顿时间为目标的收集器(追求低停顿),它在垃圾收集时使得用户线程和 GC 线程并发执行,因此在垃圾收集过程中用户也不会感到明显的卡顿。

CMS 回收过程分为以下四步:

  1. 初始标记 :首先STW,暂停所有工作线程,然后标记出 GC Roots 能直接可达的对象,一旦标记完,就恢复工作线程继续执行
  2. 并发标记:根据上一步的结果,继续向下标识所有关联的对象,直到这条链上的最尽头。这个过程是多线程的,虽然耗时理论上会比较长,但是其它工作线程并不会阻塞,没有 STW。
  3. 重新标记:顾名思义,就是要再标记一次。为啥还要再标记一次?因为第 2 步并没有阻塞其它工作线程,其它线程在标识过程中,很有可能会产生新的垃圾。有STW
  4. 并发清除:清除阶段是清理删除掉标记阶段判断的已经死亡的对象,由于不需要移动存活对象,所以这个阶段也是可以与用户线程同时并发进行的。

CSM的问题:

  1. 并发回收导致CPU资源紧张:在并发阶段,它虽然不会导致用户线程停顿,但却会因为占用了一部分线程而导致应用程序变慢,降低程序总吞吐量。CMS默认启动的回收线程数是:(CPU核数 + 3)/ 4,当CPU核数不足四个时,CMS对用户程序的影响就可能变得很大。

  2. 无法清理浮动垃圾:在CMS的并发标记和并发清理阶段,用户线程还在继续运行,就还会伴随有新的垃圾对象不断产生,但这一部分垃圾对象是出现在标记过程结束以后,CMS无法在当次收集中处理掉它们,只好留到下一次垃圾收集时再清理掉。

  3. 并发失败:如果在并发标记、并发清理过程中,由于用户线程同时在执行,如果有新对象要进入老年代,但是空间又不够,此时就会利用 Serial Old 来做一次垃圾收集,就会做一次全局的 STW。

  4. 内存碎片问题:CMS是一款基于“标记-清除”算法实现的回收器,这意味着回收结束时会有内存碎片产生。

21、谈谈你对G1收集器的理解

G1 可谓博采众家之长,力求到达一种完美。它吸取了增量收集优点,把整个堆划分为一个一个等大小的区域(region)。内存的回收和划分都以region为单位。同时,它也吸取了 CMS 的特点,把这个垃圾回收过程分为几个阶段,分散一个垃圾回收过程。而且,G1 也认同分代垃圾回收的思想,认为不同对象的生命周期不同,可以采取不同收集方式,因此,它也支持分代的垃圾回收。

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

在 Java 中,堆被划分成两个不同的区域:新生代 ( Young )、老年代 ( Old ),新生代默认占总空间的 1/3,老年代默认占 2/3。 新生代有 3 个分区:Eden、To Survivor、From Survivor,它们的默认占比是8:1:1

  • 新生代的垃圾回收(又称Minor GC)后只有少量对象存活,所以选用复制算法,只需要少量的复制成本就可以完成回收。
  • 老年代的垃圾回收(又称Major GC)通常使用“标记-清理”或“标记-整理”算法。

【巨人的肩膀】JAVA面试总结(四)_第2张图片

再描述它们之间转化流程:

  • 对象优先在Eden分配。当 eden 区没有足够空间进行分配时,虚拟机将发起一次 Minor GC。

    • 在 Eden 区执行了第一次 GC 之后,存活的对象会被移动到其中一个 Survivor 分区
    • Eden 区再次 GC,这时会采用复制算法,将 Eden 和 from 区一起清理,存活的对象会被复制到 to 区
    • 移动一次,对象年龄加 1,对象年龄大于一定阀值会直接移动到老年代。
    • Survivor 区内存不足会发生担保分配,超过指定大小的对象可以直接进入老年代
  • 大对象直接进入老年代,大对象就是需要大量连续内存空间的对象(比如:字符串、数组),为了避免为大对象分配内存时由于分配担保机制带来的复制而降低效率

  • 老年代满了而无法容纳更多的对象,Minor GC 之后通常就会进行Full GC,Full GC 清理整个内存堆 – 包括年轻代和老年代

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

Minor GC:只收集新生代的GC。

  • **Minor GC触发条件:**当Eden区满时,触发Minor GC。

Full GC: 收集整个堆,包括 新生代,老年代

  • Full GC触发条件

    • 老年代空间不够分配新的内存

    • 调用System.gc时,系统建议执行Full GC,但是不必然执行

    • 由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小

24、谈谈你对内存分配的理解?大对象怎么分配?

  1. 对象优先在 Eden 区分配:大多数情况下,对象在新生代 Eden 区分配,当 Eden 区空间不够时,发起 Minor GC
  2. 大对象直接进入老年代:大对象是指需要连续内存空间的对象,最典型的大对象是那种很长的字符串以及数组。
  3. 长期存活的对象将进入老年代:为对象定义年龄计数器,对象在 Eden 出生并经过 Minor GC 依然存活,将移动到 Survivor 中,年龄就增加 1 岁,增加到一定年龄则移动到老年代中。
  4. 动态对象年龄判定:为了更好的适应不同程序的内存情况,虚拟机不是永远要求对象年龄必须达到了某个值才能进入老年代,如果 Survivor 空间中相同年龄所有对象大小的总和大于 Survivor 空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无需达到要求的年龄

25、介绍下空间分配担保原则

如果YougGC时新生代有大量对象存活下来,而 survivor 区放不下了,这时必须转移到老年代中,但这时发现老年代也放不下这些对象了,那怎么处理呢?其实JVM有一个老年代空间分配担保机制来保证对象能够进入老年代。

在执行每次 YoungGC 之前,JVM会先检查老年代最大可用连续空间是否大于新生代所有对象的总大小。因为在极端情况下,可能新生代 YoungGC 后,所有对象都存活下来了,而 survivor 区又放不下,那可能所有对象都要进入老年代了。这个时候如果老年代的可用连续空间是大于新生代所有对象的总大小的,那就可以放心进行 YoungGC。

在允许担保失败并尝试进行YoungGC后,可能会出现三种情况:

  1. YoungGC后,存活对象小于survivor大小,此时存活对象进入survivor区中
  2. YoungGC后,存活对象大于survivor大小,但是小于老年大可用空间大小,此时直接进入老年代
  3. YoungGC后,存活对象大于survivor大小,也大于老年大可用空间大小,老年代也放不下这些对象了,此时就会发生“Handle Promotion Failure”,就触发了 Full GC。如果 Full GC后,老年代还是没有足够的空间,此时就会发生OOM内存溢出了。

26、Java中有哪些类加载器

类加载器是指:通过一个类的全限定性类名获取该类的二进制字节流叫做类加载器;类加载器分为以下四种:

  • 启动类加载器(BootStrapClassLoader):用来加载java核心类库,无法被java程序直接引用
  • 扩展类加载器(Extension ClassLoader):用来加载java的扩展库,java的虚拟机实现会提供一个扩展库目录,该类加载器在扩展库目录里面查找并加载java类
  • 系统类加载器(AppClassLoader):它根据java的类路径来加载类,一般来说,java应用的类都是通过它来加载的
  • 自定义类加载器:由java语言实现,继承自ClassLoader

【巨人的肩膀】JAVA面试总结(四)_第3张图片

27、说说类加载器双亲委派模型

当一个类加载器收到一个类加载的请求,他首先不会尝试自己去加载,而是将这个请求委派给父类加载器去加载,只有父类加载器在自己的搜索范围类查找不到给类时,子加载器才会尝试自己去加载该类。

所以,双亲委派指得是,JVM在加载类时,会委派给Ext和Bootstrap进行加载,如果没加载到才由自己进行加载。

怎么打破双亲委派模型?

答案:自定义类加载器,继承ClassLoader类,重写loadClass方法和findClass方法。

28、列举一些你知道的打破双亲委派机制的例子,为什么要打破

  • Tomcat,应用的类加载器优先自行加载应用目录下的 class,并不是先委派给父加载器,加载不了才委派给父加载器。tomcat之所以造了一堆自己的classloader,大致是出于下面三类目的
    • 对于各个 webapp中的 classlib,需要相互隔离,不能出现一个应用中加载的类库会影响另一个应用的情况,而对于许多应用,需要有共享的lib以便不浪费资源
    • jvm一样的安全性问题。使用单独的 classloader去装载 tomcat自身的类库,以免其他恶意或无意的破坏
    • 热部署

29、JVM中哪些是线程共享区

堆区和方法区是所有线程共享的,栈、本地方法栈、程序计数器是每个线程独有的。

30、一个对象加载到JVM,再到被GC清除,都经历了什么过程

  1. 类加载:首先根据字节码文件内容加载到方法区
  2. 然后根据类信息在堆区创建对象
  3. 对象首先会分配在堆区中年轻代的 Eden 区,经过一次 Minor GC后,对象如果存活,就会进入 Suvivor 区。在后续的每次 GC 中,如果对象一直存活,就会在 Suvivor 区来回拷贝,每移动一次,年龄+1
  4. 当年龄超过 15 后,对象依然存活,对象就会进入老年代
  5. 如果经过 Full GC,被标记为垃圾对象,那么就会被 GC 线程清理掉

31、怎么确定一个对象到底是不是垃圾

  1. 引用计数算法: 这种方式是给堆内存当中的每个对象记录一个引用个数。引用个数为0的就认为是垃圾。这是早期JDK中使用的方式。引用计数无法解决循环引用的问题。(两个对象相互引用,引用个数一直不为0)
  2. 可达性算法: 这种方式是在内存中,从根对象向下一直找引用,找到的对象就不是垃圾,没找到的对象就是垃圾。

32、JVM中有哪些垃圾回收算法

  1. 标记清除算法:先标记再清除
    1. 标记阶段:把垃圾内存标记出来
    2. 清除阶段:直接将垃圾内存回收。
    3. 这种算法是比较简单的,但是有个很严重的问题,就是会产生大量的内存碎片
  2. 复制算法:为了解决标记清除算法的内存碎片问题,就产生了复制算法。复制算法将内存分为大小相等的两半,每次只使用其中一半。垃圾回收时,将当前这一块的存活对象全部拷贝到另一半,然后当前这一半内存就可以直接清除。这种算法没有内存碎片,但是他的问题就在于浪费空间。而且,他的效率跟存活对象的个数有关。
  3. 标记压缩算法:为了解决复制算法的缺陷,就提出了标记压缩算法。这种算法在标记阶段跟标记清除算法是一样的,但是在完成标记之后,不是直接清理垃圾内存,而是将存活对象往一端移动,然后将边界以外的所有内存直接清除。

33、什么是STW

STW: Stop-The-World,暂停整个世界,是在垃圾回收算法执行过程当中,需要将JVM内存冻结的一种状态。在STW状态下,JAVA的所有线程都是停止执行的-GC线程除外,native方法可以执行,但是,不能与JVM交互。GC各种算法优化的重点,就是减少STW,同时这也是JVM调优的重点。

34、说说对线程安全的理解

线程安全指的是,我们写的某段代码,在多个线程同时执行这段代码时,不会产生混乱,依然能够得到正常的结果,比如i++,i初始化值为0,那么两个线程来同时执行这行代码,如果代码是线程安全的,那么最终的结果应该就是一个线程的结果为1,一个线程的结果为2,如果出现了两个线程的结果都为1,则表示这段代码是线程不安全的。

所以线程安全,主要指的是一段代码在多个线程同时执行的情况下,能否得到正确的结果

35、对守护线程的理解

,然后将边界以外的所有内存直接清除。

33、什么是STW

STW: Stop-The-World,暂停整个世界,是在垃圾回收算法执行过程当中,需要将JVM内存冻结的一种状态。在STW状态下,JAVA的所有线程都是停止执行的-GC线程除外,native方法可以执行,但是,不能与JVM交互。GC各种算法优化的重点,就是减少STW,同时这也是JVM调优的重点。

34、说说对线程安全的理解

线程安全指的是,我们写的某段代码,在多个线程同时执行这段代码时,不会产生混乱,依然能够得到正常的结果,比如i++,i初始化值为0,那么两个线程来同时执行这行代码,如果代码是线程安全的,那么最终的结果应该就是一个线程的结果为1,一个线程的结果为2,如果出现了两个线程的结果都为1,则表示这段代码是线程不安全的。

所以线程安全,主要指的是一段代码在多个线程同时执行的情况下,能否得到正确的结果

35、对守护线程的理解

线程分为用户线程守护线程,用户线程就是普通线程,守护线程就是JVM的后台线程,比如垃圾回收线程就是一个守护线程,一直在运行,守护线程会在其他普通线程都停止运行之后才会自动关闭。我们可以通过设置thread.setDaemon(true)来把一个线程设置为守护线程。

你可能感兴趣的:(#,JAVA,java,面试,jvm)