讯飞--对于jvm的考察(包括Full gc连环问)

  1. jvm内存划分

Java堆

Java堆是java虚拟机所管理的内存中最大的一块,是被所有线程都共享的内存区域。存在的唯一目的就是存放对象实例,几乎所有的对象实例都在这里进行分配内存。不过目前随着技术的不断发展,也并不是所有的对象实例都在堆中分配内存,可能也存在栈上分配。由于所占空间大,又存放各种实例对象,因此java虚拟机的垃圾回收机制主要管理的就是此区域,详细的垃圾回收方法以后会提到。JVM规范中规定堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可。并且可以通过-Xmx和-Xms来扩展堆的内存大小,如果在堆中没有足够的内存为实例分配,并且堆也无法在扩展时,就会报OutOfMemoryError异常。

方法区

跟Java堆一样,方法区是各个线程共享的内存区域,此区域是用来存储类的信息(类的名称、字段信息、方法信息)、静态变量、常量以及编译器编译后的代码。JVM规范中并不区分方法区和堆,只把方法区描述为堆的逻辑部分,但是它却有一个别名叫做非堆(Non-Heap),目的就是与Java堆区分开。根据垃圾回收机制中分代回收的思想,如果在HotSpot虚拟机上开发,可以把方法区称为“永久代”(只是可以这么理解,但实质是不一样的),垃圾回收机制在Java堆中划分一个部分称为永久代,用此区域来实现方法区,这样HotSpot的垃圾收集器就可以像管理Java堆一样管理这部分内存,而不必为方法区开发专门的内存管理器。

运行时常量池

运行时常量池是方法区的一个部分,class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译期间生成的各种字面量和符号引用,这部分内容会在类加载后进入方法区的运行时常量池中。Java 虚拟机对 Class 文件的每一部分(自然也包括常量池)的格式都有严格的规定,每一个字节用于存储哪种数据都必须符合规范上的要求,这样才会被虚拟机认可、装载和执行。

程序计数器

虽然在上图中程序计数器的面积很大,但实际上它是一块较小的内存空间,可以看做当前线程所执行字节码的行号指示器。字节码解释器在工作中时下一步该干啥、到哪了,就是通过它来确定的。大家都知道在多线程的情况下,CPU在执行线程时是通过轮流切换线程实现的,也就是说一个CPU处理器(假设是单核)都只会执行一条线程中的指令,因此为了线程切换后能恢复到正确的执行位置,每个线程都要有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。很明显,程序计数器就是线程私有的。如果线程正在执行的是一个java方法,程序计数器记录的是正在执行的虚拟机字节码指令地址;如果执行的Native方法,程序计数器记录的值为空(Undefined),此内存区域是java中唯一一个在java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。

Java虚拟机栈

我们经常会把java内存粗糙的分为两个部分,堆和栈,Java虚拟机栈就是栈这一部分,或者说是虚拟机栈中局部变量表部分。跟程序计数器一样,虚拟机栈也是线程私有的,它的生命周期跟线程相同。每个方法在执行的同时都会创建一个栈帧(Stack Frame),每个栈帧对应一个被调用的方法,栈帧中用于存储局部变量表、操作数栈、动态链表、方法出口等信息。每一个方法从开始执行到结束就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

局部变量表:顾名思义,他就是用来存储方法中的局部变量(包括在方法中生命的非静态变量以及函数形参),对于基本数据类型,直接存值,对于引用类型的变量,存储指向该对象的引用。由于它只存放基本数据类型的变量、引用类型的地址和返回值的地址,这些类型所需空间大小已知且固定,所以当进入一个方法时,这个方法需要在栈帧中分配多大的局部变量空间是完全可以确定的,在方法运行期间也不会改变局部变量表的大小。

指向运行常量池的引用:在方法执行过程中难免会使用到类中定义的常量,因此栈帧中要存放一个指向运行时常量池的引用。

方法返回地址:当一个方法执行结束后,要返回到之前调用它的地方,因此在栈帧中需要保存一个方法返回地址。

本地方法栈

本地方法栈与虚拟机栈的功能非常的相似,区别不过是虚拟机栈为虚拟机执行java方法服务,而本地方法栈为虚拟机执行Native方法服务。有的虚拟机并不会区分本地方法栈和虚拟机栈,比如Sun HotSpot虚拟机直接将两个合二为一。

Java内存区域详解(重点) | JavaGuide

 

 

2.new创建的对象一定在堆吗,局部变量是基本类型创建在哪,如果基本类型是成员变量呢

(1)Java中的对象不一定是在堆上分配的,因为JVM通过逃逸分析,能够分析出一个新对象的使用范围,并以此确定是否要将这个对象分配到堆上。

逃逸分析的概念

先以官方的形式来说下什么是逃逸分析。逃逸分析就是:一种确定指针动态范围的静态分析,它可以分析在程序的哪些地方可以访问到指针。

在JVM的即时编译语境下,逃逸分析将判断新建的对象是否逃逸。即时编译判断对象是否逃逸的依据:一种是对象是否被存入堆中(静态字段或者堆中对象的实例字段),另一种就是对象是否被传入未知代码。

直接说这些概念,确实有点晕啊,那我们就来两个示例。

一种典型的对象逃逸就是:对象被复制给成员变量或者静态变量,可能被外部使用,此时变量就发生了逃逸。

在ObjectEscape类中,存在一个成员变量user,我们在init()方法中,创建了一个User类的对象,并将其赋值给成员变量user。此时,对象被复制给了成员变量,可能被外部使用,此时的变量就发生了逃逸。

另一种典型的场景就是:对象通过return语句返回。如果对象通过return语句返回了,此时的程序并不能确定这个对象后续会不会被使用,外部的线程可以访问到这个变量,此时对象也发生了逃逸。

逃逸分析的优点

逃逸分析的优点总体上来说可以分为三个:对象可能分配在栈上、分离对象或标量替换、消除同步锁。我们可以使用下图来表示。

1对象可能分配在栈上

JVM通过逃逸分析,分析出新对象的使用范围,就可能将对象在栈上进行分配。栈分配可以快速地在栈帧上创建和销毁对象,不用再将对象分配到堆空间,可以有效地减少 JVM 垃圾回收的压力。

2分离对象或标量替换

当JVM通过逃逸分析,确定要将对象分配到栈上时,即时编译可以将对象打散,将对象替换为一个个很小的局部变量,我们将这个打散的过程叫做标量替换。将对象替换为一个个局部变量后,就可以非常方便的在栈上进行分配了。

3同步锁消除

如果JVM通过逃逸分析,发现一个对象只能从一个线程被访问到,则访问这个对象时,可以不加同步锁。如果程序中使用了synchronized锁,则JVM会将synchronized锁消除。

这里,需要注意的是:这种情况针对的是synchronized锁,而对于Lock锁,则JVM并不能消除。

要开启同步消除,需要加上 -XX:+EliminateLocks 参数。因为这个参数依赖逃逸分析,所以同时要打开 -XX:+DoEscapeAnalysis 选项。

所以,并不是所有的对象和数组,都是在堆上进行分配的,由于即时编译的存在,如果JVM发现某些对象没有逃逸出方法,就很有可能被优化成在栈上分配。

(2)局部变量是基本类型:

当我们在方法中声明一个基本类型的局部变量时,这个变量会存储在java虚拟机的栈内存中。具体来说,这个局部变量会存储在方法区的栈帧(Stack Frame)的局部变量表(Local VariableTble)中。每个方法被调用时,JVM都会为该方法创建一个新的栈帧,并在该栈帧的局部变量表中为

方法的所有局部变量分配空间。当方法执行完毕时,这个栈帧就会被销毁,局部变量也随,之被销毁。

(3)基本类型是成员变量:

成员变量(无论是静态的还是非静态的)都是类的属性,并且与类相关联。当成员变量是基本类型时,它们会作为对象实例的一部分存储在堆内存中。每个对象实例在堆上都有一个独立的存储空间,其中包含该刻对象所有成员变量的值。静态成员变量(也称为类变量)则存储在Java的方法区中。

3.jvm堆内存详细说一下,为什么要这么划分,用的什么垃圾回收算法

Java内存区域详解(重点) | JavaGuide

JVM垃圾回收详解(重点) | JavaGuide

4.什么时候会发生full gc

1. 调用 System.gc()

只是建议虚拟机执行 Full GC,但是虚拟机不一定真正去执行。不建议使用这种方式,而是让虚拟机管理内存。

2. 未指定老年代和新生代大小,堆伸缩时会产生fullgc,所以一定要配置-Xmx、-Xms

3. 老年代空间不足

老年代空间不足的常见场景比如大对象、大数组直接进入老年代、长期存活的对象进入老年代等。

为了避免以上原因引起的 Full GC,应当尽量不要创建过大的对象以及数组。

除此之外,可以通过 -Xmn 虚拟机参数调大新生代的大小,让对象尽量在新生代被回收掉,不进入老年代。

还可以通过 -XX:MaxTenuringThreshold 调大对象进入老年代的年龄,让对象在新生代多存活一段时间。

在执行Full GC后空间仍然不足,则抛出错误:java.lang.OutOfMemoryError: Java heap space

4. JDK 1.7 及以前的(永久代)空间满

在 JDK 1.7 及以前,HotSpot 虚拟机中的方法区是用永久代实现的,永久代中存放的为一些 Class 的信息、常量、静态变量等数据。

当系统中要加载的类、反射的类和调用的方法较多时,永久代可能会被占满,在未配置为采用 CMS GC 的情况下也会执行 Full GC。

如果经过 Full GC 仍然回收不了,那么虚拟机会抛出java.lang.OutOfMemoryError PermGen space

为避免以上原因引起的 Full GC,可采用的方法为增大Perm Gen或转为使用 CMS GC。

5. 空间分配担保失败

空间担保,下面两种情况是空间担保失败:

1、每次晋升的对象的平均大小 > 老年代剩余空间

2、Minor GC后存活的对象超过了老年代剩余空间

注意GC日志中是否有promotion failed和concurrent mode failure两种状况,当出现这两种状况的时候就有可能会触发Full GC。

promotion failed 是在进行 Minor GC时候,survivor space空间放不下只能晋升老年代,而此时老年代也空间不足时发生的。

concurrent mode failure 是在进行CMS GC过程,此时有对象要放入老年代而空间不足造成的,这种情况下会退化使用Serial Old收集器变成单线程的,此时是相当的慢的。

5.full gc对程序的影响

1.应用程序停顿:在Full GC进行期间,JVM(Jva虚拟机)会暂停所有的应用程序线程,等待垃圾回收完成之后再恢复线程的执行。这意味着在「uGC期间,应用程序无法执行任何操作,会导致应用程序出现长时间的停顿和卡顿,影响用户体验。

2.系统性能损耗:由于FuGC需要遍历整个堆内存,所以它的耗时较长,会消耗大量的系统资源,导致系统性能下降。如果Fu川GC发生的频率过高或回收的对象数量过大,会导致系统性能损耗更加严重。

3.内存不足:如果Full GC无法回收足够的内存,会导致OutOfMemoryError(OOM)错误,从而导致系统崩渍。这种情况下,通常需要增加堆内存的大小或优化代码实现,以减少内存的使用。

6.怎么解决full GC

1.增大堆内存:通过增加Jva堆内存的大小,可以减少Full GC的频率,从而降低系统卡死的风险。可以使用-x和-s参数来调整VM的最大和初始堆内存大小。

2.检查内存泄世漏:定期检查应用程序,确保没有潜在的内存泄漏问题。内存泄漏会导致堆内存不断增长,最终触发Full GC。可以使用工具如jconsole、.jvisualvm或MAT(Memory Analyzer Tool)来分析堆内存和查找内存泄世漏。

3.优化大对象的处理:对于频繁创建的大对象,可以考虑使用对象池或重用对象,以减少Full GC的负担。此外,也可以尝试调整VM参数,如新生代大小、Edn区和Survivor区的比例等,以适应大对象的处理

4.优化Finalizer的使用:尽量避免过度使用Finalizer,确保Finalizer方法的执行时间尽量短暂。Finalizer的过度使用可能会导致对象无法及时被回收,从而增加Full GC的频率

5.调整并发线程数:根据应用程序的负载和硬件环境,调整垃圾回收器的并发线程数,以平衡垃圾回收和应用程序的执行。过多的并发线程可能会导致系统资源竞争,影响性能。

6.优化代码实现:通过优化代码实现,减少不必要的对象创建和销毁,降低内存的使用。例如,使用缓存技术来减少数据库查询次数,避免频繁创建和销毁对象等。

7.选择合适的垃圾回收器:根据应用程序的特点和性能需求,选择合适的垃圾回收器。例如,对于延迟敏感的应用,可以选择G1垃圾回收器;对于内存占用较多的应用,可以选择CMS垃圾回收器等。

综上所述,解决Full GC的问题需要综合考虑多个方面,包括调整VM参数、优化代码实现、检查和修复内存泄漏等。通过综合运用这些解决方法,可以降低Fu川GC的频率和影响,提高系统的稳定性和性能。

7.线上系统发生了full gc该如何快速解决

1.快速恢复系统:

首先,需要尽快恢复系统的正常使用。这可能包括使用机器扩容、服务重启、接口限流等手段来维持服务的正常运行,以减轻Full GC带来的压力,并给问题定位争取时间。

2.查看监控和日志:

使用监控工具查看系统性能指标,如CPU、内存、GC次数等,了解Full GC发生的频率和严重程度。

查看GC日志,分析Full GC的原因。GC日志可以提供关于每次GC的详细信息,如GC的类

型、GC的时间、回收的内存量等。

3.定位立问题原因:

通过分析监控数据和GC日志,定位导致Full GC的具体原因。可能的原因包括内存泄漏、不合理的内存分配、过多的临时对象、不合理的对象引用等。

使用JVM调优工具,如jmap、jstack、jvisualvm等,进一步分析内存使用情况、线程堆栈信息等,帮助定位问题。

4.紧急措施:

如果FullGC导致系统无法正常运行,可以考虑采取紧急措施,如回滚到上一个稳定版本,暂时缓解问题。

如果问题是由新上线的代码导致的,需要尽快回滚到上一个版本,并修复问题后再重新上线。

  • 39
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值