java面试—JVM基础理解及GC调优

2021/7/14 22:07:05

本文主要是介绍java面试—JVM基础理解及GC调优,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

JVM性能调优

JVM性能调优面试题哔哩哔哩

**java跨平台特性:**由于java虚拟机JVM会在软件层面屏蔽不同操作系统在底层硬件和指令上的不同。(下载安装JDK时,会选择不同系统版本,分不同系统的原因就是JVM对不同操作系统的实现不一样)

JVM组成

由三大块组成:
类转载子系统---->运行时数据区(JVM中的一大块内存)---->字节码执行引擎

当java中的class文件开始执行时,JVM就开始工作。最大的一块区域就是内存区域,虚拟机调优也就是对运行时数据区的调优。

在这里插入图片描述

栈(线程)

只要一个线程被运行,java虚拟机就会为这个线程分配一个专属的空间,这个空间用于存放这个线程中的局部变量

栈帧内存空间

当没运行一个线程中的方法时,虚拟机会从该线程所分配的空间中分配出一块空间用于存储该方法中的局部变量。线程栈为每个方法分配空间的顺序符合数据栈的逻辑(先进后出),也就是后执行的方法所分配的空间要先被回收,先执行的方法所分配的空间更后被回收

在这里插入图片描述

线程栈中的方法块内存(栈帧)的组成

局部变量表、操作数栈、动态链接、方法出口

操作数栈:存储的就是一些数字数据,就是一个数据栈,对数据的临时存储,对数据的操作,都需要将数据存放到这个操作数栈中去。比如两个数相乘,首先会将相乘的两个数据常量放进操作数栈,然后执行乘法指令,乘法指令会从操作数栈的顶部取出最上面的两个数做乘法运算,再将计算的结果压入操作数栈。

局部变量表:就是存放该方法中定义的一些变量,java中定义一个变量,将要赋值的数据(一个常量)放入操作数栈,将变量存在进局部变量表中,对其进行赋值时,再将常量从操作数栈出栈将数据给到局部变量表中的变量。

mian()方法中的局部变量表:main()方法中的局部变量表会保存对象的地址空间,对象是保存在堆中的,通过保存对象的指针,使用对象的时候才能找到这个对象。

栈中的对象也是一样保存在堆中,同样通过指针去指向堆中的这个对象。

动态链接:(把一些符号引用转化为直接引用)JVM执行的.class文件是被加载到方法区的内存空间的,一个方法的方法体自然也是放在了方法区,当我们在线程栈中执行指令调用了某一个方法,代码只是使用了名字+();来表示一个方法,而方法体还存放在方法区,让这个方法名字与方法体对应起来就要通过动态链接。

方法出口:将原来的调用者的信息,原来的线程信息,以及保存的一些信息都放在了方法出口里面,通过他就可以回到之前的执行状态,继续执行的话应该从哪一行开始等等。

在这里插入图片描述
在这里插入图片描述

程序计数器

和线程栈一样,每当有一个线程开始运行,就会从程序计数器这一块分配出一个空间用于存放该线程将要执行的下一条指令的存放地址。

作用是:由于每一个线程就有一个程序计数器,当某个线程在执行的过程中,cpu的被其他线程抢占(或者时间片到了),那么这个线程就会被挂起,当其他线程执行了之后,该线程重新抢占cpu时,通过程序计数器就知道之前执行到了那一个指令,并接着之前的指令继续执行,如果没有这个程序计数器,那么该线程就不得不重新开始执行。

字节码执行引擎

虚拟机执行的.class文件是会被加载到方法区,由字节码执行引擎去执行,在执行的过程中,字节码执行引擎是知道下一条指令是什么,所以也是由字节码执行引擎去修改对应程序计数器的值

方法区

在jdk1.8之前这一个区叫做持久代或者永久代,1.8之后叫做元空间

常量+静态变量+类信息

本地方法

本地方法就是通过native修饰的方法,这样的方法是通过底层使用的C语言或C++
来实现的,而不是java语言实现的方法。

本地方法被调用的时候也需要获得内存空间才行,这些空间就是从本地方法栈中分配出来的。

在这里插入图片描述

可达性分析算法

将GC Roots对象作为起点,从这些节点开始向下搜索引用的对象,找打的对象都
标记为非垃圾对象,其余的未标记的对象都是垃圾对象(垃圾对象没有引用指向他,
也没有程序变量引用指向他),垃圾对象最终会被回收掉。

GC Roots根节点

线程栈的本地变量、静态变量、本地方法栈的变量等等

在这里插入图片描述

完整的对象不仅仅是我们看到的那些对象的属性、方法,对象还包括一个对象头,对象头中还包含了大量的信息,其中分带年龄、GC标记等都在这里面

在这里插入图片描述

堆和GC垃圾回收

堆中对对象的存储分成了四大块,Eden、s0、s1存储年轻代的对象(创建的时间相对不长,经过GC的次数不多,没有超过15次),最后一个区域存放的都是经过15次GC之后还保留了下来的对象或者是在年轻代的存储过程中存不下的对象。

对象最开始的创建的存放位置是在Eden中(此时s0、s1都是空的),当这一块区域存满了之后,minor gc (年轻代的GC)就会进行一次,将GC保留下来的对象存放到s0中,垃圾对象直接回收。Eden也就变空了(此时s0有存储了对象、s1没有),当Eden再次存满时,minor gc就会执行一次,这一次不仅是Eden中的会进行GC操作,s0中的对象也会,这一次GC保留下来的对象会放到s1中去。再继续执行,minor gc就是Eden和s1,保留对象存放到s0,如果s0存放不下就会将一部分存放到老年代中去。然后程序的继续执行产生新的对象,GC回收垃圾对象,每进行一次GC,每个对象的年龄(gc的次数)就会增加一次,当增加到15次时,也就是经过了15次GC之后,某些对象还是没有被回收,这些对象就会被放进老年代中存储。

当老年代着一部分内存被存满之后,就会触发full gc(全部gc),也就是对整个内存空间进行GC,将年轻代和老年代中的垃圾对象回收掉,直到老年代中的没有可以回收的对象,此时老年代的存储空间又被占满,这样就会导致内存溢出。

在这里插入图片描述

arthas 阿里巴巴的诊断工具(重要)

GC过程中的对象放入老年代的情况

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

调优的目的(什么是调优)

首先就是要减少full gc,内存容量大的时候还要减少minor gc

为什么要减少 full gc ?

STW(Stop the World),表示的是在做 full gc 的时候,系统会停止掉用户的线程或者一些请求,专门去做gc的工作,这时用户端反映就变得迟钝,如果full gc的时间短可能就是卡一下就好了甚至是没有感觉,但是如果full gc时间很长,那么整个网站就是一卡一卡的。

STW对整个系统的吞吐量性能是有很大的影响的,而造成STW的原因就是full gc(minor gc也会,但是如果整个内存在较小的情况下,minor gc所要回收的对象相对来说更少,这个的过程也就非常的短,所以可以暂时不过多的关注,但是当内存容量达到了一定的大容量时,就要特别关注minor gc的调优了),这也就是调优的目的为什么要首先减少 full gc的原因(通过减少full gc来减少STW)

既然会影响用户线程,为什么要设置STW机制(重点面试题)

如果没有STW机制,也就是说在进行GC时,用户线程也可以继续执行,这个时候如果用户线程的结束时间比GC的时间更快,在结束的时候用户线程中的线程栈空间会被回收,其中的变量也就被释放掉了,也就是说这些GC Roots 被释放掉了,这些变量所指向的对象也就属于未被引用的了,他们属于垃圾对象了。可是在这之前GC已经检查了这一条引用路线都是非垃圾对象,GC还没结束就变成了垃圾对象,难道GC又要重新去堆中进行可达性分析吗?堆中有成百上千的对象,重新GC明显不现实,重复去做这些工作,很有可能GC的工作循环执行没法停掉,这样就会对系统性能造成很大影响。所以就干脆设置STW机制,在进行GC操作时,直接停掉用户线程,专心做GC,等GC结束再回复用户线程,这样反而可能对性能的影响没这么大。总结来说就是不能让对象一会不是垃圾,一会又是垃圾

G1垃圾收集器

核心的是,不会等到Eden区的内存被占满之后才进行minor gc,当Eden使用空间达到了一定的值(或者其他条件),就对一部分Eden内存进行垃圾回收,小部分内存的回收一般来说不会造成长时间的STW,其实这样回收内存的时间总时间与正常的Eden区占满再回收的总时间是一样的,甚至会更长一点点,但是这样做将时间进行了分段,分成了一小部分一小部分进行,这样让用户对垃圾回收的延时近乎是无感觉的,而占满再回收,会让这个用户线程卡顿很久,造成很大的性能影响

G1会对内存空间中的垃圾回收所耗费的时间进行一些计算,当时间长度达到了所设定的参数时,G1就只会对这么一块大小的内存进行回收,不会对更多的内存进行回收。

CMS垃圾回收器底层就是没有这样的回收机制,所以对于大内存的系统不太合适。

G1 、CMS等等垃圾收集器底层都是由C\C++来编写的。

的垃圾回收所耗费的时间进行一些计算,当时间长度达到了所设定的参数时,G1就只会对这么一块大小的内存进行回收,不会对更多的内存进行回收。

CMS垃圾回收器底层就是没有这样的回收机制,所以对于大内存的系统不太合适。

G1 、CMS等等垃圾收集器底层都是由C\C++来编写的。
在这里插入图片描述



这篇关于java面试—JVM基础理解及GC调优的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程