JVM 面试题



看谁的书

深入理解JAVA虚拟机 JVM高级特性与最佳实践 第三版 周志明

JVM架构模型

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yjdXegB0-1625744462233)(imgs/jvm架构模型.png)]

JVM 的主要组成部分及其作用

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

组件的作用: 首先通过类加载器(ClassLoader)会加载类文件到内存,Class loader只管加载,只要符合文件结构就加载。运行时数据区(Runtime Data Area)是jvm的重点,我们所有所写的程序都被加载到这里,之后才开始运行。而字节码文件只是 JVM 的一套指令集规范,并不能直接交个底层操作系统去执行,因此需要特定的命令解析器执行引擎(Execution Engine),将字节码翻译成底层系统指令,再交由 CPU 去执行,而这个过程中需要调用其他语言的本地库接口(Native Interface)来融合不同的语言为java所用,从而实现整个程序的功能。

JVM 运行时数据区⭐

包括(程序计数器,虚拟机栈,本地方法栈,堆,方法区)

程序计数器

指向当前线程执行的字节码指令的地址( 行号 )。程序计数器是一个记录着当前线程所执行的字节码的行号指示器。 用处是多线程操作时, 挂起的线程在重新激活后能够知道上次执行的位置。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SIcXXw1S-1625744462235)(imgs/pc寄存器.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fzKJFEuh-1625744462237)(imgs/pc寄存器为什么是线程私有的.png)]

虚拟机栈

存储当前线程执行方法时所需要的数据,指令,返回地址。因此一个线程独享一块虚拟机栈 ,栈中内存的单位是栈帧(一个栈帧代表一个java方法)。 每个栈帧中都拥有:局部变量表、操作数栈、动态链接、方法出口信息。(生命周期和线程一致)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Jje3K8K0-1625744462239)(imgs/栈帧的内部结构.png)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aZq8O3JX-1625744462241)(imgs/栈的优点.png)]

本地方法栈

本地方法栈为虚拟机使用到的 Native 方法(本地方法是非java语言方法)服务。

唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。 详见堆内存模型。

方法区

用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。方法区是一个 JVM 规范,永久代与元空间都是其一种实现方式。

方法区可以看做是一个独立于堆的内存空间,也叫 非堆,目的就是要和堆分开。7

与java 堆一样,是各个线程共享的内存空间

方法区垃圾回收主要回收两部分内容:常量池中的废弃的常量和不再使用的类型

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tZSOdZyg-1625744462242)(imgs/hospot虚拟机方法区的变化.png)]

1.举例栈溢出的情况?

栈溢出是StackOverFlowError,通过-Xss设置栈的大小。-Xss 为jvm启动的每个线程分配的栈内存大小

2.调整栈的大小就能保证不出现溢出吗?

不能保证,如果出现死循环或者无线递归的情况,理论上是不能保证的。也就是没有上限了。

3.分配的栈内存越大越好吗?

不是,过多胡浪费空间。浪费资源

4.垃圾回收是否会涉及到虚拟机栈

不会,出栈操作就已经算湿垃圾回收了

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xj4RGYOm-1625744462243)(imgs/堆的概念.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IA7xgOnS-1625744462244)(imgs/堆.png)]

堆内存模型:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9kdwrbyA-1625744462244)(imgs/堆空间的细分.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yiJsBa3s-1625744462245)(imgs/JVM.png)]

此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。

年轻代和老年代

默认的,新生代 ( Young ) 与老年代 ( Old ) 的比例的值为 1:2 ( 该值可以通过参数 –XX:NewRatio 来指定 )

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fPD9gJKK-1625744462246)(imgs/年轻代与老年代.png)]

为什么要把java堆分代

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1MaecsWY-1625744462247)(imgs/为什么堆要分代.png)]

TLAB(线程私有的分配缓冲区)[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HmW3NzOl-1625744462248)(imgs/TLAB.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yXpldMlx-1625744462249)(imgs/tlab概念.png)]

堆是分配对象存储的唯一选择吗?[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IrKrBs7U-1625744462250)(imgs/堆是对象存储的唯一选择吗.png)]

堆和栈的区别

  1. 栈内存存储的是局部变量而堆内存存储的是实体;
  2. 栈内存的更新速度要快于堆内存,因为局部变量的生命周期很短;
  3. 栈内存存放的变量生命周期一旦结束就会被释放,而堆内存存放的实体会被垃圾回收机制不定时的回收。、

StringTable(字符串常量池)为什么要调整

String table又称为String pool,字符串常量池,其存在于堆中(jdk1.7之后改的)。最重要的一点,String table中存储的并不是String类型的对象,存储的而是指向String对象的索引,真实对象还是存储在堆中

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nF8PZQ2j-1625744462251)(imgs/StringTable为什么调整.png)]

分代回收

HotSpot JVM把年轻代分为了三部分:1个Eden区和2个Survivor区(分别叫from和to)。一般情况下,新创建的对象都会被分配到Eden区(一些大对象特殊处理),这些对象经过第一次Minor GC后,如果仍然存活,将会被移到Survivor区。对象在Survivor区中每熬过一次Minor GC,年龄就会增加1岁,当

它的年龄增加到一定程度时,就会被移动到年老代中。

因为年轻代中的对象基本都是朝生夕死的,所以在年轻代的垃圾回收算法使用的是复制算法,复制算法的基本思想就是将内存分为两块,每次只用其中一块,当这一块内存用完,就将还活着的对象复制到另外一块上面。复制算法不会产生内存碎片。

在GC开始的时候,对象只会存在于Eden区和名为“From”的Survivor区,Survivor区“To”是空的。紧接着进行GC,Eden区中所有存活的对象都会被复制到“To”,而在“From”区中,仍存活的对象会根据他们的年龄值来决定去向。年龄达到一定值(年龄阈值,可以通过-XX:MaxTenuringThreshold来设置)的对象会被移动到年老代中,没有达到阈值的对象会被复制到“To”区域。经过这次GC后,Eden区和From区已经被清空。这个时候,“From”和“To”会交换他们的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。不管怎样,都会保证名为To的Survivor区域是空的。Minor GC会一直重复这样的过程,直到“To”区被填满,“To”区被填满之后,会将所有对象移动到年老代中。

运行时数据区常见面试题

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bZtORK1T-1625744462252)(imgs/jvm运行时数据库常见面试题总结1.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-67lDTLJ1-1625744462253)(imgs/jvm运行时数据区常见面试题2.png)]

1.jvm内存模型及各个部分作用,JMM三大特性

查看了网上说法,应该是java内存模型和jvm内存分区。查看链接https://zhuanlan.zhihu.com/p/101495810

file:///F:/java%E9%9D%A2%E8%AF%95%E9%A2%98%E6%96%87%E6%A1%A3/Java-Notes-master/docs/CyC%E6%8A%80%E6%9C%AF%E9%9D%A2%E8%AF%95%E5%BF%85%E5%A4%87%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%20CyC2018.pdf

java内存模型或者jvm内存模型

线程间通信必须要经过主内存。
Java内存模型定义了以下八种操作来完成:

lock(锁定):作用于主内存的变量,把一个变量标识为一条线程独占状态。
unlock(解锁):作用于主内存变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
read(读取):作用于主内存变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用
load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。
use(使用):作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作。
assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
store(存储):作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作。
write(写入):作用于主内存的变量,它把store操作从工作内存中一个变量的值传送到主内存的变量中。

抽象角度看,JMM模型中定义了线程与主内存的关系

线程之间的共享变量存放在主内存中;
每个线程都有一个私有的本地内存(JMM的抽象概念),里面存放了该线程读/写共享变量的拷贝副本;
低层次角度看,主内存就是硬件内存,为了获取更高的运行速度,虚拟机及硬件系统会将工作内存优先存储与寄存器和高速缓存中;
JMM中线程的工作内存,是CPU的寄存器和高速缓存的抽象描述;而JVM的静态存储模型(JVM内存模型)只是一种对内存的物理划分而已,它只局限于内存。

Java 内存模型试图屏蔽各种硬件和操作系统的内存访问差异,以实现让 Java 程序在各种平台下都能达到一致的内存

访问效果。

谈jmm三大特性, 原子性(sychronized),有序性(内存屏障,禁止指令重排序),可见性(写会主内存,工作内存失效)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uKpo8zYv-1625744462254)(imgs/JMM.png)]

java内存模型(JMM)是一种抽象的概念,它是一种规范,定义了程序中各个变量访问的方式。JVM运行程序的实体是线程,每个线程创建时JVM会为其创建相应的工作内存(空栈间),用于储存线程私有数据,JMM中规定所有变量都存储在主内存上,所有线程都可访问,线程对于变量的操作(赋值、读取等)必须在工作内存进行,操作完成首在写回主内存,这样个线程之间就无法相互访问,线程间的通信(传值)必须通过主内存来完成。
    所以jmm解决线程间通信和安全问题

主内存(堆内存):主要存储实例对象,所有线程创建的实例对象(成员、局部、静态、常量等)都放在主内存中。存在线程安全问题(造成主内存与工作内存间数据存在一致性问题)。
工作区域(私有线程域,线程栈):主要存储当前方法的所有本地变量信息(主内存中变量的复制,也包含字节码行号指示器、相关Native方法信息)。线程中的本地变量对其他线程不可见,不存在线程安全问题。
————————————————

java内存分区就答 运行时数据区的几个部分(栈,堆,本地方法栈,程序计数器,方法区);

2.java8 的内存分代改进

1.8主要是将永久代改为元空间,其他无变化

谈方法区变化可以谈为什么字符串要放在堆里,一定要学会联想

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OI3oQ5tB-1625744462255)(imgs/hospot虚拟机方法区的变化.png)]

3. 栈和堆的区别

区别一: 存放对象不同。见上 运行时数据区

区别二:线程私有还是公有。

区别三: 堆初始内存比栈初始内存大。

4.为什么要设置suivivor区?

Survivor的存在意义,就是减少被送到老年代的对象,进而减少Full GC的发生,Survivor的预筛选保证,只有经历16次Minor GC还能在新生代中存活的对象,才会被送到老年代。

5. 为什么设置两个survivor,而不是一个?

主要是为了防止垃圾回收而产生的内存碎片化(可从停止复制算法原理分析而出),从而严重影响JAVA程序的性能。https://blog.csdn.net/towads/article/details/79784249

6. 伊甸园区和S0、S1的比例分配

8 : 1 : 1

7. 为什么要堆分代?

一句话就是优化GC性能(减少单次gc时间,避免全堆扫描。 采取不同得gc算法)。

如果把所有的对象全放在一起,每次GC都要扫描全部对象,而有的存活率高的对象不需要被频繁的扫描,这样就增加了单次GC的时间,影响GC性能。分代的话只需要频繁的扫描存活率较低的对象对其进行GC操作,提高了GC的效率。

并且分代还可以根据对象的特点,比如存活时间的长短,针对性的采取合适的GC算法。

8. jmm三大特性

见file:///F:/java%E9%9D%A2%E8%AF%95%E9%A2%98%E6%96%87%E6%A1%A3/Java-Notes-master/docs/CyC%E6%8A%80%E6%9C%AF%E9%9D%A2%E8%AF%95%E5%BF%85%E5%A4%87%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%20CyC2018.pdf(ctrl + f 搜 内存模型三大特性)

什么是双亲委派模型?⭐

递归到顶层(启动类加载器,一般加载java. javax. sun.开头的类),当父加载器无法完成这个请求时,子类才会尝试去加载。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dKlWbd6P-1625744462255)(imgs/双亲委派.png)]

双亲委派机制的作用

1、防止重复加载同一个.class。通过委托去向上面问一问,加载过了,就不用再加载一遍。保证数据安全。
2、保证核心.class不能被篡改。通过委托方式,不会去篡改核心.class,不同的加载器加载同一个.class也不是同一个Class对象。这样保证了Class执行安全。(比如创建一个java.lang.String,如果没有此机制,可能就不会加载java核心的String类)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aY9HQ3uA-1625744462256)(imgs/双亲委派作用.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OAuq5ZUr-1625744462257)(imgs/双亲委派面试题.png)]

对象的分配过程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W9GRPe08-1625744462258)(imgs/对象分配过程.png)]

起始伊甸园区对象向s1和s2区转移(转移前s1和s2区为均空)。转移一次后,s0和s1区,有一个区为空,称为to区,另外一个区不为空称为from区。再一次垃圾回收时,然后伊甸园区对象和from区的对象移到to区。也就是伊甸园区和s1和s2中的from区往s1和s2中的to区转移,并将对象的age值+1(age相当于标识),来回折腾到了15次(age=15,)下一次如果还不被回收就到了老年区。

上述过程都是伊甸园区满了,然后from区跟着一块回收。如果from区满了,那就是有特殊的规则处理。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-evTdTPwT-1625744462259)(imgs/对象回收总结.png)]

上述都是一般过程,有特殊的,一开始伊甸园区就放不下对象,可能一下就升到s区,在放不下,就升到老年区,在放不下,那就oom了。如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UJwJFybR-1625744462260)(imgs/对象分配具体过程.png)]

对象的内存布局

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fi6kXayD-1625744462261)(imgs/对象的内存布局1.jpg)]

Object o = new Object()在内存中占了多少字节?

markword 8字节,因为java默认使用了calssPointer压缩,classpointer 4字节,padding(使总字节数使8的倍数) 4字节 因此是16字节
如果没开启classpointer默认压缩,markword 8字节,classpointer 8字节,padding 0字节 也是16字节

User (int id,String name) User u = new User(1,‘张三’);占用多少字节
markword 8字节,开启classPointer压缩 ,classpointer 4字节,instance data int 4字节,开启普通对象指针压缩 String 4字节 padding 4 一共24字节

对象的创建过程?

Step1:类加载检查

虚拟机遇到一条 new 指令时,首先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已被加载过、解析和初始化过。如果没有,那必须先执行相应的类加载过程

Step2:分配内存

类加载检查通过后,接下来虚拟机将为新生对象分配内存。对象所需的内存大小在类加载完成后便可确定,为对象分配空间的任务等同于把一块确定大小的内存从 Java 堆中划分出来。分配方式“指针碰撞”“空闲列表” 两种。

Step3:初始化零值

内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),这一步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。

Step4:设置对象头

初始化零值完成之后,虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息。 这些信息存放在对象头中。

Step5:执行 init 方法

在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但从 Java 程序的视角来看,对象创建才刚开始,方法还没有执行,所有的字段都还为零。所以一般来说,执行 new 指令之后会接着执行方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来。

类加载的执行过程?

三个过程:加载— 链接----(检查,准备,解析)---- 初始化

  1. 加载:根据查找路径找到相应的 class 文件然后导入;
  2. 链接
    • 检查:检查加载的 class 文件的正确性;
    • 准备:给类中的静态变量分配内存空间;
    • 解析:虚拟机将常量池中的符号引用替换成直接引用的过程。符号引用就理解为一个标示,而在直接引用直接指向内存中的地址;[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B4evMOTU-1625744462261)(imgs/类的加载过程(链接).png)]
  3. 初始化:对静态变量和静态代码块执行初始化工作。

怎么判断对象是否可以被回收?

  • 引用计数器:为每个对象创建一个引用计数,有对象引用时计数器 +1,引用被释放时计数 -1,当计数器为 0 时就可以被回收。它有一个缺点不能解决循环引用的问题;
  • 可达性分析:从 GC Roots 开始向下搜索,搜索所走过的路径称为引用链。当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是可以被回收的。

Java 中都有哪些引用类型?

  • 强引用
  • 软引用
  • 弱引用
  • 虚引用

垃圾回收常见面试题

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m5b2X3uJ-1625744462262)(imgs/垃圾回收相关面试题.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VEEHCO0e-1625744462263)(imgs/垃圾回收常见面试题2.png)]

1. 什么是垃圾,什么是GC,为什么要有GC

垃圾是指在运行程序中没有任何指针指向的对象,它的主要作用就是回收程序中不再使用的内存.

GC是垃圾回收,

如果不及时对内存中的垃圾进行回收,这些垃圾对象所占的内存会一直保留到运行程序结束,被保留的空间无法被其他对象使用。甚至可能导致内存溢出。

常见的垃圾回收机制⭐(判断对象是否存活,也就是标记阶段)

  1. 引用计数法:引用计数法是一种简单速度很慢的垃圾回收技术。每个对象都含有一个引用计数器,当有引用连接至对象时,引用计数加1。当引用离开作用域或被置为null时,引用计数减1。虽然管理引用计数的开销不大,但这项开销在整个程序生命周期中将持续发生。垃圾回收器会在含有全部对象的列表上遍历,当发现某个对象引用计数为0时,就释放其占用的空间。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DyN1wJY0-1625744462264)(imgs/引用计数算法.png)]

  2. 可达性分析算法:这个算法的基本思路就是通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连(用图论的话来说,就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的。(可以解决循环引用的问题)

循环引用指:
    GC root ->A->B->C,而C又指向A,所以即使GC root 指向A的引用断了,但是ABC的引用计数器仍然部位0,此时已是垃圾,但是算法判定不是垃圾,无法回收。

jvm 有哪些垃圾回收算法⭐

  • 停止-复制:先暂停程序的运行,(如何找到对象是存活的,因为直接根据GCroot遍历,能遍历到的就是存活的,遍历一个复制一个,所以不用标记)然后将所有存活的对象从当前堆复制到另一个堆,没有被复制的对象全部都是垃圾。当对象被复制到新堆时,它们是一个挨着一个的,所以新堆保持紧凑排列,然后就可以按前述方法简单,直接的分配了。缺点是一浪费空间,两个堆之间要来回倒腾,二是当程序进入稳定态时,可能只会产生极少的垃圾,甚至不产生垃圾,尽管如此,复制式回收器仍会将所有内存自一处复制到另一处。

  • 标记-清除(mark-sweep.非移动式算法):同样是从堆栈和静态存储区出发,遍历所有的引用,进而找出所有存活的对象。每当它找到一个存活的对象,就会给对象一个标记(标记在对象头里面),这个过程中不会回收任何对象。只有全部标记工作完成的时候,清理动作才会开始。在清理过程中,没有标记的对象会被释放,不会发生任何复制动作。所以剩下的堆空间是不连续的,垃圾回收器如果要希望得到连续空间的话,就得重新整理剩下的对象。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Lhwe2xWH-1625744462265)(imgs/标记清除.png)]

    • 标记-整理/压缩(移动式算法):它的第一个阶段与标记/清除算法是一模一样的,均是遍历GC Roots,然后将存活的对象标记。移动所有存活的对象,且按照内存地址次序依次排列,然后将末端内存地址以后的内存全部回收。因此,第二阶段才称为整理阶段。
  • 分代收集算法:把Java堆分为新生代和老年代,然后根据各个年代的特点采用最合适的收集算法。新生代中,对象的存活率比较低,所以选用复制算法(如果存活率高的话,垃圾就少,就要将很多很多的对象复制到另一个区域,这个就有点得不偿失,所以复制算法适合存活率低的对象的垃圾回收),老年代中对象存活率高且没有额外空间对它进行分配担保,所以使用“标记-清除”或“标记-整理”算法进行回收。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D1RAQdl7-1625744462266)(imgs/分代回收算法.jpg)]

三种算法的对比

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lzIOMsv7-1625744462267)(imgs/垃圾回收三种算法对比.jpg)]

引用(强、软、弱、虚)

  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hxRHS4So-1625744462267)(imgs/强引用.jpg)]

注意:软引用就算是显示的将引用赋值为null,对象也不会在gc时回收,除非gc后内存仍然不够才会回收软引用的对象

软引用:内存不足才回收。 软引用可以做缓存

弱引用:执行gc就回收

虚引用:用于管理堆外内存(直接内存 )。比如Java 开发者经常用 java.nio.DirectByteBuffer 对象进行堆外内存的管理和使用, 该类会在创建对象时就分配堆外内存。也就是如果在DirectByteBuffer有一个对象指向了一块对外内存,如果DirectByteBuffer被回收了,jvm没法回收堆外内存,所以就造成了堆外内存的内存泄漏。所以设置一个虚引用指向DirectByteBuffer对象,当这个对象被回收时,虚引用作为一个通知信息放入一个队列里,jvm监视这个队列,然后进行一些特殊处理(清理堆外内存,这个操作jvm应该只是通知操作系统回收)

内存溢出

outMemeryError:没有空闲内存,并且垃圾回收器也无法提供更多的内存。

内存溢出原因有二: 一是本身运行的程序数据量很大,堆的初始值设置太小,可通过参数-Xms来调整

内存泄露

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kZqDqimO-1625744462268)(imgs/内存泄漏.jpg)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KA03qRWy-1625744462269)(imgs/内存泄漏举例.jpg)]

几个GC的区别

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ODcGTGeZ-1625744462270)(imgs/minor GC,major GC, full GC.png)]

Minor GC和Full GC触发条件⭐

  • Minor GC触发条件:当Eden区满时,触发Minor GC。
  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7ZBcbEHd-1625744462271)(imgs/年轻代触发机制.png)]
  • major gc:
  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GN5FSAT7-1625744462272)(imgs/老年代GC触发条件.png)]
  • Full GC触发条件:
    1. 调用System.gc时,系统建议执行Full GC,但是不必然执行
    2. 老年代空间不足
    3. 方法区空间不足
    4. 通过Minor GC后进入老年代的平均大小大于老年代的可用内存
    5. 由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小

GC中Stop the world(STW)⭐

作用:确保垃圾回收期间的数据的一致性。如果在分析过程中对象引用关系还在不断变化,那会直接影响分析结果的准确性。

在执行垃圾收集算法时(标记阶段开始前停),Java应用程序的其他所有除了垃圾收集收集器线程之外的线程都被挂起。此时,系统只能允许GC线程进行运行,其他线程则会全部暂停,等待GC线程执行完毕后才能再次运行。这些工作都是由虚拟机在后台自动发起和自动完成的,是在用户不可见的情况下把用户正常工作的线程全部停下来,这对于很多的应用程序,尤其是那些对于实时性要求很高的程序来说是难以接受的。

任何垃圾回收器不难以避免STW,只能尽量缩短stop时间。

垃圾回收和用户线程是可以并发(一个时间段来回切换)执行的,但不能并行执行(任何某个时间点都在执行)。

并不能任何时候都可以暂停,只有在“安全点”才能执行GC

安全区域是拓展的安全点,相当每个点都是安全的

哪些对象可以作为GC Roots

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IC6SAD9i-1625744462273)(imgs/哪些对象可以作为GCroot.png)]

JVM锁优化和膨胀过程⭐

锁优化:

  1. 自旋锁:互斥同步进入阻塞状态的开销都很大,应该尽量避免。在许多应用中,共享数据的锁定状态只会持续很短的一段时 间。自旋锁的思想是让一个线程在请求一个共享数据的锁时执行忙循环(自旋)一段时间,如果在这段时间内能获得 锁,就可以避免进入阻塞状态。 自旋锁虽然能避免进入阻塞状态从而减少开销,但是它需要进行忙循环操作占用 CPU 时间,它只适用于共享数据的 锁定状态很短的场景。

  2. 锁粗化:虚拟机通过适当扩大加锁的范围以避免频繁的拿锁释放锁的过程。

  3. 锁消除:通过逃逸分析发现其实根本就没有别的线程产生竞争的可能(别的线程没有临界量的引用),或者同步块内进行的是原子操作,而“自作多情”地给自己加上了锁。有可能虚拟机会直接去掉这个锁。

    锁升级:

    1.无锁

    2.偏向锁:在大多数的情况下,锁不仅不存在多线程的竞争,而且总是由同一个线程获得。因此为了让线程获得锁的代价更低引入了偏向锁的概念。偏向锁的意思是如果一个线程获得了一个偏向锁,如果在接下来的一段时间中没有其他线程来竞争锁,那么持有偏向锁的线程再次进入或者退出同一个同步代码块,不需要再次进行抢占锁和释放锁的操作。

    3.轻量级锁:当存在超过一个线程在竞争同一个同步代码块时,会发生偏向锁的撤销。当前线程会尝试使用CAS来获取锁,当自旋(自旋锁)超过指定次数(可以自定义,默认十次)时仍然无法获得锁,此时锁会膨胀升级为重量级锁。

    4.重量级锁:重量级锁依赖对象内部的monitor锁来实现,而monitor又依赖操作系统的MutexLock(互斥锁)。当系统检查到是重量级锁之后,会把等待想要获取锁的线程阻塞,被阻塞的线程不会消耗CPU,但是阻塞或者唤醒一个线程,都需要通过操作系统来实现。

为什么Full GC 比 majorGC效率低

1.(空间换时间s1、s2)新生代复制算法比较快。Eden区回收时直接全部清空,存活的对象存放到内存容量比较小的s1,少了解决内存碎片整理 加上直接copy的速度,效率很高。

2.(时间换空间),老年代标记清除算法会导致内存碎片化,因此就引入了标记整理算法,执行完毕后,存活的对象会按序放置,移动对象的内存地址(重点),来解决碎片化,但是执行时间较长。

jvm 有哪些垃圾回收器?

  • Serial:最早的单线程串行垃圾回收器。

  • Serial Old:Serial 垃圾回收器的老年版本,同样也是单线程的,可以作为 CMS 垃圾回收器的备选预案。

  • ParNew:是 Serial 的多线程版本。

  • Parallel 和 ParNew 收集器类似是多线程的,但 Parallel 是吞吐量优先的收集器,可以牺牲等待时间换取系统的吞吐量。

  • Parallel Old 是 Parallel 老生代版本,Parallel 使用的是复制的内存回收算法,Parallel Old 使用的是标记-整理的内存回收算法。

  • CMS:一种以获得最短停顿时间为目标的收集器,非常适用 B/S 系统。

  • G1:一种兼顾吞吐量和停顿时间的 GC 实现,是 JDK 9 以后的默认 GC 选项。

    新生代垃圾回收器和老生代垃圾回收器都有哪些?有什么区别?

  • 新生代回收器:Serial、ParNew、Parallel Scavenge

  • 老年代回收器:Serial Old、Parallel Old、CMS

  • 整堆回收器:G1

新生代垃圾回收器一般采用的是复制算法,复制算法的优点是效率高,缺点是内存利用率低;老年代回收器一般采用的是标记-整理的算法进行垃圾回收。

详细介绍一下 CMS 垃圾回收器⭐

CMS 是英文 Concurrent Mark-Sweep 的简称,是以牺牲吞吐量为代价来获得最短回收停顿时间的垃圾回收器。对于要求服务器响应速度的应用上,这种垃圾回收器非常适合。在启动 JVM 的参数加上“-XX:+UseConcMarkSweepGC”来指定使用 CMS 垃圾回收器。

CMS 使用的是标记-清除的算法实现的,所以在 gc 的时候回产生大量的内存碎片,当剩余内存不能满足程序运行要求时,系统将会出现 Concurrent Mode Failure,临时 CMS 会采用 Serial Old 回收器进行垃圾清除,此时的性能将会被降低。

CMS工作原理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ASZS7Eaw-1625744462274)(imgs/cmd工作原理1.jpg)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U2nwQl7y-1625744462275)(imgs/cms 工作原理2.jpg)]

CMS 优点和缺点

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Pmb1MRCn-1625744462276)(imgs/CMS的优点和缺点.jpg)]为什么cms使用标记清除而不是用标记压缩

因为在重新标记阶段,用户线程和垃圾回收线程并发执行,而标记整理算法在标记整理过程中需要移动对象,而此时用户线程是在执行的,怎能随意修改对象的地址呢?

G1

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0fd6VUBp-1625744462277)(简历项目总结/imgs/G1.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dNVin9xB-1625744462278)(简历项目总结/imgs/个.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-24AUGjBd-1625744462278)(简历项目总结/imgs/而3.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RVs3jgSW-1625744462279)(简历项目总结/imgs/个回收过程.png)]

G1和CMS的比较

  1. CMS收集器是获取最短回收停顿时间为目标的收集器,因为CMS工作时,GC工作线程与用户线程可以并发执行,以此来达到降低手机停顿时间的目的(只有初始标记和重新标记会STW)。但是CMS收集器对CPU资源非常敏感。在并发阶段,虽然不会导致用户线程停顿,但是会占用CPU资源而导致引用程序变慢,总吞吐量下降。

  2. CMS仅作用于老年代,是基于标记清除算法,所以清理的过程中会有大量的空间碎片。

  3. CMS收集器无法处理浮动垃圾,由于CMS并发清理阶段用户线程还在运行,伴随程序的运行自热会有新的垃圾不断产生,这一部分垃圾出现在标记过程之后,CMS无法在本次收集中处理它们,只好留待下一次GC时将其清理掉。

  4. G1是一款面向服务端应用的垃圾收集器,适用于多核处理器、大内存容量的服务端系统。G1能充分利用CPU、多核环境下的硬件优势,使用多个CPU(CPU或者CPU核心)来缩短STW的停顿时间,它满足短时间停顿的同时达到一个高的吞吐量。

  5. 从JDK 9开始,G1成为默认的垃圾回收器。当应用有以下任何一种特性时非常适合用G1:Full GC持续时间太长或者太频繁;对象的创建速率和存活率变动很大;应用不希望停顿时间长(长于0.5s甚至1s)。

  6. G1将空间划分成很多块(Region),然后他们各自进行回收。堆比较大的时候可以采用,采用复制算法,碎片化问题不严重。整体上看属于标记整理算法,局部(region之间)属于复制算法。

  7. G1 需要记忆集 (具体来说是卡表)来记录新生代和老年代之间的引用关系,这种数据结构在 G1 中需要占用大量的内存,可能达到整个堆内存容量的 20% 甚至更多。而且 G1 中维护记忆集的成本较高,带来了更高的执行负载,影响效率。所以 CMS 在小内存应用上的表现要优于 G1,而大内存应用上 G1 更有优势,大小内存的界限是6GB到8GB。

    ​ 记忆集(Remember Set):对于跨代引用的问题,CMS选择了不维护新生代对老年代记忆集,因为新生代变化太快,维护起来开销比较大(cms作用于老年代,我们的顾虑是,新生代对象引用了老年代对象,那么我们怎么将被新生代引用的对象给回收掉呢??答案是即使年轻代有对象引用了老年代,但是由于新生代朝生夕死,而且老年代回收前肯定进行过年年轻代回收,所以维护卡表意义不大),而G1的解决方案是,不管Yong GC还是Mixed GC,都会将Yong Gen加入到Collection Set中,简单说就是要么是只回收新生代,要么整个新生代和老年代一起回收,这样就避免了新生代对老年代记忆集的维护。
    ❝ 这里只讨论了新生代对老年代的引用的记忆集的维护,老年代对新生代的引用还是会维护一个记忆集的(再回收新生代的时候判断那些新生代对象被引用了,从而不回收

    所以cms和g1都是在回收老年代时不考虑引用问题,而g1再回收新生代时需要考虑,所以需要维护一个新生代被老年代引用的卡表

i++操作的字节码指令⭐

  1. 将int类型常量加载到操作数栈顶
  2. 将int类型数值从操作数栈顶取出,并存储到到局部变量表的第1个Slot中
  3. 将int类型变量从局部变量表的第1个Slot中取出,并放到操作数栈顶
  4. 将局部变量表的第1个Slot中的int类型变量加1
  5. 表示将int类型数值从操作数栈顶取出,并存储到到局部变量表的第1个Slot中,即i中

如果自己设计jvm,怎么考虑

自己的思路

首先介绍下jvm是干什么的

jvm屏蔽了与平台相关的信息,可以让字节码文件在jvm上运行,

首先我们编写一个java文件,编译之后生成字节码文件,然后当我们需要用到该文件的内容(或者该类)时,需要将其加载到内存,所以需要设计一个类加载器或者类加载机制(类加载过程),将类信息加载到内存,肯定需要战鹰内存空间,所以需要给各个变量、常亮、静态变量、对象等划分内存区域。当我们创建对象的时候对象的创建的过程啊,每个方法执行的时候相关信息放在哪呀,程序被挂起时如何回到原地方继续执行啊,当我们不需要使用这些变量或者对象时,应该对其回收,所以要设计相应的垃圾回收算法和垃圾回收器,如果需要高效的回收,肯定还需要根据不同对象的特点进行分批回收。

说一下 jvm 调优的工具

JDK 自带了很多监控工具,都位于 JDK 的 bin 目录下,其中最常用的是 jconsole 和 jvisualvm 这两款视图监控工具。

  • jconsole:用于对 JVM 中的内存、线程和类等进行监控;

  • jvisualvm:JDK 自带的全能分析工具,可以分析:内存快照、线程快照、程序死锁、监控内存的变化、gc 变化等。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3fDPZAUS-1625744462280)(imgs/jvm调优工具.png)]

常用的 jvm 调优的参数都有哪些?

  • -Xms2g:初始化推大小为 2g。默认为物理内存的1/64(年轻代+老年代。不包括永久代或元空间,-X 是jvm的运行参数,ms 是memory start);
  • -Xmx2g:堆最大内存为 2g;默认为物理内存的1/64(一般初始值和最大值设置一样的,因为频繁的扩容和释放会造成系统性能下降
  • -Xmn:设置新生代的大小。
  • -XX:PrintFlagsInitial : 查看所有参数的默认初始值
  • -XX:PrintFlagsFinal : 查看所有参数的最终值(可能会修改,,不在是初始值)。
  • 具体查
  • -XX:NewRatio=4:设置年轻的和老年代的内存比例为 1:4(默认值2);
  • -XX:SurvivorRatio=8:设置新生代 Eden 和 Survivor 比例为 8:2(Edon和S1、S2的比例是8:1:1)(默认值8);
  • -XX: MaxTenuringThreshold: 设置新生代垃圾的最大年龄(age)。
  • –XX:+UseParNewGC:指定使用 ParNew + Serial Old 垃圾回收器组合;
  • -XX:+UseParallelOldGC:指定使用 ParNew + ParNew Old 垃圾回收器组合;
  • -XX:+UseConcMarkSweepGC:指定使用 CMS + Serial Old 垃圾回收器组合;
  • -XX:+PrintGC:开启打印 gc 信息;
  • -XX:+PrintGCDetails:打印 gc 详细信息。

TODO:

  • CMS GC回收分为哪几个阶段?分别做了什么事情?
  • 为什么要划分成年轻代和老年代?
  • 年轻代为什么被划分成eden、survivor区域?
  • 年轻代为什么采用的是复制算法?
  • 老年代为什么采用的是标记清除、标记整理算法
  • 什么情况下使用堆外内存?要注意些什么?
  • 堆外内存如何被回收?
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值