面试必问---jvm入门

大家都说java是一个能够跨平台的语言,就是写的java代码,能在window、mac、linux里面运行,那个这个java到底是怎么跨平台的?

在这里插入图片描述

现在我们写了一个java代码,后缀是.java,编译成.class文件,
但是并不是机器可以识别的语言,而是字节码,最终还是需要 jvm的解释,才能在各个平台执行,
这同时也是java跨平台的原因。

jvm:jvm在整个jdk中处于最底层,负责于操作系统的交互,用来屏蔽操作系统环境,提供一个完整的java运行环境

在这里插入图片描述

类加载器:

1.自定义加载器

  1. 继承java.lang.ClassLoader
  2. 重写父类的findClass方法
  3. 重写父类的loadClass方法(破坏双亲委托机制)

2.启动类(根)加载器 bootstrap

3.扩展加载器 /jre/lib/ext Extension

4.应用程序加载器 /jre/lib/rt Application

双亲委派机制(安全): app–>ext—>boot

类加载器收到类加载的请求  
将这个请求向上委托给父类加载器,一直向上委托,直到根加载器
根加载器检查是否能够加载该类,能加载就结束,如果找不到,就抛出异常,通知子类加载器加载
重复3步骤
https://zhuanlan.zhihu.com/p/31182000   双亲委派机制详细过程

https://blog.youkuaiyun.com/weixin_42262357/article/details/84031196 具体可以了解类加载过程

1)、方法区
方法区是JVM 所有线程共享。
主要用于存储类的信息、常量池、方法数据、方法代码等。方法区逻辑上属于堆的一部分,但是为了与 进行区分,通常又叫 非堆

JDK6、JDK7 时,方法区 就是 PermGen(永久代),在虚拟机里。
JDK8 时,方法区就是 Metaspace(元空间),在本地内存中

2)、堆

堆内存也是 JVM 所有线程共享的部分,在虚拟机启动的时候就已经创建。所有的对象和数组都在堆上进行分配。这部分空间可通过 GC 进行回收。当申请不到空间时会抛出 OutOfMemoryError 。

3)、栈

每个线程包含一个栈区,栈中只保存方法中(不包括对象的成员变量)的基础数据类型和自定义对象的引用(不是对象),对象都存放在堆区中每个栈中的数据(原始类型和对象引用)都是私有的,其他栈不能访问,当一段代码或者一个方法调用完毕后,栈中为这段代码所提供的基本数据类型或者对象的引用立即被释放。栈分为3个部分:基本类型变量区、执行环境上下文、操作指令区(存放操作指令)

GC:Garbage Collection 垃圾收集。这里所谓的垃圾指的是在系统运行过程当中所产生的一些无用的对象,这些对象占据着一定的内存空间,如果长期不被释放,可能导致OOM

哪些内存需要回收?

什么时候回收?

如何回收?

内存区域中的程序计数器、虚拟机栈、本地方法栈这3个区域随着线程而生,线程而灭;栈中的栈帧随着方法的进入和退出而有条不紊地执行着出栈和入栈的操作,每个栈帧中分配多少内存基本是在类结构确定下来时就已知的。在这几个区域不需要过多考虑回收的问题,因为方法结束或者线程结束时,内存自然就跟着回收了

Java堆和方法区则不同,一个接口中的多个实现类需要的内存可能不同,一个方法中的多个分支需要的内存也可能不一样,我们只有在程序处于运行期间时才能知道会创建哪些对象,这部分内存的分配和回收都是动态的,GC关注的也是这部分内存,后面的文章中如果涉及到“内存”分配与回收也仅指着一部分内存。

GC算法:

1、引用计数法:

概念:给对象添加一个引用计数器,每当有一个地方引用它时,计数值就加一,当引用失效时,计数器值就减一,任何时刻计数器为0的对象都是不可能在被使用

优点:引用计数收集器可以很快的执行,交织在程序运行中。对程序需要不被长时间打断的实时环境比较有利。

缺点:引用和去引用伴随加法和减法,影响性能,致命的缺陷:对于循环引用的对象无法进行回收(如父对象有一个对子对象的引用,子对象反过来引用父对象。这样,他们的引用计数永远不可能为0)

可达性算法 (下面几种算法都是基于可达性算法实现的):

现代虚拟机基本都是采用这种算法来判断对象是否存活,可达性算法的原理是以一系列叫做 GC Root 的对象为起点出发,引出它们指向的下一个节点,再以下个节点为起点,引出此节点指向的下一个结点。。。(这样通过 GC Root 串成的一条线就叫引用链),直到所有的结点都遍历完毕,如果相关对象不在任意一个以 GC Root 为起点的引用链中,则这些对象会被判断为「垃圾」,会被 GC 回收。
在这里插入图片描述

如图示,如果用可达性算法即可解决上述循环引用的问题,因为从GC Root 出发没有到达 a,b,所以 a,b 可回收

a, b 对象可回收,就一定会被回收吗?并不是,对象的 finalize 方法给了对象一次垂死挣扎的机会,当对象不可达(可回收)时,当发生GC时,会先判断对象是否执行了 finalize 方法,如果未执行,则会先执行 finalize 方法,我们可以在此方法里将当前对象与 GC Roots 关联,这样执行 finalize 方法之后,GC 会再次判断对象是否可达,如果不可达,则会被回收,如果可达,则不回收!

注意: finalize 方法只会被执行一次,如果第一次执行 finalize 方法此对象变成了可达确实不会回收,但如果对象再次被 GC,则会忽略 finalize 方法,对象会被回收!这一点切记!

那么这些 GC Roots 到底是什么东西呢,哪些对象可以作为 GC Root 呢,有以下几类

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中 JNI(即一般说的 Native 方法)引用的对象

2、标记清除法(Mark-Sweep 老年代使用)

最基础的垃圾回收算法,分为两个阶段,标注和清除。标记阶段标记出所有需要回收的对象,清除阶段回收被标记的对象所占用的空间。如图
在这里插入图片描述

缺点:内存碎片化严重,后面可能导致大对象不能找到可利用空间的问题

3、标记整理法(老年代的使用)(Mark-Compact)

结合了复制内存法和标记清除法两个算法,为了避免缺陷而提出。标记阶段和 Mark-Sweep 算法相同, 标记后不是清理对象,而是将存活对象移向内存的一端。然后清除端边界外的对象。如图:

在这里插入图片描述

4、复制内存法 (新生代的使用)

为了解决 Mark-Sweep 算法内存碎片化的缺陷而被提出的算法。按内存容量将内存划分为等大小的两块。每次只使用其中一块,当这一块内存满后将尚存活的对象复制到另一块上去,把已使用的内存清掉
在这里插入图片描述

(参考网址)
https://blog.youkuaiyun.com/github_34457546/article/details/81739971?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522159909557219724835840966%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=159909557219724835840966&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2alltop_click~default-2-81739971.first_rank_ecpm_v3_pc_rank_v4&utm_term=gc%E7%AE%97%E6%B3%95&spm=1018.2118.3001.4187

分代收集算法

分代收集法是目前大部分 JVM 所采用的方法,其核心思想是根据对象存活的不同生命周期将内存划分为不同的域,一般情况下将 GC 堆划分为老生代(Tenured/Old Generation)和新生代(Young Generation)。老生代的特点是每次垃圾回收时只有少量对象需要被回收,新生代的特点是每次垃圾回收时都有大量垃圾需要被回收,因此可以根据不同区域选择不同的算法。

Stop The World(STW)

是java的一种全局暂停

全部线程停止工作,除了GC线程和本地方法的线程

多半是由GC引起的 DUMP线程

gc为什么要stw?

一、比如你打扫房间,你边打扫,边丢垃圾,那是永远清理不干净的

二、保持一致性,如果不STW,那在清理的时候对象的引用关系一直在变化,那在清理对象的时候是不准确的

危害:

**长时间服务停止,没有响应(将用户正常工作的线程全部暂停掉)**遇到HA系统,可能引起主备切换,严重危害生产环境

垃圾回收器:

如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。Java 虚拟机规范并没有规定垃圾收集器应该如何实现,一般会给出参数来让用户根据应用的特点来组合各个年代使用的收集器,目前HotSpot虚拟机用到的垃圾回收器如下图所示。注意只有两个回收器之间有连线才能配合使用
在这里插入图片描述

新生代回收器:Serial 、 Parnew 、 ParallelScavenge

老年代回收器:CMS 、SerialOld 、Parallel Old

新生代老年代通用:G1回收器

新生代:

Serial:新生代默认回收器,单线程的垃圾回收器,只用一个CPU运行,在运行的时候,必须暂停其他的用户线程(STW),直到收集完成。它适合Client模式的应用,在单CPU环境下,它简单高效,由于没有线程交互的开销,专心垃圾收集自然可以获得最高的单线程效率。

Parnew:ParNew 收集器是 Serial 收集器的多线程版本,除了使用多线程,其他像收集算法,STW,对象分配规则,回收策略与 Serial 收集器完成一样。ParNew 主要工作在 Server 模式,我们知道服务端如果接收的请求多了,响应时间就很重要了,多线程可以让垃圾回收得更快,也就是减少了 STW 时间,能提升响应时间,所以是许多运行在 Server 模式下的虚拟机的首选新生代收集器,另一个与性能无关的原因是因为除了 Serial 收集器,只有它能与 CMS 收集器配合工作,CMS 是一个划时代的垃圾收集器,是真正意义上的并发收集器,它第一次实现了垃圾收集线程与用户线程(基本上)同时工作,它采用的是传统的 GC 收集器代码框架,与 Serial,ParNew 共用一套代码框架,所以能与这两者一起配合工作,而后文提到的 Parallel Scavenge 与 G1 收集器没有使用传统的 GC 收集器代码框架,而是另起炉灶独立实现的,另外一些收集器则只是共用了部分的框架代码,所以无法与 CMS 收集器一起配合工作。

在多 CPU 的情况下,由于 ParNew 的多线程回收特性,毫无疑问垃圾收集会更快,也能有效地减少 STW 的时间,提升应用的响应速度

Parallel Scavenge:Parallel Scavenge 收集器也是一个使用复制算法多线程,工作于新生代的垃圾收集器,CMS 等垃圾收集器关注的是尽可能缩短垃圾收集时用户线程的停顿时间,而 Parallel Scavenge 目标是达到一个可控制的吞吐量(吞吐量 = 运行用户代码时间 / (运行用户代码时间+垃圾收集时间)),也就是说 CMS 等垃圾收集器更适合用到与用户交互的程序,因为停顿时间越短,用户体验越好,而 Parallel Scavenge 收集器关注的是吞吐量,所以更适合做后台运算等不需要太多用户交互的任务。尽快完成程序的运算任务可以设置最大停顿时间MaxGCPauseMillis以及,吞吐量大小GCTimeRatio。如果设置了-XX:+UseAdaptiveSizePolicy参数,则随着GC,会动态调整新生代的大小,Eden,Survivor比例等,以提供最合适的停顿时间或者最大的吞吐量。用于新生代收集,复制算法。通过-XX:+UseParallelGC参数,Server模式下默认提供了其和SerialOld进行搭配的分代收集方式。

老年代:

serial old:上文我们知道, Serial 收集器是工作于新生代的单线程收集器,与之相对地,Serial Old 是工作于老年代的单线程收集器,此收集器的主要意义在于给 Client 模式下的虚拟机使用,如果在 Server 模式下,则它还有两大用途:一种是在 JDK 1.5 及之前的版本中与 Parallel Scavenge 配合使用,另一种是作为 CMS 收集器的后备预案,在并发收集发生 Concurrent Mode Failure 时使用

parallel old:Parallel Old 是相对于 Parallel Scavenge 收集器的老年代版本,使用多线程和标记整理法,两者组合示意图如下,这两者的组合由于都是多线程收集器,真正实现了「吞吐量优先」的目标

CMS收集器:

CMS 收集器是以实现最短 STW 时间为目标的收集器,如果应用很重视服务的响应速度,希望给用户最好的体验,则 CMS 收集器是个很不错的选择!

我们之前说老年代主要用标记整理法,而 CMS 虽然工作于老年代,但采用的是标记清除法,主要有以下四个步骤

  1. 初始标记
  2. 并发标记
  3. 重新标记
  4. 并发清除

img

从图中可以的看到初始标记和重新标记两个阶段会发生 STW,造成用户线程挂起,不过初始标记仅标记 GC Roots 能关联的对象,速度很快,并发标记是进行 GC Roots Tracing 的过程,重新标记是为了修正并发标记期间因用户线程继续运行而导致标记产生变动的那一部分对象的标记记录,这一阶段停顿时间一般比初始标记阶段稍长,但远比并发标记时间短

整个过程中耗时最长的是并发标记和标记清理,不过这两个阶段用户线程都可工作,所以不影响应用的正常使用,所以总体上看,可以认为 CMS 收集器的内存回收过程是与用户线程一起并发执行的。

但是 CMS 收集器远达不到完美的程度,主要有以下三个缺点

  • CMS 收集器对 CPU 资源非常敏感 原因也可以理解,比如本来我本来可以有 10 个用户线程处理请求,现在却要分出 3 个作为回收线程,吞吐量下降了30%,CMS 默认启动的回收线程数是 (CPU数量+3)/ 4, 如果 CPU 数量只有一两个,那吞吐量就直接下降 50%,显然是不可接受的
  • CMS 无法处理浮动垃圾(Floating Garbage),可能出现 「Concurrent Mode Failure」而导致另一次 Full GC 的产生,由于在并发清理阶段用户线程还在运行,所以清理的同时新的垃圾也在不断出现,这部分垃圾只能在下一次 GC 时再清理掉(即浮云垃圾),同时在垃圾收集阶段用户线程也要继续运行,就需要预留足够多的空间要确保用户线程正常执行,这就意味着 CMS 收集器不能像其他收集器一样等老年代满了再使用,JDK 1.5 默认当老年代使用了68%空间后就会被激活,当然这个比例可以通过 -XX:CMSInitiatingOccupancyFraction 来设置,但是如果设置地太高很容易导致在 CMS 运行期间预留的内存无法满足程序要求,会导致 Concurrent Mode Failure 失败,这时会启用 Serial Old 收集器来重新进行老年代的收集,而我们知道 Serial Old 收集器是单线程收集器,这样就会导致 STW 更长了。
  • CMS 采用的是标记清除法,上文我们已经提到这种方法会产生大量的内存碎片,这样会给大内存分配带来很大的麻烦,如果无法找到足够大的连续空间来分配对象,将会触发 Full GC,这会影响应用的性能。当然我们可以开启 -XX:+UseCMSCompactAtFullCollection(默认是开启的),用于在 CMS 收集器顶不住要进行 FullGC 时开启内存碎片的合并整理过程,内存整理会导致 STW,停顿时间会变长,还可以用另一个参数 -XX:CMSFullGCsBeforeCompation 用来设置执行多少次不压缩的 Full GC 后跟着带来一次带压缩的。

G1垃圾收集器

G1(Garbage First) 收集器

G1 收集器是面向服务端的垃圾收集器,被称为驾驭一切的垃圾回收器,主要有以下几个特点

  • 像 CMS 收集器一样,能与应用程序线程并发执行。

  • 整理空闲空间更快。

  • 需要 GC 停顿时间更好预测。

  • 不会像 CMS 那样牺牲大量的吞吐性能。

  • 不需要更大的 Java Heap

与 CMS 相比,它在以下两个方面表现更出色

  1. 运作期间不会产生内存碎片,G1 从整体上看采用的是标记-整理法,局部(两个 Region)上看是基于复制算法实现的,两个算法都不会产生内存碎片,收集后提供规整的可用内存,这样有利于程序的长时间运行。
  2. 在 STW 上建立了可预测的停顿时间模型,用户可以指定期望停顿时间,G1 会将停顿时间控制在用户设定的停顿时间以内。

为什么G1能建立可预测的停顿模型呢,主要原因在于 G1 对堆空间的分配与传统的垃圾收集器不一器,传统的内存分配就像我们前文所述,是连续的,分成新生代,老年代,新生代又分 Eden,S0,S1,如下

img

而 G1 各代的存储地址不是连续的,每一代都使用了 n 个不连续的大小相同的 Region,每个Region占有一块连续的虚拟内存地址,如图示

img除了和传统的新老生代,幸存区的空间区别,Region还多了一个H,它代表Humongous,这表示这些Region存储的是巨大对象(humongous object,H-obj),即大小大于等于region一半的对象,这样超大对象就直接分配到了老年代,防止了反复拷贝移动。那么 G1 分配成这样有啥好处呢?

传统的收集器如果发生 Full GC 是对整个堆进行全区域的垃圾收集,而分配成各个 Region 的话,方便 G1 跟踪各个 Region 里垃圾堆积的价值大小(回收所获得的空间大小及回收所需经验值),这样根据价值大小维护一个优先列表,根据允许的收集时间,优先收集回收价值最大的 Region,也就避免了整个老年代的回收,也就减少了 STW 造成的停顿时间。同时由于只收集部分 Region,可就做到了 STW 时间的可控。

G1 收集器的工作步骤如下

  1. 初始标记
  2. 并发标记
  3. 最终标记
  4. 筛选回收

img

可以看到整体过程与 CMS 收集器非常类似,筛选阶段会根据各个 Region 的回收价值和成本进行排序,根据用户期望的 GC 停顿时间来制定回收计划。

https://blog.youkuaiyun.com/iva_brother/article/details/87886525 具体学习

面试题:

GC 的几种主要的收集方法:标记清除、标记整理、复制算法的原理与特点,各自的优劣势

为啥会有 Serial ,CMS, G1 等各式样的回收器,各自的优劣势是什么,为啥没有一个统一的万能的垃圾回收器

新生代为啥要设置成 Eden, S0,S1 这三个区,基于什么考虑呢

堆外内存不受 GC 控制,那该怎么释放呢

对象可回收,就一定会被回收吗?

什么是 SafePoint,什么是 Stop The World

GC 日志格式怎么看

主要有哪些发生 OOM 的场景

发生 OOM,如何定位,常用的内存调试工具有哪些

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值