JVM-面试题

1、JVM运行时内存区域划分
一个java程序的编译和执行过程如下:
在这里插入图片描述
· .java ——编译——> .class

· 类加载器负责加载各个字节码文件(.class)

· 加载完.class后,由执行引擎执行,在执行过程中,需要运行时数据区提供数据
JAVA运行时数据区:

包括5个区域:程序计数器、Java虚拟机栈、本地方法栈、方法区、堆

程序计数器:用于记录Java程序线程执行的位置,每个线程都有一个属于自己的程序计数器,当线程阻塞、挂起、恢复等一系列操作都需要程序计数器的参与,所以程序计数器都是线程私有的。

Java虚拟机栈:Java虚拟机栈随着线程的创建而创建,用于存放栈帧也是线程私有的。当Java虚拟机调用程序方法时,首先会创建一个栈帧,用于存放Java方法运行时产生的临时变量和中间结果,例如:局部变量表、操作数栈、动态链接、方法出口灯信息。如果Java虚拟机栈的内存深度大于Java虚拟机允许的最大深度则会抛出StackOverflowError异常,Java虚拟机从开始调用一个方法到一个方法执行结束都对应着一个栈帧在虚拟机中入栈和出栈的过程。

本地方法栈:和Java虚拟机栈相似,线程私有,不过Java虚拟机栈是对虚拟机中的方法进行操作,而本地方法栈值支持native方法,例如在Java中调用c/c++;

堆:被虚拟机中的所有线程所共享,随着jvm的启动而创建,主要用于存放Java对象实例,也是垃圾回器回收垃圾的主要区域,可分为新生代和老年代,当堆内存不够时,会向jvm申请内存,如果还不够则会抛出OutOfMemoryError异常。
为什么新生代内存需要有两个Survivor区?
一、为什么需要Survivor区?
答:减少被送往老年代的对象,只有经过Survivor区预筛选进行16次Minor GC后还存活的对象才被送往老年代。这样可以减少Full GC的发生。因为老年代比新生代空间大的多,进行一次Full GC 花费大量时间,所以可以提高时间效率与性能。

二、Survivor区为什么分为From和To两个区域?
答:为了解决内存的碎片化问题,提高内存的使用率。具体表现为,在进行一次Minor GC 的时候,Eden 区的存活对象会被复制的Survivor区S1(From 或 To)中,当触发第二次Minor GC 的时候,此时Eden 区的存活对象应该被复制到Survior区的S2(From 或 To);同时将Survivor区S1中存活的对象也复制到S2区;当下一次再进行Minor GC 的时候,Eden区的存活对象就复制到空的S1区,这样循环往复。这样做的好处使得Survivor区的内存空间连续避免了碎片化。
方法区:和堆一样被线程所共享,主要用于存放类加载时的类信息、静态变量、常量池等数据。
2、常见的GC回收算法及其含义
什么时候被回收?(判断对象是否需要被回收的方法)
(1)引用计数法

引用计数法就是给每个对象分配一个引用计数器,当一个对象别另外一个对象引用时,引用计数器加1,当引用失效时引用计数器减1,当计数器为0时,说明该对象需要被回收。

但引用计数器存在一个缺陷:对相互引用的对象无法进行垃圾回收。

Eg:

Object a=new Object();//a计数器为1

Object b=new Object();//b计数器为1

a.next=b;//a计数器为2

b.next=a;//b计数器为2

a=null;//对象无法访问

b=null;

此时对于相互引用的计数器来说引用计数器永远不为0,即相互引用的对象就永远无法被回收。

(2)可达性分析法–JVM使用的方法

可达性分析法就是内存中以称为:“GCROOTS”为根节点,向下不断搜索对象,到这个对象的路径称为引用链,如果一个对象不存在应用链,就是说这个对象到GCROOTS是不可达的,那么这个对象就会被GC回收。

引用:强引用、软引用、弱引用、虚引用

强引用:被new关键字创建的对象都是强引用,只要是被强引用关联的对象都不会被垃圾回收器回收。

软引用:指的是对象有用但不是必须的,被软引用关联的对象,在系统进行垃圾回收之前,会把北软引用关联的对象列进垃圾回收的范围内,对其进行第二次垃圾回收,如果第二次垃圾回收后内存还不够则会抛出内存溢出异常。

弱引用:比软引用强度低一些,被弱引用关联的对象一般发生在垃圾回收之前,无论内存是否足够,都会把被弱引用关联的对象进行回收。

虚引用:最弱的一个引用,被虚引用关联的对象,会在垃圾回收时收到一个系统通知。

3)怎么回收?–垃圾回收算法

(1)标记-清除算法

标记清除算法首先会把内存中的需要被回收的对象进行标记,然后被标记的对象进行统一回收。但存在2个问题:

一是效率问题,因为是对内存中所有的对象进行扫描判断是否为垃圾,在统一进行标记和回收,所以标记和清除效率都不高。二是空间问题:当对内存中的标记的对象进行回收时,会产生大量的不连续的碎片空间,当下一次要创建占用内存较大的对象时,就无法为这个对象分配足够连续的内存空间。

(2)复制算法–新生代

复制算法弥补了标记清除算法的空间问题和效率问题。标记算法原理是把堆中的内存分为大小相等的两部分,一部分用于存贮对象,每次垃圾回收只对这一部分进行处理,进行垃圾回收时,会首先把还存活的对象统一移动到另外一部分内存中去,然后对着一部分的垃圾对象进行统一回收。对于Java虚拟机而言,一般会把内存分为1个eden区2个survivor区,Eden:survivor=1:8,即内存可用空间为90%,只有10%空间被浪费。

(3)标记-清理算法–老年代

根据标记-清除算法改进而来,首先会把内存中的需要被回收的对象进行统一标记,然后把还存活的对象统一都移动到内存中的一端,把这个端边界以外的对象全部进行清理。

(4)分代收集算法

把堆的内存分为新生代和老年代,对于新生代产生的对象一般生命周期很短“朝生夕死”,大量对象死亡,少量存活,这时采用复制算法,保证可以分配连续的大的内存空间,对于老年代,生命周期较长,则采用标记-清理算法。

3.垃圾回收器
新生代:serial回收器、parNew回收器、parallel Scavenge收集器

老年代:serial Old回收器、parNew Old收集器、CMS回收器

介于新生代和老年代之间的回收器G1回收器

(1)serial回收器:单线程回收器,当进行垃圾回收时只使用一个cpu和线程,同时停止其它线程执行,“stop the world!”很霸道。采用复制算法

(2)Serial Old回收器:是serial老年代回收器,回收算法是标记-整理算法,可以与parallel Scavenge回收器进行搭配使用。

(3)parNew回收器:与serial回收器几乎一样,除了parNew是多线程的。采用复制算法

(4)parNew Old回收器,parNew回收器的老年代回收器,采用标记-整理的回收算法

(5)parallel Scavenge收集器新生代收集器,采用复制算法,目的在于尽量达到一个可控制的吞吐量,采用复制算法。

(6)CMS回收器:并发标记回收器,目的在于尽量缩短垃圾回收的停顿时间,属于老年代回收器,采用的算法是标记-清理算法

(7)G1回收器(Garbage-First),与CMS算法相比,G1回收器具有以下特点:

1).支持并发与并行。可以支持多cpu和多核环境下,来缩短垃圾回收的停顿时间,G1可以通过并发的形式让Java程序继续运行。

2).分带收集。G1回收器保留了分代的概念,可以独立管理整个GC堆,可以通过不同的方式对新生代和老年代的对象进行回收。

3).空间整合。G1回收器整体采用标记-整理回收算法,但局部采用复制算法,不会产生空间碎片,可以存放占用较大内存的对象。

4).G1回收器可以设定垃圾回收的停顿时间,相比于CMS这是一大优势,降低停顿时间时G1和CMS的共同目标,G1回收器实现预测停顿时间的原因是:Java把划分为很多个相等的Region空间,不在物理隔离,Java会在后台维护一个优先列表,这个列表是region的按回收价值排序的,G1会优先回收回收价值大的region,可以保证空间中垃圾最大化的被回收。

G1回收器回收过程:初始标记——>并发标记——>最终标记——>筛选回收

初始标记:标记能关联到的GCROOTS的对象

并发标记:从GCROOTS到对象进行可达性分析,找出存活的对象

最终标记:修正由于程序运行导致在并发标记中发生改变的标记记录

筛选回收:对region按回收价值排序,按设定的停顿时间进行回收

新生代GC(Minor GC)比老年代GC(Full GC)要快10倍以上
如果对象在新生代中的Eden中出生,并经过一次Minor GC后会进入survivor区,再在survivor中经过一次Minor GC后进入老年代。可通过:-XX:MAXTenuring Threshold设置老年代阈值。
MInor GC与Full GC分别在什么时候发生?什么时候触发FULL GC
Minor GC(又叫Young GC):回收New Generation内存空间(Eden、From Survivor、To Survivor);

Full GC:回收New Generation和Tenured Generation、PermGen内存空间

1 Minor GC/Young GC触发

当新对象生成,并且在Eden申请空间失败时,就会触发Minor GC,对Eden区域进行GC,清除非存活对象,并且把尚且存活的对象移动到Survivor区,然后整理Survivor的两个区。这种方式的GC是对New区的Eden区进行,不会影响到Old区。因为大部分对象都是从Eden区开始的,同时Eden区不会分配的很大,所以Eden区的Minor GC会频繁进行。因而,一般在这里需要使用速度快、效率高的算法,使Eden去能尽快空闲出来。

2 Full GC触发

Full GC要对整个Heap区进行回收,包括New、Old和PermGen,所以比Minor GC要慢,因此应该尽可能减少Full GC的次数。在对JVM性能调优的过程中,很大一部分工作就是对于Full GC的调节。有如下原因可能导致Full GC:

·Tenured区被写满

·PermGen区被写满

·System.gc()被显示调用

·上一次GC之后Heap的各域分配策略动态变化

需要注意的是,Full GC与YoungGC是无法完全区分开来的,很多情况下,Full GC是由YoungGC导致的。例如在Eden Space中的对象经历过几次垃圾回收,依然还存活,就会移动到Tenured区,而如果此时Tenured区空间不够,就会出发垃圾回收
3.强引用、软引用、弱引用、虚引用
强引用:被new关键字创建的对象都是强引用,只要是被强引用关联的对象都不会被垃圾回收器回收。

软引用:指的是对象有用但不是必须的,被软引用关联的对象,在系统进行垃圾回收之前,会把北软引用关联的对象列进垃圾回收的范围内,对其进行第二次垃圾回收,如果第二次垃圾回收后内存还不够则会抛出内存溢出异常。

弱引用:比软引用强度低一些,被弱引用关联的对象一般发生在垃圾回收之前,无论内存是否足够,都会把被弱引用关联的对象进行回收。

虚引用:最弱的一个引用,被虚引用关联的对象,会在垃圾回收时收到一个系统通知。
**4、类加载器、双亲委派模型、一个类的生命周期、类是如何加载到JVM中的
从java虚拟机角度来讲,只存在两种不同的类加载器:

   (1)一种是启动类加载器,由C++语言实现的,属于虚拟机的一部分;

   (2)一种是所有的其他类加载器,这些都是由Java实现的,独立于虚拟机外部,继承自java.lang.ClassLoader;

  但是从开发人员角度来讲,应该分的再细一些,绝大部分程序都使用到了以下三种系统提供的类加载器:

   (1)启动类加载器,该加载器是C++实现的,它负责加载存放于<JAVA_HOME>\lib目录下的类,它是仅仅按照文件的名字来识别的,名字不符合的类就算放到该目录下,也是毫无卵用的.....

   (2)扩展类加载器,它是负责加载<JAVA_HOME>\lib\Ext目录下的;

   (3)应用程序类加载器,这个类也被称为系统类加载器,它是负责用户类路径classpath上指定的类库,开发者可以直接使用这个加载器;

应用程序都是由这三种加载器相互配合进行加载的,有必要的话,还可以实现属于自己的类加载器,这几种加载器关系如图:
在这里插入图片描述
这种层次结构我们就称之为双亲委派模型,可以很直观的看出除了顶层的启动类加载器外,其他的都有属于自己的父类加载器。但是我们在这里不要混淆一个概念,就是继承(Inheritance),这个结构图并不是继承关系而是通过组合的方式来实现向上委托的…

   双亲委派的工作流程就是:如果一个类加载器收到了类加载的请求,它是不会自己立马亲自动手去加载的(比较懒,哈哈!),而是把该请求委托给父类,每一层都是如此,到了顶层后,这时就无法再向上传递了,所有的请求都集中到了启动类加载器,当父类反馈自己无法满足这个请求时,这时就会再把请求一层层向下传递。

   这样的好处是啥??相信大家看这种层次结构应该很清晰,但是这有什么意义吗?比如java.lang.Object,他是存在rt.jar里的,不论哪种加载器,是系统自带的也好还是我们自己实现的也好,都会把请求一层层的往上委托,直到启动类加载器,而启动类加载器一看,自己是有这个类的,所以加载,因此Object在程序的各个类加载器的加载下永远都是同一个类。反之,没有双亲委派模型,任由各个类加载器自己去加载的话,比如我们开发者自己写了Object类,包名也是java.lang,那么系统中就会出现各种各样的Object,每一个层级的类加载器都加载了自己具有个性的Object,那么作为程序中这么基础这么重要的Object,他的唯一性得不到保证,应用程序就会杂乱不堪。

在这里插入图片描述

从图中的代码,我们大致可以看出这个委托机制是如何实现的,当一个加载器收到请求后,首先会判断一下当前这个类是否已经被加载过,如果没有被加载的话,开始委托父类加载器了(就是这么懒,哈哈),如果没有父类的话,就默认使用启动类加载器。如果抛异常了,就代表当前类加载器的父类无法加载,满足不了请求,那么此时只能自己亲自出马了!!所以什么事还是自己来做的靠谱啊哈哈。
5、类加载的过程:加载、验证、准备、解析、初始化**
1)Class Loader 类加载器

负责加载.class文件,class文件在文件开头有特定的文件标识,并且ClassLoader 负责class文件的加载。

类的生命周期:加载 ——> 验证 ——> 准备 ——> 解析 ——> 初始化 ——> 使用 ——> 卸载

1、加载:

① 通过一个类的全限定名,来获取此类的二进制字节码;

② 将这个字节码所代表的静态存储结构转化为方法区的运行时数据结构;

③ 在Java堆中生成一个代表这个类的Class对象,作为方法区这些数据的访问路口。

2、验证:文件格式验证、元数据验证、字节码验证等。

3、准备:准备阶段是正式为静态变量分配内存并设置静态变量初始值(各数据类型的零值)的阶段,这些内存将在方法区中进行分配。但是如果静态变量的字段属性为常量,则会初始化为指定的值,而不是零值。

4、解析:解析阶段是在虚拟机将符号引用替换为直接引用的过程。直接引用可以是直接指向目标的指针、相对偏移量。

5、初始化:为类的静态变量赋予指定的初始值。

6、使用:使用类之前,必须对类进行实例化,如new或者反射。只有实例化了,才能通过对象的引用来访问对象的实例。

7、卸载:执行垃圾回收。

使用优化算法,以优化VMD算法的惩罚因子惩罚因子 (α) 和分解层数 (K)。 1、将量子粒子群优化(QPSO)算法与变分模态分解(VMD)算法结合 VMD算法背景: VMD算法是一种自适应信号分解算法,主要用于分解信号为不同频率带宽的模态。 VMD的关键参数包括: 惩罚因子 α:控制带宽的限制。 分解层数 K:决定分解出的模态数。 QPSO算法背景: 量子粒子群优化(QPSO)是一种基于粒子群优化(PSO)的一种改进算法,通过量子行为模型增强全局搜索能力。 QPSO通过粒子的量子行为使其在搜索空间中不受位置限制,从而提高算法的收敛速度与全局优化能力。 任务: 使用QPSO优化VMD中的惩罚因子 α 和分解层数 K,以获得信号分解的最佳效果。 计划: 定义适应度函数:适应度函数根据VMD分解的效果来定义,通常使用重构信号的误差(例如均方误差、交叉熵等)来衡量分解的质量。 初始化QPSO粒子:定义粒子的位置和速度,表示 α 和 K 两个参数。初始化时需要在一个合理的范围内为每个粒子分配初始位置。 执行VMD分解:对每一组 α 和 K 参数,运行VMD算法分解信号。 更新QPSO粒子:使用QPSO算法更新粒子的状态,根据适应度函数调整粒子的搜索方向和位置。 迭代求解:重复QPSO的粒子更新步骤,直到满足终止条件(如适应度函数达到设定阈值,或最大迭代次数)。 输出优化结果:最终,QPSO算法会返回一个优化的 α 和 K,从而使VMD分解效果最佳。 2、将极光粒子(PLO)算法与变分模态分解(VMD)算法结合 PLO的优点与适用性 强大的全局搜索能力:PLO通过模拟极光粒子的运动,能够更高效地探索复杂的多峰优化问题,避免陷入局部最优。 鲁棒性强:PLO在面对高维、多模态问题时有较好的适应性,因此适合海上风电时间序列这种非线性、多噪声的数据。 应用场景:PLO适合用于优化VMD参数(α 和 K),并将其用于风电时间序列的预测任务。 进一步优化的建议 a. 实现更细致的PLO更新策略,优化极光粒子的运动模型。 b. 将PLO优化后的VMD应用于真实的海上风电数据,结合LSTM或XGBoost等模型进行风电功率预测。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值