JVM校招面试

JVM内存模型:(能不能介绍一下 jvm内存模型)

在这里插入图片描述

jvm的内存主要分为两大部分:一部分线程私有的,一部分线程共享的:
首先线程私有的有三块:分别是 jvm栈,本地方法栈,程序计数器:
jvm栈:栈存储的是基本数据类型和堆中对象的引用;每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。栈帧又由局部变量表,操作数栈,和一个指向常量池的指针组成。如此,来解决程序如何运行的问题。
本地方法栈:类似于jvm栈来实现ava代码的运行,本地方法栈实现native方法的运行。
程序计数器:可看作一个字节码的行号指示器。标识当前字节码执行到哪一行。

线程共享的有两块:
堆(Heap): 堆是所有线程共享的,主要是存放对象实例和数组。处于物理上不连续的内存空间,只要逻辑连续即可。

堆的进一步划分
堆被划分成两个不同的区域:新生代 ( Young )、老年代 ( Old )。新生代 ( Young ) 又被划分为三个区域:Eden、From Survivor(S0)、To Survivor(S1)在这里插入图片描述

常用参数
1.-Xmx -Xms 这个就表示设置堆内存的最大值和最小值。这个设置了最大值和最小值后,jvm启动后,并不会直接让堆内存就扩大到指定的最大数值。而是会先开辟指定的最小堆内存,如果经过数次GC后,还不能,满足程序的运行,才会逐渐的扩容堆的大小,但也不是直接扩大到最大内存。

2.-Xmn 设置新生代的内存大小。

3.-XX:NewRatio 新生代和老年代的比例。比如:1:4,就是新生代占五分之一。

4.-XX:PermSize -XX:MaxPermSize 设置永久区的内存大小和最大值。永久区内存用光也会导致OOM的发生。

5.-Xss 设置栈的大小。栈都是每个线程独有一个,所有一般都是几百k的大小。

方法区:存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据

注:方法区的引申问题:

第一:方法区,常量池,永久代,元空间之间的关系
方法区:方法区是jvm的规范,不是具体实现
永久代:PermGen space 是 JDK7及之前, HotSpot 虚拟机 对 方法区 的一个落地实现。在JDK8被移除
元空间:Metaspace(元空间)是 JDK8及之后, HotSpot 虚拟机 对 方法区 的新的实现
第二:元空间和永久代的区别?
1 元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存
2 永久代物理是是堆的一部分,和新生代,老年代地址是连续的,而元空间属于本地内存
3 元空间存储类的元信息,静态变量和常量池等并入堆中。
第三:为什么要这样?
1) 字符串存在永久代中(1.6),容易出现性能问题和内存溢出。
2)类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。
3)Oracle 可能会将HotSpot 与 JRockit (JRockit没有永久代)合二为一。

对象

1 创建对象的方式
在这里插入图片描述
2 创建对象的流程

(1)去常量池找符号引用,检查符号引用代表的类类是否加载:
(2)为对象分配内存
(3)初始化(赋初值)
(4)设置对象的对象头
(5)成员变量初始化

3 Java中的对象一定再堆上分配内存吗?
在这里插入图片描述

前面我们说过,Java堆中主要保存了对象实例,但是,随着JIT编译期的发展与逃逸分析技术逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙的变化,所有的对象都分配到堆上也渐渐变得不那么“绝对”了。
其实,在编译期间,JIT会对代码做很多优化。其中有一部分优化的目的就是减少内存堆分配压力,其中一种重要的技术叫做逃逸分析。
如果JIT经过逃逸分析,发现有些对象没有逃逸出方法,那么有可能堆内存分配会被优化成栈内存分配。

4 什么样的对象进入老年代?

在这里插入图片描述
5 对象的内存布局
在这里插入图片描述

其中MarkWord(32位如下,64位double)
在这里插入图片描述

64位机new Object 16个字节。对象的属性存储也会换位以保证padding

JIT(编译中的优化:浅析)

不是所有的对象都分配在堆上: 使用逃逸分析,编译器会做优化,优化方法主要有:
1 栈上分配:如果一个对象,经过分析,没有逃逸出方法,此时,对象不会分配到堆上,而是在栈上分配,这样的好处是可以减少堆空间的占用,减少FullGC
2 同步省略(锁消除):如果一个加锁的对象或者代码块被发现只能从一个线程被访问到,那么对于这个对象的操作可以不同步,消除锁的判断 3
3 标量替换:标量是指一个无法再分解成更小的数据的数据。java的标量就是原始数据类型。相对的,还可以分解的数据叫做聚合类。如果JIT阶段,经过逃逸分析,发现一个对象不会被外界访问,就会把这个对象拆解成若干个其中包含的若干个成员变量来代替,这个过程叫标量替换。
比如一个Person对象,有两个属性,int x,inty,Person经过分析无法逃逸,此时不会分配Person的内存,只会分配x和y的内存。

https://juejin.cn/post/6844903639308304397

垃圾回收:

1 哪些对象需要回收

1强软弱虚四种引用
强引用:程序代码中,普遍存在,只要强引用存在,垃圾回收器宁可OOM异常永远不会回收这些对象。
软引用:软引用关联的对象,在系统要发生OOM的时候,先把这些对象放到回收范围,回收其他对象,如果回收其他对象之后不会再OOM,软引用对象不会被回收,否则会被回收
弱引用:弱引用关联的对象只可以生存到下一次垃圾收集发生之前,垃圾收集器工作,无论当前内存是否足够,都只会回收掉被弱引用关联的对象
虚引用:一个对象是否具有虚引用,完全不会对其生存时间构成影响,也无法通过虚引用来取得对象实例。它得作用是能在这个对象被回收时收到一个系统通知(貌似可以监测native得对象回收)。

2 垃圾回收算法
引用计数法 缺点:循环引用
可达性分析法

可达性分析法 用GC ROOT看哪些对象可达,可作为GC ROOT的对象有:
(1) 虚拟机栈中引用的对象(本地变量表)
(2)方法区中静态属性引用的对象
(3) 方法区中常量引用的对象
(4) 本地方法栈中引用的对象

对象两次不可达才被回收,流程图如下

在这里插入图片描述

垃圾回收算法:
标记-清除

标记-清除算法采用从根集合进行扫描,对存活的对象对象标记,标记完毕后,再扫描整个空间中未被标记的对象,进行回收,如上图所示。标记-清除算法不需要进行对象的移动,并且仅对不存活的对象进行处理,在存活对象比较多的情况下极为高效(因为不需要移动,往往存活的对象越多,需要的移动越多),但由于标记-清除算法直接回收不存活的对象,因此会造成内存碎片。

复制算法

建立在存活对象少(因为每次复制的),垃圾对象多的前提下。此算法每次只处理正在使用中的对象,因此复制成本比较小,同时复制过去后还能进行相应的内存整理,不会出现碎片问题。但缺点也是很明显,就是需要两倍内存空间。

标记整理

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

分代回收策略(为什么JVM分成老年代和新生代)

新生代由于其对象存活时间短,且需要经常gc,因此采用效率较高的复制算法,其将内存区分为一个eden区和两个suvivor区,默认eden区和survivor区的比例是8:1,分配内存时先分配eden区,当eden区满时,使用复制算法进行gc,将存活对象复制到一个survivor区,当一个survivor区满时,将其存活对象复制到另一个区中,当对象存活时间大于某一阈值时,将其放入老年代。老年代和永久代因为其存活对象时间长,因此使用标记清除或标记整理算法

**

垃圾回收器

**
在这里插入图片描述
搭配整理:
在这里插入图片描述
单线程的垃圾收集器:

(单线程没有吞吐量还是响应优先的说法,因为吞吐量降低的原因是要保留线程做gc)

1. Serial(-XX:+UseSerialGC)

Serial 回收器是最基本的 新生代 垃圾回收器,是 单线程 的垃圾回收器。由于垃圾清理时,Serial 回收器 不存在线程间的切换,因此,特别是在单 CPU 的环境下,它的 垃圾清除效率 比较高。对于 Client 运行模式的程序,选择 Serial回收器是一个不错的选择。 Serial 新生代回收器 采用的是 复制算法

2 Serial Old 回收器

Serial 回收器的 老年代版本,属于 单线程回收器,它使用 标记-整理 算法。对于 Server
模式下的虚拟机,在 JDK1.5 及其以前,它常与 Parallel Scavenge 回收器配合使用,达到较好的
吞吐量
,另外它也是 CMS 回收器在 Concurrent Mode Failure 时的 后备方案。使用的是标记整理算法

多线程的垃圾收集器(响应速度优先)
1 ParNew(-XX:+UseParNewGC)(不确定哪一种优先,不问就不说了,问的话就说效率优先)

ParNew 回收器是在 Serial 回收器的基础上演化而来的,属于 Serial 回收器的 多线程版本,同样运行在
新生代区域。在实现上,两者共用很多代码。在不同运行环境下,根据 CPU 核数,开启 不同的线程数,从而达到 最优 的垃圾回收效果。对于那些 Server 模式的应用程序,如果考虑采用 CMS 作为 老生代回收器 时,ParNew回收器是一个不错的选择。使用标记复制算法

在这里插入图片描述
2 CMS(标记清除,老年代,相应速度优先(最短的STW))

CMS(Concurrent Mark Sweep) 回收器是在 最短回收停顿时间 为前提的回收器,属于 多线程回收器,采用标记-清除!!!算法。
在这里插入图片描述

CMS过程:
初始标记(CMS initial mark)(STW)
标记 GC Roots 内 直接关联 的对象。这个阶段 速度很快,需要 Stop the World。
并发标记(CMS concurrent mark)(不STW)
从 GC Roots 开始对堆进行 可达性分析,找出 存活对象。时间最长
重新标记(STW)
修正 并发期间由于 用户进行运作 导致的 标记变动 的那一部分对象的 标记记录。这个阶段的 停顿时间 一般会比 初始标记阶段 稍长一些,但远比 并发标记 的时间短,也需要 Stop The World。
并发清除(CMS concurrent sweep)(STW)
并发清除 阶段会清除垃圾对象。由于此时不STW,所以还会有在清理的过程中还有新垃圾产生

CMS缺点:

1 CMS回收器对CPU资源非常依赖

CMS 回收器过分依赖于 多线程环境,默认情况下,开启的 线程数 为(CPU 的数量 + 3)/ 4,当 CPU 数量少于 4 个时,CMS对 用户查询 的影响将会很大,因为他们要分出一半的运算能力去 执行回收器线程;如果CPU核心很少,那吞吐量就很低了。

2 CMS回收器无法清除浮动垃圾(Concurrent Mode Failure)
由于 CMS 回收器 清除已标记的垃圾 (处于最后一个阶段)时,用户线程 还在运行,因此会有新的垃圾产生。但是这部分垃圾 未被标记,在下一次 GC 才能清除,因此被成为 浮动垃圾。当 老生代 中的内存使用超过一定的比例时,系统将会进行 垃圾回收;当 剩余内存 不能满足程序运行要求时,系统将会出现 Concurrent Mode Failure,临时采用 Serial Old 算法进行 清除,此时的 性能 将会降低。

3 垃圾收集结束后残余大量空间碎片 CMS 回收器采用的 标记清除算法,本身存在垃圾收集结束后残余 大量空间碎片 的缺点。CMS 配合适当的 内存整理策略(用参数开启带内存整理的gc,就是先整理一下再GC),在一定程度上可以解决这个问题。

多线程的垃圾收集器(吞吐量优先)

1 ParallelScavenge

主要工作在Server模式复制算法新生代多线程关注吞吐量多线程
其他与Parnew的主要不同 1 可利用参数精确控制吞吐量。 2
自适应调节(设置好想要的吞吐量,和-xmx设置最大堆,不用设置其他参数,它就能自适应的调节其他参数达到想要的吞吐量。)

2 Parallel Old

Parallel Old 回收器是 Parallel Scavenge 回收器的 老生代版本,属于 多线程回收器,采用 标记-整理算法。Parallel Old 回收器和 Parallel Scavenge 回收器同样考虑了 吞吐量优先 这一指标,非常适合那些 注重吞吐量 和 CPU 资源敏感 的场合。

其他: G1回收器(垃圾区域Region优先,1.7投入使用)同时用于新生代和老年代。

使用的算法:(不问别说,细节还没有研究)

G1 从 整体 来看是基于 标记—整理 算法实现的回收器。从 局部(两个 Region 之间)上来看是基于 复制算法 实现的。

G1 首先将 堆 分为 大小相等 的 Region,避免 全区域 的垃圾回收。然后追踪每个 Region 垃圾 堆积的价值大小,在后台维护一个 优先列表,根据允许的回收时间优先回收价值最大的 Region。
在这里插入图片描述
单独解释图中H:大对象区域,直接分配老年代,所谓大对象就是超过Region一半大小的对象

工作过程:

初始标记(CMS initial mark)(STW) 初始标记 仅仅是标记 GC Roots 内 直接关联 的对象。这个阶段 速度很快,需要 Stop the World。
并发标记(CMS concurrent mark)(不STW) 并发标记 进行的是 GC Tracing,从 GC Roots 开始对堆进行 可达性分析,找出 存活对象。
重新标记(CMS remark)(STW) 重新标记 阶段为了 修正 并发期间由于 用户进行运作 导致的 标记变动 的那一部分对象的 标记记录。这个阶段的 停顿时间 一般会比 初始标记阶段 稍长一些,但远比 并发标记 的时间短,也需要 Stop The World。
筛选回收(与CMS过程中的主要不同) 首先对各个 Region 的 回收价值 和 成本 进行排序,根据用户所期望的 GC 停顿时间 来制定回收计划。这个阶段可以与用户程序一起 并发执行,但是因为只回收一部分 Region,时间是用户可控制的,而且停顿 用户线程将大幅提高回收效率。

GC的特点:(优势)
并行与并发

使用多个 CPU 来缩短 Stop-the-World 的 停顿时间,部分其他回收器需要停顿 Java 线程执行的 GC 动作,G1
回收器仍然可以通过 并发的方式 让 Java 程序继续执行。

分代回收

与其他回收器一样,分代概念 在 G1 中依然得以保留。虽然 G1 可以不需要 其他回收器配合 就能独立管理 整个GC堆,但它能够采用不同的策略 去处理 新创建的对象 和 已经存活 一段时间、熬过多次 GC 的旧对象,以获取更好的回收效果。新生代 和 老年代 不再是 物理隔离,是多个 大小相等 的独立 Region。

空间整合

G1 运作期间 不会产生内存空间碎片,回收后能提供规整的可用内存。这种特性有利于程序长时间运行,分配大对象 时不会因为无法找到 连续内存空间而提前触发 下一次 GC。

可预测的停顿

降低停顿时间 是 G1 和 CMS 共同的关注点。G1 除了追求 低停顿 外,还能建立 可预测 的
停顿时间模型,能让使用者明确指定在一个 长度 为 M 毫秒的 时间片段 内,消耗在 垃圾回收 上的时间不得超过 N 毫秒。(后台维护的
优先列表,优先回收 价值大 的 Region)。

GC的执行机制

GC的类型:

Minor GC: Scavenge GC(Minor GC): 一般情况下,当新对象生成(age=0),并且在Eden申请空间失败时,就会触发ScavengeGC,对Eden区域进行GC,清除非存活对象,并且把尚且存活的对象移动到Survivor区(age+1)。然后整理(其实是复制过去就顺便整理了)Survivor的两个区。这种方式的GC是对年轻代的Eden区进行,不会影响到年老代。因为大部分对象都是从Eden区开始的,同时Eden区不会分配的很大,所以Eden区的GC会频繁进行。因而,一般在这里需要使用速度快、效率高的算法(即复制-清理算法),使Eden去能尽快空闲出来。Java 中的大部分对象通常不需长久存活,具有朝生夕灭的性质。

Full GC:

对整个堆进行整理,包括Young、Tenured和Perm。Full GC因为需要对整个对进行回收,所以比ScavengeGC要慢,因此应该尽可能减少Full GC的次数。在对JVM调优的过程中,很大一部分工作就是对于FullGC的调节

FullGC触发条件
在这里插入图片描述
YoungGC触发条件
在这里插入图片描述
新生对象回收流程

1 对象创建时,一般在Eden区完成内存分配(有特殊); 2
当Eden区满了,再创建对象,会因为申请不到空间,触发minorGC,进行young(eden+1survivor)区的垃圾回收; 3
minorGC时,Eden和survivor A不能被GC回收且年龄没有达到阈值(tenuring
threshold)的对象,会被放入survivor B,始终保证一个survivor是空的; 4
当做第3步的时候,如果发现survivor满了,将这些对象copy到old区(分配担保机制);或者survivor并没有满,但是有些对象已经足够Old,也被放入Old区
XX:MaxTenuringThreshold;(回顾下对象进入老年代的情况) 5 直接清空eden和survivor A; 6
当Old区被放满的之后,进行fullGC。

减少GC开销的措施
在这里插入图片描述
在这里插入图片描述

OOM的情况有哪些
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值