JVM面试题

JVM

jvm: jvm就是java虚拟机,它主要分为三部分分别是类加载器、运行时数据区、字节码执行引擎。

类加载器

四种加载器类型

  • 启动类加载器(Bootstrap ClassLoader): 加载jre/lib/rt.jar charset.jar等核心类,是由C++实现的
  • 拓展类加载器(Extension ClassLoader):加载拓展jar包,jre/lib/ext/*.jar 或者由-Djava.ext.dirs指定
  • 系统类加载器(Application ClassLoader):加载classpath指定内容
  • 自定义类加载器(Custom ClassLoader):加载自定义ClassLoader
1. 双亲委派模型

双亲委派机制原理:一个类如果收到了类加载请求,它不会自己先去加载,而是把这个请求委托给父类加载器去执行(这里的父类指的是上一级加载器,并不是继承关系那种),如果父类还存在父类,就将继续向上委托,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,如果父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式。
优势:

  • 避免类的重复加载:当一个类加载器需要加载某个类时,它会先委托给它的父类加载器去加载,如果父类加载器也找不到该类,它才会自己去尝试加载,避免重复加载同一个类。
  • 确保类的安全性:通过双亲委派模型,可以确保核心类库的安全性,同时防止应用程序自己编写的类替换掉核心类库中的类,避免恶意代码的潜在危险。
    缺点:
    由于加载范围的限制,顶层的类加载器无法访问底层的类加载器所加载的类。
2. 打破双亲委派机制
  • 自定义类加载器,必须重写findClass和loadClass
  • 通过线程上下文类加载器的传递性,让父类加载器中调用子类加载器的加载动作。

运行时数据区

  • 堆是jvm所管理的内存中最大的一块区域,是被所有线程共享的一块内存区域。在虚拟机启动时候创建,此内存区域是用来存放类的对象。
  • 堆是垃圾收集器管理的主要区域,因此也被称为"GC堆"。
  • 当堆中没用内存可以完成对象分配,并且堆也无法拓展时,就会抛出OOM(OutOfMemeoryError)异常。
  • 堆中年轻代和老年代的比例是1:2。就是说年轻代占内存1/3,老年代占内存的2/3。
  • 年轻代中伊甸园:s0:s1的比例是8:1:1
  • 虚拟机栈 :栈帧是虚拟机的单位,一个方法(指的是java中的方法)一个栈帧。栈帧包括:局部变量表,操作数栈,动态链接,方法出口
    • 局部变量表:是用来存储各种基本类型,对象引用地址,returnAddress类型(returnAddress中保存的是return后要执行的字节码的指令地址)。(如果变量表中的是对象,这个对象是存在堆里的,局部变量表中存放的只是对象在堆中的地址。)
    • 操作数栈:变量操作的中间容器。一般是用来计算的,将计算后的结果放入局部变量表中去。
    • 动态链接:是存储别的方法连接的地方,比如在A方法中调用了B方法,需要连接到B方法,这就是动态链接,存储链接的地方。
    • 方法出口:当一个方法执行的时候,只有两种可以退出方法的办法。正常情况下就是return,不正确的话就是异常抛出。方法退出的时候相当于把栈帧出栈。
  • 本地方法栈
    • 一般很少用到,用native标记的方法,如果调用了本地方法,本地方法是在本地方法栈的。native修饰的大部分源码都是C和C++
方法区(jdk1.8之后叫元空间)
  1. 方法区是所有线程共享的内存区域,存放常量,静态变量,类信息。如果静态变量是对象的话,则对象在堆中存储,方法区中的对象类型的变量存放的是堆中的地址。
  2. 有个别名叫Non-Heap(非堆)。当方法区无法满足内存分配需求时,会抛出OOM(OutOfMemoryError)异常
程序计数器
  1. 程序计数器是一块较小的内存空间,它可以看作是:保存当前线程所正在执行的字节码指令的地址(行号)
  2. 由于jvm的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,一个处理器都只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都有一个独立的程序计数器,各线程之间计数器互不影响,独立存储。程序计数器内存区域是虚拟机中唯一没有规定OutOfMemoryError情况的区域。
    eg:线程A执行任务中,此时线程B抢夺了A的时间片,此时线程A就会挂起,等B执行完毕,A执行。此时就会有一个问题,A是继续执行的,那么它如何知道自己上次执行到哪个指令了呢?这时程序计数器的作用就体现出来了,它保存了线程执行的字节码指令的行号,A就可以按照这个行号继续执行下去了。

字节码执行引擎

字节码执行引擎:是虚拟机的核心组件,负责执行虚拟机的字节码,一般会先编译成机器码然后再进行执行。执行的字节码文件是在方法区的。

垃圾回收

垃圾回收

在jdk1.8,永久代的概念被移除了。

  1. 程序在运行过程中,会产生大量的内存垃圾,为了确保程序运行时的性能,JVM在程序运行过程中会不断的进行垃圾回收。
  2. 没有引用指向的内存对象都属于内存垃圾
  3. 垃圾回收是字节码执行引擎做的
  4. Minor GC是指年轻代的回收。(下文中的第一次第二次gc是相对于某个对象来说的,即对象经历的第一次gc,第二次gc,第三次gc)
    a. 年轻代。无论什么时候,s0和s1总有一块是空闲状态
    ⅰ. 伊甸园区 : 没有被标记为垃圾的对象会从伊甸园移入s0区域,经历一次回收,年龄会+1。
    ⅱ. s0区 : 在s0区中没有被标记为垃圾的对象会从s0区域移入s1区域,经历一次回收,年龄会再次+1
    ⅲ. s1区:没有标记为垃圾的对象,会从s1区重新移入s0区,年龄再次+1
    b. 老年代。Major GC是指老年代的回收
    ⅰ. 老年代,年龄大于默认值15的对象会被放入老年代。对象的大小超过s0或者s1区的50%的时候,会直接放入老年代,这个时候不考虑年龄。
    ⅱ. 老年代满,会触发full gc
  5. Minor GC的触发条件。(一般情况)
    a. eden区满。
    b. 新创建对象的大小>剩余空间大小
  6. Major GC的触发条件通常是跟full GC是等价的
    a. 晋升到老年代的对象的大小>老年代剩余空间
    b.
  7. 垃圾回收必定会出现STW(stop-the-world)。
    a. 调优的目的是减少STW就是gc的停顿时间。
    b. 为了不让回收垃圾对象的状态变换(已经标记为非垃圾的对象,由于没有STW,导致系统方法调用完成,由非垃圾变成了垃圾),这就是为什么会有STW.

判断对象是否存活的算法

1. 引用计数法

引用计数法:当对象在创建的时候,就给对象绑定一个计数器。每当有一个引用指向这个对象的时候,计数器+1;每当又一个指向它的引用被删除时,计数器减一。当没有引用指向该对象时,计数器为0。说明该对象已经死亡。
优点:实现简单,判定效率高
缺点:对象之间相互循环引用的问题难以解决,检测不到环的存在。

2. 可达性分析法

可达性分析法:又叫做根搜索算法,通过GC Roots的对象作为起点,从上往下搜索。搜索走过的路径称为引用链,当发现某个对象与GC Roots之间没有任何引用链相连,就认为该对象不可达,该对象就是垃圾对象。大多数虚拟机都是采用的这种算法。
GC Roots的对象

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

优点:解决了循环引用的问题
缺点:多线程场景下,其他线程可能会更新已经访问过的对象的引用

java中引用的四种类型:
  1. 强引用:是指在程序代码中普遍存在的引用赋值。如Object obj = new Object(),这类引用就是强引用,只要强引用还存在,垃圾收集器就不会回收被引用的对象。
  2. 软引用:用来描述一些可能还有用,但是并非必须的对象,当系统内存不够用时候,这类应用关联的对象会被垃圾收集器回收。SoftReference可以实现软引用
  3. 弱引用:也是用来描述非必须的对象的,但是它的强度比软引用还要弱,被弱引用关联的对象,只能生存到下一次垃圾收集发生在之前。当垃圾收集器工作的时候,不论空间是否足够,弱引用关联的对象都会被回收。WeakReference类可以实现弱引用
  4. 虚引用:是最弱的一种引用关系,完全不会对其生存对象构成影响,也无法通过虚引用来取得一个对象实例。如果一个对象仅持有虚引用,它和没有引用几乎是一样的,随时可能被垃圾回收器回收。

垃圾回收机制策略(GC的算法)

1. 引用计数算法

引用计数法:当对象在创建的时候,就给对象绑定一个计数器。每当有一个引用指向这个对象的时候,计数器+1;每当又一个指向它的引用被删除时,计数器减一。当没有引用指向该对象时,计数器为0,说明对象死亡。
优点:实现简单,判定效率高
缺点:对象之间相互循环引用的问题难以解决,检测不到环的存在。

2. 标记-清除算法

标记-清除算法:为每个对象存储一个标记位,记录对象的状态(存活|死亡)。标记-清除算法有两个阶段,一个是标记阶段,这个阶段内,为每个对象更新标记位,检查对象是否死亡;第二个阶段是清除阶段,该阶段对死亡的对象进行清除,执行垃圾回收操作。
优点

  1. 可以解决循环引用的问题
  2. 必要时才回收

缺点
1. 回收时,应用需要挂起。(STW)
2. 标记和清除的效率不高,特别是需要扫描的对象比较多的时候
3. 会造成内存碎片,有空间,但是都是碎的,没办法存储大一点的对象
应用场景:一般用在老年代,因为老年代对象的生命周期比较长。

3.标记-整理算法

标记-整理算法:标记整理算法和标记清除算法非常相似,标记整理算法在标记清除法的基础上解决了内存碎片化的问题。在标记阶段,该算法将所有的对象标记为存活或者死亡状态。不同的是,第二阶段,标记整理算法不会直接对死亡对象进行清理,而是将所有存活的对象整理一下,放到另一个空间,然后把剩余的全部清除。
优点
解决了内存碎片化的问题
缺点
在压缩阶段,由于移动了可用对象,需要区更新引用
应用场景:一般用在老年代,因为老年代对象的生命周期比较长。

4.复制算法

该算法将内存分成两个部分,每次只使用其中一部分,当这部分内存满的时候,将内存中所有存活的对象复制到另一个内存中,然后将之前的内存清空,只使用这部分内存,循环下去。
优点
存活对象不多的情况下,性能高,能解决内存碎片和引用更新的问题。
缺点

  1. 会造成一部分的内存浪费。
  2. 存活对象的数量比较大的时候,性能比较差。
    应用场景:一般用在新生代中,因为新生代的对象存活时间短,存活对象的数量少。

垃圾收集器

垃圾收集器:是垃圾回收算法的具体实现。
七种不同的分代收集器

  1. Serial
  2. ParNew
  3. Parallel Scavenge
  4. CMS
  5. Serial Old
  6. Parallerl Old
  7. G1
    按照收集范围进行划分:
  8. 新生代收集器:Senrial、ParNew、Parallel Scavenge
  9. 老年代收集器:CMS、Serial Old、Parallel Old
  10. 整堆收集器:G1
    其中Parallel Old不能和Senrial、ParNew搭配使用,Parallel Scavenge不能和CMS搭配使用。
1. Serial

Serial收集器:它是收集新生代的一个单线程收集器,它的单线程的意义不仅是它只会使用一个CPU或者一条收集线程去完成垃圾收集工作,更重要的是它进行垃圾收集的时,必须暂停其他所有的工作线程,直到它收集结束。
特点

  1. 它是新生代收集器,采用的算法是复制算法。
  2. 它是单线程收集器,垃圾回收时,其他所有的线程都将停止工作。
  3. 简单高效,适合单cpu环境。由于单线程没有线程交互的开销,因此它拥有很高的单线程收集效率。
2. ParNew

ParNew收集器:它是一个新生代的收集器,是Serial收集器的多线程版本。除了Serial收集器外,只有他能与CMS收集器配合工作。
特点:

  1. 它是新生代收集器,采用的算法是复制算法。
  2. 除了多线程这一特点外,其他的行为,特点与Serial收集器一样。
  3. 除了Serial外,只有他能与CMS收集器配合工作。
3. Parallel Scavenge

Parallel Scavenge收集器:它是一个新生代的收集器,该收集器更关注吞吐量,尽快的完成计算任务。适合后台运算不需要太多交互的任务。
吞吐量:吞吐量就是CPU用于运行用户代码的时间和CPU消耗时间的比值,吞吐量=运行用户代码的时间/(运行用户代码的时间+垃圾收集时间)
特点

  1. 它是新生代收集器,采用的算法是复制算法。
  2. 多线程收集器
  3. 它以高吞吐量为目标,减少垃圾收集的时间,让用户代码获得更长的运行时间。
4. Serial Old

Serial Old 收集器:Serial的老年代版本,采用的算法是标记整理法。
特点:

  1. 老年代收集器,采用标记整理算法
  2. 单线程收集器
5. Parallel Old

Parallel Old 收集器:它是一个老年代收集器,是Parallel Scavenge的老年代版本,使用标记整理算法。
特点

  1. 它是老年代收集器,采用标记整理算法。
  2. 多线程收集
6. CMS

CMS收集器:老年代收集器,以获取最短回收停顿时间为目标的收集器,适用于互联网站或者B/S系统的服务端上。尽管他采用的是并发回收,但是在其初始化标记和再次标记这两个阶段,仍然会有STW机制,不过暂停时间不会太长,因此可以说目前所有的垃圾收集器都做不到完全不需要STW,只是尽可能缩短罢了。
特点

  1. 它是老年代收集器,采用标记清除算法
  2. 以获取最短停顿时间为目标
  3. 并发收集,低停顿
  4. 垃圾收集线程与用户线程基本上可以同时工作

缺点
1. 由于采用的是标记清除法,所以会产生内存碎片
2. 对CPU资源非常敏感,它虽然不会导致用户线程停顿,但是会因为占用了一部分线程,导致应用程序变慢,总吞吐量降低
3. 无法处理浮动垃圾。在并发标记阶段如果产生新的垃圾对象,CMS无法对这些垃圾对象进行标记,最终会导致新产生的垃圾无法及时回收,只能等待下一次回收时候回收。

7. G1

G1 收集器:G1收集器是一款面向服务端应用的垃圾收集器,G1能充分利用多CPU、多核环境下的硬件优势,使用多个CPU来缩短STW停顿时间(G1收集器可以通过并发的方式让Java程序继续执行)。G1收集器仍然有分代的概念,它本身能够采用不同的方式去处理新创建的对象和存活一段时间、经过多次GC的对象,因此它不需要其他收集器的配合就能独立管理整个GC堆。G1从整体上看是基于标记整理算法实现的收集器,从局部来看(两个region之间)是基于复制算法来实现的。这样来看,G1在运行期间不会产生内存碎片,收集后能够提供规整的可用内存。最重要的是G1可预测停顿时间,它能简历可预测的停顿时间模型,能让使用者明确指定一个长度为M毫秒的时间片段内,消耗在GC上的时间不得超过N毫秒,这几乎已经是实时Java(RTSJ)的垃圾收集器的特征了。
JAVA RTSJ: JAVA的实时规范。
特点

  1. 能够充分利用多CPU、多核环境下的硬件优势。
  2. 可以通过并行来缩短STW停顿时间。
  3. 可以并发让垃圾回收和用户程序同时进行
  4. 分代收集,收集范围贯穿新生代和老年代,独立管理整个GC堆
  5. 应用场景可以面向服务端,针对具有大内存、多处理器的机器。
  6. 采用复制算法+标记整理法来回收垃圾
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值