当前位置: 首页 > news >正文

Java 8 内存管理原理解析及内存故障排查实践

介绍Java8虚拟机的内存区域划分、内存垃圾回收工作原理解析、虚拟机内存分配配置,介绍各垃圾收集器优缺点及场景应用、实践内存故障场景排查诊断,方便读者面临内存故障时有一个明确的思路和方向。

一、背景

Java是一种流行的编程语言,可以在不同的操作系统上运行。它具有跨平台、面向对象、自动内存管理等特点,Java程序在运行时需要使用内存来存储数据和程序状态。

Java的自动内存管理机制是由 JVM 中的垃圾收集器来实现的,垃圾收集器会定期扫描堆内存中的对象,检测并清除不再使用的对象,以释放内存资源。

Java的自动内存管理机制带来了许多好处,首先,它可以避免程序员手动管理内存时的错误,例如内存泄漏和悬空指针等问题。其次,它可以提高程序的运行效率,因为程序员不需要频繁地手动分配和释放内存,而是可以将更多时间和精力专注于程序的业务逻辑,最后,它可以提高程序的可靠性和稳定性,因为垃圾收集器可以自动检测和清除不再使用的内存资源,避免内存溢出等问题。

了解和掌握垃圾收集器原理可以帮助提高程序的性能、稳定性和可维护性。

名词解释:

响应速度:响应速度指程序或系统对一个请求的响应有多迅速。比如,用户查询数据响应时间,对响应速度要求很高的系统,较大的停顿时间是不可接受的。

吞吐量:吞吐量关注在一个特定时间段内应用系统的最大工作量,例如每小时批处理系统能完成的任务数量,在吞吐量方面优化的系统,较长的GC停顿时间也是可以接受的,因为高吞吐量应用更关心的是如何尽可能快地完成整个任务,不考虑快速响应用户请求。

GC导致的应用暂停时间影响系统响应速度,GC处理线程的CPU使用率影响系统吞吐量。

二、Java 的内存管理

2.1 JVM(Java虚拟机)内存划分

Java运行时数据区域划分,Java虚拟机在执行Java程序时,将其所管理的内存划分为不同的数据区域,每个区域都有特定的用途和创建销毁的时间。其中,有些区域在虚拟机进程启动时就存在,而有些区域则是随着用户线程的启动和结束而建立和销毁。这些数据区域包括程序计数器、虚拟机栈、本地方法栈、堆、方法区等,每个区域都有其自身的特点和作用。了解这些数据区域的使用方式和特点,可以更好地理解Java虚拟机的内存管理机制和运行原理。

JVM的内存区域划分可分为:1.堆内存空间、2.Java虚拟机栈区域、3.程序计数器、4.本地方法栈、5.元空间区域、6.直接内存。

图片

图片

  • 堆内存空间:JVM中占用内存空间最大的是堆,平常对象的创建大部分都是在堆上分配内存的,是垃圾回收的主要目标和方向。

  • 本地方法栈区域:Native Mehod Stack与Java虚拟机栈的作用非常相似,区别是Java虚拟机栈为虚拟机执行Java方法或者为字节码而服务,本地方法栈是为了Java 虚拟机栈得到Native方法。

  • Java虚拟机栈区域:负责Java的解释过程、程序的执行过程、入栈和出栈,它是与线程相关的,当启动一个新的线程时,Java程序就会分配一个Java 虚拟机栈提供运行;Java 虚拟机栈从方法入栈到具体字节码执行是一个双层栈结构,可以栈里包含栈。

  • 程序计数器:记录线程执行位置,线程私有,因为操作系统不停的调度,无法获取到线程被调度之前的位置,程序计数器提供了这样一个线程执行位置。

  • 元空间区域:在原来的老的Java 7之前划分中,永久代用来存放类的元数据信息、静态变量以及常量池等。在现在Java8后类的元信息存储在元空间中,静态变量和常量池等并入堆中,相当于原来的永久代中的数据,被元空间和堆内存给瓜分了。

  • 直接内存:使用了Java 的直接内存的API的内存,例如缓冲ByteBuffer,可以控制虚拟机参数调整大小,而本地内存是使用了native函数操作的内存,是不受JVM管理控制。

堆内存空间

JVM回收的主要目标是堆内存,对象主要的创建分配内存在堆上进行,堆可以想象成一个对象池子,对象不停创建放入池子中,而JVM垃圾回收是不停的回收池子中一些被标记为可回收对象的对象,启动回收线程进行打扫战场,当回收对象的速度赶不上程序的创建时,池子就会立马满,当满了之后从而发生溢出,就是常见的OOM。

GC的速度和堆的内存中存活对象的数量有关,与堆内存所有的对象无关,GC的速度和堆内存的大小无关,如一个4GB大小的堆内存和一个16GB的堆内存,只要2个堆内存存活对象都是一样多的时候,GC速度都是基本差不多。每次垃圾回收也不是必须要把垃圾清理干净,重要的是保证不把正在使用的对象给标记清除掉。

2.2 堆内存管理

JVM中占用内存空间最大的是堆内存,平常对象的创建大部分都是在堆上分配内存的,是Java垃圾回收的主要目标和方向、是 Java内存管理机制的核心组成部分,它可以自动管理 Java程序的内存分配和释放,Java垃圾收集器可以自动检测和回收不再使用的内存,以便重新分配给其他需要内存的程序。这种自动内存管理的机制可以提高程序的运行效率和可靠性,防止因内存泄漏等问题导致程序崩溃或性能下降,Java 垃圾收集器使用了不同的垃圾回收算法和垃圾收集器实现,以适应不同的应用场景和需求。Java垃圾收集器的性能特征和优化技术也是 Java程序员需要了解和掌握的重要知识。

因此,了解 Java垃圾回收的背景、原理和实践经验对于编写高效、可靠的 Java程序非常重要。

2.2.1 对象如何被判断为可回收

JVM怎么判断堆内存里面的对象是否可回收的,就是当一个对象没有任何引用指向它了,它就是可回收对象,判断的方式有两种算法,一个是引用计数法,一个是可达性分析法。

可回收对象:

图片

(1)引用计数法

给对象中添加一个引用计数器,每当有一个地方引用它时,这个计数器值加一,当引用失效断开时,计数器值就减一,在任何时刻时计数器为0的时候,代表这个对象是可以被回收的,没有任何引用使用它了。

图片

引用计数法是有缺点,当对象直接互相依赖引用时,这些对象的计数器都不能为0,都不能被回收。

(2)可达性分析法

它使用tracing(链路追踪)方式寻找存活对象的方法,通过一些列称为“GC Roots”的对象作为初始点,从这些初始点开始向下查找,直到向下查找没有任何链路时,代表这个对象可以被回收,这种算法是目前Java唯一且默认使用来判定可回收的算法。

图片

2.2.2 GC Roots的概念和对象类型

  1. Java 虚拟机栈中引用的对象,例如各个线程被调用的方法栈用到的参数、局部变量或者临时变量等。

  2. 方法区的静态类属性引用对象或者说Java类中的引用类型的静态变量。

  3. 方法区中的常量引用或者运行时常量池中的引用类型变量。

  4. JVM内部的内存数据结构的一些引用、同步的监控对象(被修饰同步锁)。

  5. JNI中的引用对象。

当然,被GC Roots追溯到的对象不是一定不会被垃圾回收,具体需要看情况,Java 对象与对象引用存在四种引用级别:分别是强引用、软引用、弱引用、虚引用,默认的对象关系是强引用,只有在和GCRoots没有关系时才会被回收;软引用用于维护一些可有可无的对象,当内存足够时不会被回收;弱引用只要发生了垃圾回收就会被清理;虚引用人如其名形同虚设,任何对象都与它无关。

2.2.3 垃圾对象回收算法

当JVM定位到了那些对象可回收时,这个时候是通过三个算法标记清除,分别是标记清除算法、复制算法、标记压缩算法。

(1)标记清除算法

首先标记出所有需要回 收的对象,在标记完成后,统一回收掉所有被标记的对象,但是该算法缺点是执行效率低,当大量对象时需要大量标记和清理动作,而且容易产生内存碎片化,当需要一块连续内存时,会因为碎片化无法分配。

图片

(2)标记压缩算法

标记压缩算法跟清除算法很像,只不过它对内存进行了整理, 让存活对象都向内存空间的一端移动,然后将边界的其它对象全部清理,这样能达到内存碎片化问题,不过它比清除算法多了移步动作。

图片

(3)复制算法

为了解决标记-清除算法面对大量可回收对象时执行效率低的问题,将存活对象复制到一块空置的空间里,然后将原来的区域全部清理,缺点是需要额外空间存放存活对象。

图片

2.2.4 分代垃圾回收模型概念和原理

堆内存分代模型图

图片

当JVM进行GC(垃圾回收)时,JVM会发起“Stop the world”,所有的业务线程都进行停止,进入SafePoint状态,JVM回收垃圾线程开始进行标记和追溯,如何解决这种停止和如何减少STW的时间呢?

目前主流垃圾收集器采用分代垃圾回收方式,大部分对象的声明周期都比较短,只有少部分的对象才存活的比较长,分代垃圾回收会在逻辑上把堆内存空间分为两部分,一部分为年轻代,一部分为老年代。

(1)年轻代空间

年轻代主要是存放新生成的对象,一般占用堆空间的三分之一空间,因为会频繁创建对象,所以年轻代GC频率是最高的。

分为Eden空间、Survivor1(from)区、Survivor2(to)区,S1和S2总要有一块空间是空的,为了方便年轻代存活对象来回存放,晋升存活对象年龄。

三个区的默认比例是8:1:1,可以通过配置参数调整比例。

年轻代回收发起Minor GC(YongGC),当Eden内存区域被占满之后就发起GC,短暂的STW,基于垃圾收集器。

(2)老年代空间

是堆内存中最大的空间, ,里面的对象都是比较稳定或者老顽固,GC频率不会频繁执行。

图片

老年代对象:

  1. 正常提升:由年轻代存活对象年龄到达阈值时,这个对象则会被移动到老年代中。

  2. 分配担保:如果年轻代中的空间不足时,此时有新的对象需要分配对象空间,需要依赖其它内存进行分配担保,老年代担保直接创建。

  3. 大对象:当创建需要大量连续内存空间的对象时,如长字符串或者数组等,大小超过了阈值时,直接在老年代分配。

  4. 动态年龄对象:有的垃圾收集器不需要到达指定年龄大小直接晋升老年代,比如相同年龄的对象的大小总和 > Survivor空间的50%, 年龄大于等于该年龄对象直接移动老年代,无需等待正常提升。

老年代回收发起Major GC / FULL GC,当老年代满时会触发MajorGC,通常至少经历过一次Minor GC,再紧接着进行Major GC, Major GC清理Tenured区,用于回收老年代(CMS才能单独清理)。

FUll GC:清除整个堆空间,一般来说是针对整个新生代、老生代、元空间的全局范围的清理。

不管是Major GC还是 Full GC, STW的耗时都是Ygc的十倍以上,所以说对象能在年轻代被回收是最优的。

Full GC触发条件:

  • 老年代空间不足。

  • 元空间不足扩容导致。

  • 程序代码执行System.gc时可能会执行。

  • 当程序创建一个大对象时,Eden区域放不下大对象,老年代内存担保分配,老年代也不足空间时。

  • 年轻代存留对象晋升老年代时,老年代空间不足时。

2.2.5 Java对象内存分配过程

图片

 对象的分配过程

  1. 编译器通过逃逸分析优化手段,确定对象是否在栈上分配还是堆上分配。

  2. 如果在堆上分配,则确定是否大对象,如果是则直接进入老年代空间分配, 不然则走3。

  3. 对比tlab, 如果tlab_top + size <= tlab_end, 则在tlab上直接分配,并且增加tlab_top值,如果tlab不足以空间放当前对象,则重新申请一个tlab尝试放入当前对象,如果还是不行则往下走4。

  4. 分配在Eden空间,当eden空间不足时发生YGC, 幸存者区是否年龄晋升、动态年龄、老年代剩余空间不足发生Full GC 。

  5. 当YGC之后仍然不足当前对象放入,则直接分配老年代。

TLAB作用原理:Java在内存新生代Eden区域开辟了一小块线程私有区域,这块区域为TLAB,默认占Eden区域大小的1%, 作用于小对象,因为小对象用完即丢,不存在线程共享,快速消亡GC,JVM优先将小对象分配在TLAB是线程私有的,所以没有锁的开销,效率高,每次只需要线程在自己的缓冲区分配即可,不需要进行锁同步堆 。

对象除了基本类型的不一定是在堆内存分配,在JVM拥有逃逸分析,能够分析出一个新的对象所拥有的范围,从而决定是否要将这个对象分配到堆上,是JVM的默认行为;Java 逃逸分析是一种优化技术,可以通过分析 Java 对象的作用域和生命周期,确定对象的内存分配位置和生命周期,从而减少不必要的内存分配和垃圾回收。可以在栈上分配,可以在栈帧上创建和销毁,分离对象或标量替换,同步消除。

public class TaoYiFenxi {     Object obj;     public void setObj() {        obj = new Object();    }     public Object getObject() {        Object obj1 = new Object();        return obj1;    }      public void test1() {        synchronized (new Object()) {         }    } }

2.2.6 JVM垃圾收集器特点与原理

(1)Serial垃圾收集器、Serial Old垃圾收集器

图片

Serial收集器采用复制算法, 作用在年轻代的一款垃圾收集器,串行运行,执行过程中会STW,是使用单个线程进行垃圾回收,响应速度优先。

Serial Old 收集器采用标记整理算法,作用在老年代的一款收集器,串行运行,执行过程中会暂停所有用户线程,会STW,使用单个线程进行垃圾回收,响应速度优先。

使用场景:

适合内存小几十兆以内,比较适合简单的服务或者单CPU服务,避免了线程交互的开销。

优点:

小堆内存且单核CPU执行效率高。

缺点:

堆内存大,多核CPU不适合,回收时长非常长。

(2)Parallel Scavenge垃圾收集器、Parallel Old垃圾收集器

图片

Parallel Scavenge垃圾收集器采用了复制算法,作用在年轻代的一款垃圾收集器,是并行的多线程运行,执行过程中会发生STW,关注与程序吞吐量。

Parallel Old垃圾收集器采用标记整理算法,作用,作用在老年代的一款垃圾收集器, 是并行的多线程运行,执行过程中会发生STW,关注与程序吞吐量。

Parallel Scavenge + Parallel Old组合是Java8当中默认使用的一个组合垃圾回收。

所谓的吞吐量是CPU用于运行用户代码时间与CPU总消耗时间的比值,也就是说吞吐量 = 运行用户代码时间 / (运行用户代码时间 + 垃圾收集器时间), 录入程序运行了100分钟,垃圾收集器花费时间1分钟,则吞吐量达到了99%。

使用场景:

适用于内存在几个G之间,适用于后台计算服务或者不需要太多交互的服务,保证吞吐量的服务。

优点:

可控吞吐量、保证吞吐量,并行收集。

缺点:

回收期间STW,随着堆内存增大,回收暂停时间增大。

(3)Par New垃圾收集器

Par New垃圾收集器采用了复制算法,作用在年轻代的一款垃圾收集器, 也是并行多线程运行,跟Parallel非常相似,是它的增强版本,或者说是Serial收集器的多线程版本,是搭配CMS垃圾收集器特制的一个收集器。

使用场景:

搭配CMS使用

(4)CMS垃圾收集器

CMS是一款多线程+分段操作的一款垃圾收集器。其最大的优点就是将一次完整的回收过程拆分成多个步骤,并且在执行的某些过程中可以使用户线程可以继续运行,分别有初始标记,并发标记,重新标记,并发清理和并发重置。

图片

CMS是一款多线程+分段操作的一款垃圾收集器。其最大的优点就是将一次完整的回收过程拆分成多个步骤,并且在执行的某些过程中可以使用户线程可以继续运行,分别有初始标记,并发标记,重新标记,并发清理和并发重置。

CMS分段

  • 初始标记阶段, 这个阶段会暂停用户线程, 扫描所有的根对象,因为根对象比较少,所以一般stw时间都非常短。

  • 并发标记阶段,这个阶段与用户线程一起执行,会一直沿着根往下扫描,不停的识别对象是否为垃圾,标记,采用了三色算法, 在对象头(Mark World)标识了一个颜色属性,不同的颜色代表不同阶段,扫描过程中给与对象一个颜色,记录扫描位置,防止cpu时间片切换不需要重新扫描。

  • 重新标记阶段, 这个阶段暂停用户线程, 修正一些漏标对象,回扫发生引用变化的对象。

  • 并发清理阶段, 这个阶段与用户线程一起执行,标记清除已经成为垃圾的对象。

三色标记

  • 黑色:代表了自己已经被扫描完毕,并且自己的引用对象也已经确定完毕。

  • 灰色:代表自己已经被扫描完毕了, 但是自己的引用还没标记完。

  • 白色:则代表还没有被扫描过。

标记过程结束后,所有未被标记的对象都是不可达的,可以被回收。

图片

三色标记算法的问题场景:当业务线程做了对象引用变更,会发生B对象不会被扫描,当成垃圾回收。

public class Demo3 {     public static void main(String[] args) {        R r = new R();        r.a = new A();        B b = new B();        // GCroot遍历R, R为黑色, R下面的a引用链还未扫完置灰灰色,R.b无引用, 切换时间分片        r.a.b = b;        // 业务线程发生了引用改变, 原本r.a.b的引用置为null        r.a.b = null;        // GC线程回来继续上次扫描,发现r.a.b无引用,则认为b对象无任何引用清除        r.b = b;        // GC 回收了b, 业务线程无法使用b    }} class R {    A a;    B b;} class A {    B b;} class B {}

图片

当GC线程标记A时,CPU时间片切换,业务线程进行了对象引用改变,这时候时间片回到了GC线程,继续扫描对象A, 发现A没有任何引用,则会将A赋值黑色扫描完毕,这样B则不会被扫描,会标记B是垃圾, 在清理阶段将B回收掉,错误的回收正常的对象,发生业务异常。

CMS基于这种错误标记的解决方案是采取写屏障 + 增量更新Incremental Update , 在业务线程发生对象变化时,重新将R标识为灰色,重新扫描一遍,Incremental Update 在特殊场景下还是会产生漏标。

图片

public class Demo3 {     public static void main(String[] args) {        // Incremental Update还会产生的问题        R r = new R();        A a = new A();        A b = new A();        r.a1 = a;        // GC线程切换, r扫完a1, 但是没有扫完a2, 还是灰色        r.a2 = b;        // 业务线程发生引用切换, r置灰灰色(本身灰色)        r.a1 = b;        // GC线程继续扫完a2, R为黑色, b对象又漏了~    }} class R {    A a1;    A a2;} class A {}

当GC 1线程正在标记O, 已经标记完O的属性 O.1, 准备标记O.2时,业务线程把属性O,1 = B,这时候将O对象再次标记成灰色, GC 1线程切回,将O.2线程标记完成,这时候认为O已经全部标记完成,O标记为黑色, B对象产生了漏标, CMS针对Incremental Update产生的问题,只能在remark阶段,暂停所有线程,将这些发生过引用改变过的,重新扫描一遍。

使用场景:

适用于互联网或者 B/S服务, 响应速度优先,适合6G左右。

优点:

并发收集, 低停顿,回收过程中最耗时的是并发标记和并发清除,它都能与用户线程保持一起工作。

缺点:

收集器对CPU的资源非常敏感,会占用用户线程部分使用,导致程序会变得缓慢,吞吐量下降。

无法处理浮动垃圾,在并发清理阶段用户线程还是在运行,这时候产生的新垃圾无法在这次当中处理,只有等待下次才会清理。

因为CMS使用了Incremental Update,remark阶段还是会所有暂停,重新扫描发生引用改变的GC root,效率慢耗时高。

因为收集器是基于标记清除算法实现的,所以在收集器回收结束后,内存会产生碎片化,当碎片化非常严重的时候,这时候有大对象进入无法分配内存时会触发FullGC,特殊场景下会使用Serial收集器,导致停顿不可控。

(5)G1垃圾收集器

G1也是采用三色标记分段式进行回收的算法, 不过它是写屏障 + STAB快照实现,G1设定的目标是在延迟可控(低暂停)的情况下获得尽可能高的吞吐量,仍然可以通过并发的方式让Java 程序继续运行,G1垃圾收集器在很多方面弥补了CMS的不足,比如CMS使用的是mark-sweep标记清除算法,自然会产生内存碎片(CMS只能在Full GC时,STW 整理内存碎片),然而G1整体来看是基于标记整理算法实现的收集器,但是从局部来看也是基于复制算法实现的,高效的整理剩余内存,而不需要管理内存碎片它。

G1同样有年轻代和老年代的概念,只不过物理空间划分已经不存在,逻辑分区还存在,G1会把堆切成若干份,每一份当作一个目标,在部分上目标很容易达成,G1在进行垃圾回收的时候,将会根据最大停顿时间设置值动态选取部分小堆区垃圾回收。

图片

G1的特点是尽量追求吞吐量,追求响应时间,并发收集,压缩空闲空间不会延长GC暂停时间,更容易预测GC暂停时间,能充分利用CPU、多核环境下的硬件优势,使用多个CPU对STW进行控制(200ms以内)灵活的分区回收,优先回收花费时间少的或者垃圾比例高的region新老比例也是动态调整,不需要配置;年龄晋升也是15,但是可以动态年龄,当幸存者region超过了50时,会把年龄最大的放入老年代。

G1动态Y区域设置,G1每个分区都可能是年轻代或者老年代,但是同一时刻只属于一个代,分代概念还存在,逻辑上分代方便复用以前分代逻辑,在物理上不需要连续,这样能带来额外好处,有的分区内垃圾比较多,有的分区比较少,G1会优先回收垃圾比较多的分区,这样可以花费少量的时间来回收这些分区垃圾,即收集最多垃圾分区;但是新生代回收不适合这种,新生代达到阈值时发生YGC,对整个新生代进行回收或者晋升幸存,新生代也分区是方便动态调整分区大小,在进行垃圾回收时,会将存活对象拷贝到另一个可用分区上,这样也能避免一定程度的内存碎片化过程,每个分区的大小都是在1M- 32M之间,取决2的幂次方。

Humingous:如果一个对象占用的空间超过了分区容量50%以上,G1收集器就认为这是一个巨型对象。这些巨型对象,默认直接会被分配在年老代,但是如果它是一个短期存在的巨型对象,就会对垃圾收集器造成负面影响;为了解决这个问题,G1划分了一个Humongous区,它用来专门存放巨型对象。如果一个H区装不下一个巨型对象,那么G1会寻找连续的H分区来存储。为了能找到连续的H区,有时候不得不启动Full GC。

CardTable:记录每一块card内存区域是否dirty,如果在发生YGC时,怎么知道那些是存活对象,并且其它代区域有没有引用这部分对象,于是把内存划分了很多card区域, 每个区域大小不超过512b,当该card区域里的对象有引用关系,将当前card置为“dirty”, 并且使用卡表(CardTable)来记录每一块card是否dirty,在进行GC时,不用遍历所有的空间, 只需要遍历卡表中为"dirty"或者说布尔符合条件的card区域进行回扫。

图片

CSet:Collection SET用于记录可被回收分区的集合组, G1使用不同算法,动态的计算出那些分区是需要被回收的,将其放到CSet中,在CSet当中存活的数据都会在GC过程中拷贝到另一个可用分区,CSet可以是所有类型分区,它需要额外占用内存,堆空间的1%。

RSet:RememberedSet 每个Region都有一个Rset,是一个记录了其他Region中的对象到本身Region的引用,它可以使得垃圾收集器不需要扫描整个堆去找到谁的引用了当前分区对象,是G1高效回收的关键点,也是三色算法的一个以来点。

图片

RSet和卡表的区别是什么?

卡表记录的是堆内存中card有没有变成"dirty", 但是它本身不知道dirty里面哪些是引用了的对象,它是一个大维度的一个记录,RSet是记录自身Region中对象引用了其它Region中的那些对象,详细的记录对方引用对象信息,G1使用了两者的结合,实现了增量式的垃圾回收,并优化跨区引用的最终处理。

SATB算法:是一种基于快照的算法,它可以避免在垃圾回收时出现对象漏标或者重复标记的问题,从而提高垃圾回收的准确性和效率,在垃圾回收开始时,对堆中的对象引用进行快照,然后在并发标记阶段中记录下所有被修改过对象引用,保存到satb_mark_queue中,最后在重新标记阶段重新扫描这些对象,标记所有被修改的对象,保证了准确性和效率。

SATB算法在remark阶段不需要暂停遍历整个堆对象,只需要扫描“satb_mark_queue”队列中的记录,避免了这个阶段长耗时,而cms的增量算法在这个阶段是需要重新扫描GC Roots标记整个堆对象,导致了不可控时间暂停,总的来说G1是通过回收领域应用并行化策略,将原来的几块大内存块回收问题,演变成了N个小内存块回收,使得回收效率可以高度并行化,停顿时间可控,可以与用户线程并发执行,将一块内存分而治之。

图片

G1默认当分区内存占用阈值达到总内存的45%,会发生Mixed gc(混和GC),YoungGC + 并发回收Mixed GC过程:初始标记(stw)、并发标记、最终标记(重新标记stw)、筛选回收(stw并行)。

使用场景:

响应速度优先,较高的吞吐量,面向服务端,使用内存6G以上。

优点:

并行与并发收集,分代分区收集,优先垃圾收集,空间整合,可控或者可预测停顿时间。

缺点:

收集中产生内存,G1的每个region都需要有一份记忆集和卡表记录跨代指针,这导致记忆集可能占用堆空间10-20%甚至更多空间。

执行过程中额外负载开销加大,写屏障进行维护卡表操作外,还需要原始快照能够减少并发标记和重新标记阶段的消耗,避免最终标记阶段停顿过长,运行过程中会产生由跟踪引用变化带来的额外开销负担,比CMS增量算法消耗更多,CMS的写屏障实现直接是同步操作, 而G1是把写屏障和写后屏障中要做的事情放到队列里异步处理。

G1对于Full GC是没有处理流程, 一旦发生Full GC G1的回收执行的是单线程的Serial回收器进行回收。

2.2.7 垃圾收集器配置使用

机器配置:64位 4C8G

Java 程序使用CMS收集器进行内存垃圾回收初始内存划分情况:

-Xms4096M -Xmx4096M -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/{runuser}/logs/other -XX:+UseConcMarkSweepGC

图片

CMS 跟 parNew占比情况, 默认下 ParNew占用整个堆的空间为:机器位数 * CPU核数 * 13 /10 , 当前机器配置计算得出 64 * 4 * 13 / 10 = 332M , 与图上数值差别不大。

Java程序使用G1收集器进行内存垃圾回收初始内存划分情况:

-Xms4096M -Xmx4096M -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/{runuser}/logs/other -XX:+UseG1GC

图片

G1 新老年代的占比是动态调整, 随着运行时根据实际情况划分空间。

Java8默认ParallerGC收集器初始内存划分情况:

图片

parallel GC回收器默认堆old区与young区内存大小比例 2:1, 图上数值差别不大。

三、内存诊断实践

3.1 内存快照生成

当发生线上应用告警,告警相关内存故障问题时, 应当如何进行故障排查呢?首先应用在发生内存溢出无法执行时,应DUMP当前内存快照,需要在Java程序执行启动命令时添加上:

-XX:+HeapDumpOnOutOfMemoryError 

-XX:HeapDumpPath=${filePath} 参数

 当发生时自动生成一份当前内存快照,方便与开发人员使用快照文件进行问题诊断分析。

在Java应用运行时,想手动生成内存快照,可以使用JDK自带几个问题排查工具,可以使用jmap工具生成指定PID内存快照,不过需要耗费较长的一个时间,会暂停应用程序执行,使用jcmd工具可以快速的DUMP内存快照,因为在堆转储存文件过程中,jcmd可以利用虚拟机中的一些优化技术,例如分代堆、增量式垃圾回收等技术,相比传统的jmap效率高很多,一般来说在DUMP内存前会进行一次

Full FC,可以指定屏蔽这次Full GC,保留当前所有内存中的对象。

除了自带的内存诊断工具, 也可以使用Arthas诊断工具,提供了多个命令来帮助诊断内存问题,例如 dashboard(当前Java程序内存实时数据面板)、JVM(查看当前JVM信息,包括使用的gc收集器、内存分区分布情况等信息)、heapdump(当前内存快照类似jmap命令的heap dump)、memory(当前内存分区及占用情况)、monitor(监控模式,可监控内存及查看对象占用情况)profiler(火焰图可以输出多种火焰图,内存分区占用火焰图)等相关内存命令。这些命令可以帮助获取应用程序的内存快照、堆内存使用情况等信息,能快速定位内存问题。

引用:Arthas 命令列表

3.2 dump内存快照分析

(1)jhat 是 Java 开发工具包自带的一款堆内存分析工具,它可以帮助解决 Java 应用程序的内存问题。Jhat 可以读取 Java 应用程序生成的堆转储文件,并以 HTML 格式展示内存中的对象信息和引用关系,支持 OQL 查询和灵活的过滤和排序功能。

用例  jhat E:\diydump\Java_pid2680.hprof

图片

  • All classes including platform:列举应用程序中所有类的信息,并快速定位内存问题。

  • Show all members of the rootset:显示堆内存中所有根对象的信息,包括系统对象、静态对象、本地对象等。

  • Show instance counts for all classes (including platform):显示所有类的实例数量。

  • Show heap histogram:显示程序堆内存的直方图,可以知道每个类的实例数量和占用内存大小等信息,快速知道内存泄漏原因。

(2)jvisualvm也是Java 开发工具包里自带的一款图形化工具,可以用于监控和诊断Java应用程序的性能问题。使用它可以实时查看Java 应用程序的内存使用情况、CPU使用情况、线程情况等,并可以进行内存分析、CPU分析、线程分析等内容。

以Java_pid2680.hprof为例,进行内存分析内存泄漏原因:

图片

(3)MAT 是基于Eclipse的内存分析工具,是一个快速、功能丰富的Java内存分析工具,能够快速的分析出dump文件中各项结果,快速给出内存泄漏原因报告。

还是以Java_pid2680.hprof文件进行分析,比原生的jhat方便很多,功能也比原生的更加丰富:

图片

MAT的一些常用功能点介绍(如图所示):

  • Overview 标签内容有比较多块内容,其中details末块介绍总共使用内存大小,类的数量,实例的数量,类的加载器,以及实例的内存直方图;

  • Biggest Objects by Retained Size模块,使用了饼状图列出了当前内存中占用最大的几个对象,按照百分比划分,点击不同的饼状块能够看到具体对象及其对象属性等信息;

  • actions模块,这里拥有不同的分析功能,Histogram生成视图列出每个类所对应的对象个数以及占用内存大小,Dominator Tree生成视图寻找出大对象,每个实例对象的内存占比比重;

  • Reports模块是生成报告,其中Leak Suspects可以自动分析内存泄漏主要原因报告,可以通过报告准确定位泄漏原因或者可能造成泄漏的原因,并且可以定位到具体累积实例,线程stack等信息。

例子中:leak Suspects报告给出“0xfe3be480” 非常多内存, Gc root Thread 所引用,在发生gc时,不是可回收对象,无法回收内存,导致内存溢出。

图片

四、总结

本文介绍了Java程序中的内存模型,内存模型划分多份内存区域,不同区域的作用介绍及不同区域的线程之间的内存共享范围,可以帮助开发人员更加理解Java 中内存管理的机制和原理。

堆是内存模型中最大的一块内存区域,以堆的空间划分详细的介绍了内存分代,部分垃圾收集器即是物理分代和逻辑分代,G1收集器则物理不分代逻辑保留了以前分代,讲述了不同收集器的原理实现和优缺点,可以根据项目的业务属性,机器配置等因素选择最优的收集器,帮助程序使用最优的收集器可以使得程序的吞吐量和响应速度达到最佳状态。还讲述了不同的参数调优收集器,并且当发生了程序内存溢出崩溃,如何进行内存分析,介绍不同工具的使用,快速定位内存溢出的罪魁祸首,从而在代码层面上根本解决这类问题。

原文地址: Java 8 内存管理原理解析及内存故障排查实践

相关文章:

Java 8 内存管理原理解析及内存故障排查实践

介绍Java8虚拟机的内存区域划分、内存垃圾回收工作原理解析、虚拟机内存分配配置&#xff0c;介绍各垃圾收集器优缺点及场景应用、实践内存故障场景排查诊断&#xff0c;方便读者面临内存故障时有一个明确的思路和方向。 一、背景 Java是一种流行的编程语言&#xff0c;可以在不…...

RH850从0搭建Autosar开发环境【3X】- Davinci Configurator之RTE模块配置详解(上)

RTE模块配置详解 - 上 一、RTE模块配置实操1.1 打开RTE模块1.2 RTE模块错误消除在这里插入图片描述 这里我们利用工具的自动处理功能。二、Configurator工具Validation总结本节我们就手把手详解RTE配置实现,其实也没有什么过多的操作。。。这个模块更多是工具自动处理的。 一、…...

小米汽车su7全色系展示源码

源码简介 小米汽车全色系展示源码&#xff0c;小米汽车su7全色系展示源码 安装教程 纯HTML&#xff0c;直接将压缩包上传网站目录解压即可 首页截图 源码下载 小米汽车su7全色系展示源码-小8源码屋源码简介 小米汽车全色系展示源码&#xff0c;小米汽车su7全色系展示源码 …...

钉钉事件订阅前缀树算法gin框架解析

当钉钉监测到发生一些事件&#xff0c;如下图 此处举例三个事件user_add_org、user_change_org、user_leave_org&#xff0c;传统的做法是&#xff0c;我们写三个if条件&#xff0c;类似下图 这样字符串匹配效率比较低&#xff0c;于是联想到gin框架中的路由匹配算法&#xff0…...

React18从入门到实战

文章目录 一、React环境的搭建二、项目文件的介绍&#xff08;1&#xff09;package.json&#xff0c;他是项目存放依赖包的地方&#xff0c;里面包括了一些项目核心包及下载的其他插件包&#xff08;2&#xff09;src文件夹是项目源码目录&#xff0c;平时开发页面就在其中&am…...

【漏洞复现】某科技X2Modbus网关多个漏洞

漏洞描述 最近某科技X2Modbus网关出了一个GetUser的信息泄露的漏洞,但是经过审计发现该系统80%以上的接口均是未授权的,没有添加相应的鉴权机制,以下列举多个未授权接口以及获取相关敏感信息的接口。 免责声明 技术文章仅供参考,任何个人和组织使用网络应当遵守宪法法律…...

专业140+总410+国防科技大学831信号与系统考研经验国防科大电子信息与通信,真题,大纲,参考书。

应群里同学要求&#xff0c;总结一下我自己的复习经历&#xff0c;希望对大家有所借鉴&#xff0c;报考国防科技大学&#xff0c;专业课831信号与系统140&#xff0c;总分410&#xff0c;大家以前一直认为国防科技大学时军校&#xff0c;从而很少关注这所军中清华&#xff0c;现…...

【Linux】进程管理(2):进程控制

一、进程创建&#xff1a;fork函数 我们在命令行中输入man fork 即可得到fork函数的函数接口的函数的使用方法。 我们可以看到&#xff0c;fork函数位于man手册的第2部分&#xff0c;由于第2部分通常是用于描述系统调用和库函数&#xff0c;所以我们可以了解到fork函数实际是一…...

组合数(费马小定理, 快速幂)

给定 n 组询问&#xff0c;每组询问给定两个整数 a&#xff0c;b&#xff0c;请你输出 Cbamod(1097)的值。 输入格式 第一行包含整数 n。 接下来 n 行&#xff0c;每行包含一组 a 和 b。 输出格式 共 n 行&#xff0c;每行输出一个询问的解。 数据范围 1≤n≤10000, 1≤…...

VMware Esxi安装群辉系统

群晖的网络存储产品具有强大的操作系统&#xff0c;提供了各种应用程序和服务&#xff0c;包括文件共享、数据备份、多媒体管理、远程访问等。用户可以通过简单直观的界面来管理他们的存储设备&#xff0c;并且可以根据自己的需求扩展设备的功能。总的来说&#xff0c;群晖的产…...

arm交叉编译器工具

下载地址&#xff1a; Builds & Downloads | Linaro 进入首页后&#xff0c;点击" GNU Toolchain Integration Builds" 有以下版本&#xff1a; 根据自己的选择下载对应的版本&#xff0c;本例选择14.0-2023.06-1 根据板端对应的版本选择相应的下载 比如下载3…...

Dajngo -- 表单

表单 form 此后的 app 名以 polls 为例 <h1>{{ question.question_text }}</h1>{% if error_message %} <p> <strong>{{ error_message }}</strong> </p> {% endif %}<form action"{% url polls:vote question.id %}" method…...

NIO基础知识

在学习Netty之前先要学习一下NIO相关的知识&#xff0c;因为Netty是基于NIO搭建的一套网络编程框架。 一. NIO 基础 non-blocking io 非阻塞 IO 1. 三大组件 1.1 Channel & Buffer channel 有一点类似于 stream&#xff0c;它就是读写数据的双向通道&#xff0c;可以从…...

C语言正则表达式 regnext regreplace regreplaceAll

由于C语言的正则表达式API相对比较简易.默认API只有regcomp/regerror/regexec/regfree这些函数.相对于其他的高级语言中正则表达式所所能实现的功能(如:查找/替换)有所欠缺.所以想着自己写下一些需要的函数以备后续需要使用. #ifndef _E_REGEX_INCLUDE #define _E_REGEX_INCLU…...

使用aspose相关包将excel转成pdf 并导出

SpringBoot 项目 基于aspose相关jar包 将excel 转换成pdf 导出 1、依赖的jar包 &#xff0c; jar获取链接 aspose相关三方jar &#xff0c;下载解压后,在项目路径下建一个libs包&#xff0c;然后将下图两个jar 拷贝至刚新建的libs目录中 2、pom.xml中加入maven引入 <depend…...

按关键字搜索商品API接口搜索关键字,显示商品总数,标题,图片,优惠价参数等

按关键字搜索商品API接口通常用于根据关键字搜索商品&#xff0c;并返回商品的相关信息。以下是一个示例&#xff0c;说明如何使用Python调用按关键字搜索商品API接口。 item_search-按关键字搜索淘宝商品 公共参数 API接口请求地址:调用key 名称类型必须描述keyString是调用…...

网络基础知识入门

目录 一、局域网与广域网 1、局域网 2、广域网 二、协议 1、概念 2、协议的理解 3、协议的分层 1、分层 2、OSI七层模型 三、网络传输基本流程 1、报头 2、局域网通信原理 3、跨网络传输流程 四、IP地址和MAC地址 1、IP地址 2、MAC地址 3、两者的区别 一、局域…...

D435i发布的话题学习

参考自https://blog.csdn.net/sinat_16643223/article/details/136406602?spm1001.2014.3001.5502 这里整理一下D435i庞大的topic话题数据。 根据算法来说的话&#xff0c;vins-fusion需要双目灰度图像的话题&#xff1a; camera/infra1/image_rect_raw 和camera/infra2/image…...

Springboot启动过程

加载配置&#xff1a;Spring Boot会加载应用程序的配置文件&#xff0c;包括application.properties或application.yml等。这些配置文件中包含了应用程序的各种配置信息&#xff0c;如数据库连接、端口号等。 创建Spring容器&#xff1a;Spring Boot会创建一个Spring容器&…...

网络安全之命令注入

漏洞原理&#xff1a; 应用系统设计需要给用户提供指定的远程命令操作的接口&#xff0c;比如&#xff1a;路由器&#xff0c;防火墙&#xff0c;入侵检测等设备的web管理界面。一般会给用户提供一个ping操作的web界面 用户从web界面输入目标IP&#xff0c;提交后台会对改IP地…...

使用GDAL进行简单的坐标系转换

使用GDAL进行简单的坐标系转换 使用python GDAL进行简单的坐标系转换&#xff0c;暂时不考虑不同基准坐标系转换的精度问题。 安装环境 使用UbuntuAnaconda python 环境 conda install gdal 定义坐标系 from osgeo import gdal from osgeo import osrsrs_wgs84 osr.Spati…...

【AIGC调研系列】AI大模型结合迁移学习进行微调的应用

AI大模型结合迁移学习进行微调的应用主要体现在通过预训练模型快速适应新任务&#xff0c;提高模型性能和准确性。迁移学习允许我们利用在其他任务上学到的知识来加速新任务的学习过程&#xff0c;从而减少对大量标注数据的依赖&#xff0c;提高训练效率[1][2][3]。在AI领域&am…...

低代码革新:软件开发的未来潜力与创新路径探索

过去的一年&#xff0c;挑战与机遇并存。人们一边忧虑市场经济下行所带来的新的增长难题、裁员危机&#xff0c;一边惊叹于AIGC、量子技术等领域不断涌现新的创新成果。 时代发生了改变&#xff0c;传统“互联网”的模式已走入尾声&#xff0c;新一轮的科技革命与产业变革正在到…...

AI智能校色解决方案,专业级画质提升

由于拍摄环境、设备性能以及编辑经验等多种因素的影响&#xff0c;视频画质往往难以达到理想状态。这时&#xff0c;一款高效、智能的校色解决方案就显得尤为重要。美摄科技凭借深厚的图像处理技术和AI算法研发实力&#xff0c;推出了全新的AI智能校色解决方案&#xff0c;助力…...

面试算法-148-轮转数组

题目 给定一个整数数组 nums&#xff0c;将数组中的元素向右轮转 k 个位置&#xff0c;其中 k 是非负数。 示例 1: 输入: nums [1,2,3,4,5,6,7], k 3 输出: [5,6,7,1,2,3,4] 解释: 向右轮转 1 步: [7,1,2,3,4,5,6] 向右轮转 2 步: [6,7,1,2,3,4,5] 向右轮转 3 步: [5,6,7,…...

Linux——静态库 共享库

1.库文件 1).库文件 库是一组预先编译好的方法的集合; Linux系统存储库的位置一般在/lib 和 /usr/lib (64位系统/usr/lib64) 库的头文件放在/usr/include 2).库的分类 静态库:libxxx.a(命名规则) 共享库:libxxx.so(命名规则) 3).准备文件: //add.c int add(int x,int y) { re…...

基于Spring Boot+Vue的在线拍卖系统

随着社会的发展&#xff0c;社会的各行各业都在利用信息化时代的优势。计算机的优势和普及使得各种信息系统的开发成为必需。 在线拍卖系统&#xff0c;主要的模块包括管理员&#xff1b;首页、个人中心、用户管理、商品类型管理、拍卖商品管理、历史竞拍管理、竞拍订单管理、…...

Unity构建详解(6)——SBP的Bundle写操作生成

以下三个操作实际上是为了得到构建Bundle需要的其他参数&#xff0c;最关键的Bundle组装参数在上文已经说过了&#xff0c;至于这三个操作的具体细节不用过于追究&#xff0c;一般不怎么会取修改。 这些参数采用命令模式被封装起来&#xff0c;这是常见的参数非常多的时候的处…...

2024新版PHP在线客服系统多商户AI智能在线客服系统源码机器人自动回复即时通讯聊天系统源码PC+H5

搭建环境&#xff1a; 服务器 CPU 2核心 ↑ 运存 2G ↑ 宽带 5M ↑ 服务器操作系统 Linux Centos7.6-7.9 ↑ 运行环境&#xff1a; 宝塔面板 Nginx1.18- 1.22 PHP 7.1-7.3 MYSQL 5.6 -5.7 朵米客服系统是一款全功能的客户服务解决方案&#xff0c;提供多渠道支持…...

使用GPT需要注意的事项

GPT出来之后&#xff0c;基本就告别浏览器搜索问题答案了。将问题原封不动的copy给GPT基本可以得到解答。 但是这个也有弊端&#xff0c;那就是太依赖GPT了。 1&#xff0c;使用GPT需要更强的专业知识&#xff1a;除了能问对问题&#xff0c;还要具备识别GPT&q…...

如何查看网站蜘蛛/发外链比较好的平台

一、先说一下java的特点&#xff0c;java最大的两个特点&#xff1a;面向对象和一次编写&#xff0c;到处运行。1、java是如何实现一次编写到处运行的呢&#xff1f;因为java是面向java虚拟机编程的&#xff0c;所以在不同的平台上我们都是面向java虚拟机在编程&#xff0c;以致…...

常见的网页布局结构有哪些/武汉seo关键字优化

2019独角兽企业重金招聘Python工程师标准>>> 使用 sqlalchemy 有3种方式: 方式1, 使用raw sql; 方式2, 使用SqlAlchemy的sql expression; 方式3, 使用ORM. 前两种方式可以统称为 core 方式. 本文讲解 core 方式访问数据库, 不涉及 ORM. 对于绝大多数应用, 推荐…...

毕设 做网站/seo成创网络

点击上方“蓝字”&#xff0c;发现更多精彩。01PART基本概念①&#xff0e;命名空间&#xff1a;每一个命名空间就是一个作用域&#xff0c;为防止名字冲突提供了更加可控的机制。②&#xff0e;全局命名空间&#xff1a;全局作用域中的定义的名字都定义在全局命名空间&#xf…...

wordpress防攻击代码/如何把网站推广

[JavaScript] 纯文本查看 复制代码Title陈翔六点半铁头无敌铁头传人战江湖狄仁杰之四大天王韩庚郑恺战前任前任3再见前任靓汤高空徒手扒飞机碟中谍神秘国度佛魔大战一触即发降魔武僧赵文卓洪金宝抗倭荡寇风云赵文卓洪金宝抗倭家有喜事家有喜事删减片段曝光唐人街探案2王宝强爆笑…...

大型 网站的建设 阶段/网站建设公司大型

本文将向您展示如何添加完成按钮、替换返回按钮、在键盘上方添加自定义栏以及如何使用 FocusState 使 TextField 成为第一响应者。 完成按钮 首先,要添加完成按钮,只需添加一个 TextField 并添加以下修饰符: .submitlabel(.done)这应该产生这样的结果: 如果有关于仅在 …...

平面设计主要做什么内容/合肥seo服务商

为什么80%的码农都做不了架构师&#xff1f;>>> 一、只进行备份 数据库名称:demodb 账号:root 密码:root #!/bin/bash # bak mysql adate %Y%m%d /usr/local/mysql/bin/mysqldump -h 192.168.0.96 -u root -proot demodb > /data/mysqldb_bak/$a.sql 2013-08…...