【JVM故障问题排查心得】「Java技术体系方向」Java虚拟机内存优化之虚拟机参数调优原理介绍(二)

简介: 【JVM故障问题排查心得】「Java技术体系方向」Java虚拟机内存优化之虚拟机参数调优原理介绍

【JVM故障问题排查心得】「Java技术体系方向」Java虚拟机内存优化之虚拟机参数调优原理介绍(一)https://developer.aliyun.com/article/1471126


运行结果


-XX:+DoEscapeAnalysis -XX:InitialHeapSize=5242880 -XX:MaxHeapSize=5242880 -XX:+PrintCommandLineFlags -XX:+PrintGC -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC -XX:-UseTLAB


[GC (Allocation Failure)  1023K->516K(5632K), 0.0028410 secs]
[GC (Allocation Failure)  1540K->578K(5632K), 0.0023265 secs]
........
[GC (Allocation Failure)  2466K->1442K(5632K), 0.0013395 secs]
[GC (Allocation Failure)  2466K->1442K(5632K), 0.0004367 secs]
8925

调整启动参数: -XX:+DoEscapeAnalysis -XX:-UseTLAB

运行结果:

ruby

复制代码

-XX:+DoEscapeAnalysis -XX:InitialHeapSize=5242880 -XX:MaxHeapSize=5242880 -XX:+PrintCommandLineFlags -XX:+PrintGC -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC -XX:-UseTLAB

csharp

复制代码

[GC (Allocation Failure)  1023K->516K(5632K), 0.0028410 secs]
[GC (Allocation Failure)  1540K->578K(5632K), 0.0023265 secs]
........
[GC (Allocation Failure)  2466K->1442K(5632K), 0.0013395 secs]
[GC (Allocation Failure)  2466K->1442K(5632K), 0.0004367 secs]
8925

经过对比得出结论:

分配内存为>64byte == -XX:-UseTLAB

经过多次测试发现当_1B=64b时效率还是非常高,一旦大于64b就会急剧下降。所以推断出64byte是JVM选择是TLAB分配 OR Eden区分配的临界值。

TLAB的基本介绍

TLAB(Thread Local Allocation Buffer)

线程本地分配缓存,这是一个线程独享的内存分配区域。

特点

  • TLAB解决了:直接在线程共享堆上安全分配带来的线程同步性能消耗问题(解决了指针碰撞)
  • TLAB内存空间位于Eden区。
  • 默认TLAB大小为占用Eden Space的1%。

开启TLAB的参数

  • -XX:+UseTLAB
  • -XX:+TLABSize
  • -XX:TLABRefillWasteFraction
  • -XX:TLABWasteTargetPercent
  • -XX:+PrintTLAB

TLAB的源码

TLAB的数据结构

c

复制代码

class ThreadLocalAllocBuffer: public CHeapObj<mtThread> {
  HeapWord* _start;                              // address of TLAB
  HeapWord* _top;                                // address after last allocation
  HeapWord* _pf_top;                             // allocation prefetch watermark
  HeapWord* _end;                                // allocation end (excluding alignment_reserve)
  size_t    _desired_size;                       // desired size   (including alignment_reserve)
  size_t    _refill_waste_limit;                 // hold onto tlab if free() is larger than this
}
  • _start 指TLAB连续内存起始地址。
  • _top 指TLAB当前分配到的地址。
  • _end 指TLAB连续内存截止地址。
  • _desired_size 是指TLAB的内存大小。
  • _refill_waste_limit 是指最大的浪费空间。默认值为64b

eg:假设为_refill_waste_limit=5KB:

  1. 假如当前TLAB已经分配96KB,还剩下4KB可分配,但是现在new了一个对象需要6KB的空间,显然TLAB的内存不够了,4kb<5kb这时只浪费4KB的空间,在_refill_waste_limit之内,这时可以申请一个新的TLAB空间,原先的TLAB交给Eden管理。
  2. 假如当前TLAB已经分配90KB,还剩下10KB,现在new了一个对象需要11KB,显然TLAB的内存不够了,这时就不能简单的抛弃当前TLAB,这11KB会被安排到Eden区进行申请。

分配规则

  1. obj_size + tlab_top <= tlab_end,直接在TLAB空间分配对象。
  2. obj_size + tlab_top >= tlab_end && tlab_free > tlab_refill_waste_limit,
  • 对象不在TLAB分配,在Eden区分配。(tlab_free:剩余的内存空间,tlab_refill_waste_limit:允许浪费的内存空间
  • tlab剩余可用空间>tlab可浪费空间当前线程不能丢弃当前TLAB本次申请交由Eden区分配空间
  1. obj_size + tlab_top >= tlab_end && tlab_free < _refill_waste_limit,重新分配一块TLAB空间,在新的TLAB中分配对象。
  • tlab剩余可用空间<tlab可浪费空间,在当前允许可浪费空间内,重新申请一个新TLAB空间,原TLAB交给Eden
  • 清单:/src/share/vm/memory/ThreadLocalAllocationBuffer.inline.hpp
  • 功能:TLAB内存分配

c

复制代码

inline HeapWord* ThreadLocalAllocBuffer::allocate(size_t size) {
  invariants();
  // 获取当前top
  HeapWord* obj = top();
  if (pointer_delta(end(), obj) >= size) {
    // successful thread-local allocation
#ifdef ASSERT
    // Skip mangling the space corresponding to the object header to
    // ensure that the returned space is not considered parsable by
    // any concurrent GC thread.
    size_t hdr_size = oopDesc::header_size();
    Copy::fill_to_words(obj + hdr_size, size - hdr_size, badHeapWordVal);
#endif // ASSERT
    // This addition is safe because we know that top is
    // at least size below end, so the add can't wrap.
    // 重置top
    set_top(obj + size);
    invariants();
    return obj;
  }
  return NULL;
}

实际上虚拟机内部会维护一个叫作refill_waste的值,当剩余对象空间大于refill_waste时,会选择在堆中分配,若小于该值,则会废弃当前TLAB,新建TLAB来分配对象

这个阈值可以使用TLABRefillWasteFraction来调整,它表示TLAB中允许产生这种浪费的比例。

默认值为64,即表示使用约为1/64的TLAB空间作为refill_waste。

  • TLAB和refill_waste都会在运行时不断调整的,使系统的运行状态达到最优。
  • 如果想要禁用自动调整TLAB的大小,可以使用-XX:-ResizeTLAB禁用ResizeTLAB
  • 使用-XX:TLABSize手工指定一个TLAB的大小。

指针碰撞&Eden区分配

java

复制代码

// 指针碰撞分配
HeapWord* compare_to = *Universe::heap()->top_addr();
HeapWord* new_top = compare_to + obj_size;
if (new_top <= *Universe::heap()->end_addr()) {
    if (Atomic::cmpxchg_ptr(new_top, Universe::heap()->top_addr(), compare_to) != compare_to) {
                  goto retry;
    }
    result = (oop) compare_to;
   }
}

Eden区指针碰撞,需要模拟多线程并发申请内存空间。

java

复制代码

/**
 * @since 2019/8/19  下午11:25
-Xmx100m -Xms100m -XX:-DoEscapeAnalysis -XX:+UseTLAB 
-XX:TLABWasteTargetPercent=1 -XX:+PrintCommandLineFlags  -XX:+PrintGCDetails
 */
public class AllocationTLABSomeThread {
    private static final int threadNum = 100;
    private static CountDownLatch latch = new CountDownLatch(threadNum);
    private static final int n = 50000000 / threadNum;
    private static void alloc() {
        byte[] b = new byte[100];
    }
    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        for (int i = 0; i < threadNum; i++) {
            new Thread(() -> {
                for (int j = 0; j < n; j++) {
                    alloc();
                }
                latch.countDown();
            }).start();
        }
        try {
            latch.await();
        } catch (InterruptedException e) {
            System.out.println("hello world");
        }
        long end = System.currentTimeMillis();
        System.out.println(end - start);
    }
}

且需要关闭逃逸分析 -XX:-DoEscapeAnalysis -XX:+UseTLAB

运行结果

ruby

复制代码

-XX:-DoEscapeAnalysis -XX:InitialHeapSize=104857600 -XX:MaxHeapSize=104857600 -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:TLABWasteTargetPercent=1 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC -XX:+UseTLAB 
[GC (Allocation Failure) [PSYoungGen: 25600K->960K(29696K)] 25600K->968K(98304K), 0.0019559 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 26560K->960K(29696K)] 26568K->968K(98304K), 0.0022243 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 26560K->768K(29696K)] 26568K->776K(98304K), 0.0022446 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
........
[GC (Allocation Failure) [PSYoungGen: 32768K->0K(33280K)] 34193K->1425K(101888K), 0.0014598 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 32768K->0K(33280K)] 34193K->1425K(101888K), 0.0015168 secs] [Times: user=0.00 sys=0.01, real=0.00 secs] 
823
Heap
 PSYoungGen      total 33280K, used 3655K [0x00000007bdf00000, 0x00000007c0000000, 0x00000007c0000000)
  eden space 32768K, 11% used [0x00000007bdf00000,0x00000007be291c48,0x00000007bff00000)
  from space 512K, 0% used [0x00000007bff80000,0x00000007bff80000,0x00000007c0000000)
  to   space 512K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007bff80000)
 ParOldGen       total 68608K, used 1425K [0x00000007b9c00000, 0x00000007bdf00000, 0x00000007bdf00000)
  object space 68608K, 2% used [0x00000007b9c00000,0x00000007b9d64798,0x00000007bdf00000)
 Metaspace       used 4255K, capacity 4718K, committed 4992K, reserved 1056768K
  class space    used 477K, capacity 533K, committed 640K, reserved 1048576K

关闭逃逸和TLAB分配 -XX:-DoEscapeAnalysis -XX:-UseTLAB 运行结果:

ruby

复制代码

-XX:-DoEscapeAnalysis -XX:InitialHeapSize=104857600 -XX:MaxHeapSize=104857600 -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:TLABWasteTargetPercent=1 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC -XX:-UseTLAB 
[GC (Allocation Failure) [PSYoungGen: 25599K->976K(29696K)] 25599K->984K(98304K), 0.0023516 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 26575K->880K(29696K)] 26583K->888K(98304K), 0.0015459 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 26480K->832K(29696K)] 26488K->840K(98304K), 0.0006776 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
.......
[GC (Allocation Failure) [PSYoungGen: 32767K->0K(33280K)] 34053K->1285K(101888K), 0.0004838 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 32767K->0K(33280K)] 34053K->1285K(101888K), 0.0005389 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
5388
Heap
 PSYoungGen      total 33280K, used 21392K [0x00000007bdf00000, 0x00000007c0000000, 0x00000007c0000000)
  eden space 32768K, 65% used [0x00000007bdf00000,0x00000007bf3e4230,0x00000007bff00000)
  from space 512K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007bff80000)
  to   space 512K, 0% used [0x00000007bff80000,0x00000007bff80000,0x00000007c0000000)
 ParOldGen       total 68608K, used 1285K [0x00000007b9c00000, 0x00000007bdf00000, 0x00000007bdf00000)
  object space 68608K, 1% used [0x00000007b9c00000,0x00000007b9d41788,0x00000007bdf00000)
 Metaspace       used 4248K, capacity 4718K, committed 4992K, reserved 1056768K
  class space    used 478K, capacity 533K, committed 640K, reserved 1048576K

经过对比,相差7倍左右。二者内存回收♻️,从YoungGC次数和耗时上没有太大变化:应为都是Eden区分配

G1垃圾回收过程

触发混合回收条件:

-XX:InitiatingHeapOccupancyPercent=45 ,当老年代空间使用占整个堆空间45%时

混合回收范围:

新生代、老年代、大对象。

混合回收过程:

初始标记:

  1. 这个过程会STW,停止系统线程。
  2. 标记GC-Roots的直接引用对象。
  1. 线程栈中局部变量表 。
  2. 方法区中的静态变量/常量等。
  3. 本地方法栈。
  • 特点:速度极快。

并发标记

  1. 这个过程不会STW,系统线程正常运行
  2. 从第一阶段标记的GC-Roots开始追踪所有存活对象
  • 特点:慢,很耗时
  • 优化:JVM会对“并发标记”阶段新产生的对象及对象修改做记录(RememberSet)

最终标记:

  1. 这个过程会STW,系统线程停止运行。
  2. 会根据“并发标记”阶段记录的RememberSet进行对象标记。
  • 特点:很快。
  • RememberSet相当于是拿空间换时间

混合回收:

  1. 这个过程会STW,系统线程停止运行
  2. 会计算老年代中每个Region中存活对象数量,存活对象占比,执行垃圾回收预期耗时和效率
  • 耗时:会根据启动参数中-XX:MaxGCPauseMillis=200和历史回收耗时来计算本次要回收多少老年代Region才能耗时200ms
  • 特点:回收了一部分远远没有达到回收的效果,G1还有一个特殊处理方法,STW后进行回收,然后恢复系统线程,然后再次STW,执行混合回收掉一部分Region,‐XX:G1MixedGCCountTarget=8 (默认是8次),反复执行上述过程8次

注意:假设要回收400个Region,如果受限200ms,每次只能回收50个Region,反复8次刚好全部回收完毕。这么做的好处是避免单次停顿回收STW时间太长

  1. **还有一个参数要提一下‐XX:G1HeapWastePercent=5 (默认是5%)
  • 混合回收是采用复制算法,把要回收的Region中存活的对象放入其他Region中
  • 然后这个Region中的垃圾全部清理掉,这样就会不断有Region释放出来,当释放出的Region占整个堆空间5%时,停止混合回收
  1. 还有一个参数:‐XX:G1MixedGCLiveThresholdPercent=85 (默认值85%) 。回收Region的时候,必须是存活对象低于85%。

混合回收失败时:

  1. 在Mixed回收的时候,无论是年轻代还是老年代都是基于复制算法进行回收,都要把各个Region的存活对象拷贝到另外其他的Region里去,万一拷贝是发生空间不足,就会触发一次失败
  2. 一旦回收失败,立马就会切换采用Serial 单线程进行标记+清理+整理,整个过程是非常慢的(灾难)。
相关文章
|
1月前
|
前端开发 Java 物联网
智慧班牌源码,采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署
智慧班牌系统是一款基于信息化与物联网技术的校园管理工具,集成电子屏显示、人脸识别及数据交互功能,实现班级信息展示、智能考勤与家校互通。系统采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署与私有化定制。核心功能涵盖信息发布、考勤管理、教务处理及数据分析,助力校园文化建设与教学优化。其综合性和可扩展性有效打破数据孤岛,提升交互体验并降低管理成本,适用于日常教学、考试管理和应急场景,为智慧校园建设提供全面解决方案。
226 70
|
1月前
|
人工智能 Java 程序员
Java程序员在AI时代必会的技术:Spring AI
在AI时代,Java程序员需掌握Spring AI技术以提升竞争力。Spring AI是Spring框架在AI领域的延伸,支持自然语言处理、机器学习集成与自动化决策等场景。它简化开发流程,无缝集成Spring生态,并提供对多种AI服务(如OpenAI、阿里云通义千问)的支持。本文介绍Spring AI核心概念、应用场景及开发步骤,含代码示例,助你快速入门并构建智能化应用,把握AI时代的机遇。
|
2月前
|
编解码 虚拟化 Windows
通过Hyper分辨率适配技术,可以解决虚拟机显示器上的分辨率适配难题
Hyper-V的增强会话模式通过RDP协议实现虚拟机与物理显示器的高质量连接,支持更高分辨率、优化图形性能,并提供共享剪贴板、拖放文件等增强功能。启用此模式可解决虚拟机分辨率适配难题,提升显示效果和交互性。需确保操作系统支持RDP,网络稳定,且注意对主机性能的影响。
|
2月前
|
编解码 监控 虚拟化
Hyper分辨率优化技术,怎么使得虚拟机中的图形应用能够以更高的清晰度呈现
Hyper分辨率优化技术通过增强虚拟机的图形处理能力,显著提升图像清晰度和视觉体验,适用于图形设计、视频编辑等场景。该技术依赖于虚拟机的硬件配置、显卡驱动及显示设置,确保高分辨率内容的最佳呈现。使用时需合理设置分辨率,定期更新驱动并监控性能,以实现最佳效果。
|
2月前
|
存储 监控 数据可视化
SaaS云计算技术的智慧工地源码,基于Java+Spring Cloud框架开发
智慧工地源码基于微服务+Java+Spring Cloud +UniApp +MySql架构,利用传感器、监控摄像头、AI、大数据等技术,实现施工现场的实时监测、数据分析与智能决策。平台涵盖人员、车辆、视频监控、施工质量、设备、环境和能耗管理七大维度,提供可视化管理、智能化报警、移动智能办公及分布计算存储等功能,全面提升工地的安全性、效率和质量。
|
3月前
|
缓存 Java 物联网
CRaC技术助力ACS上的Java应用启动加速
容器计算服务借助ACS的柔性算力特性并搭配CRaC技术极致地提升Java类应用的启动速度。
|
3月前
|
存储 监控 Java
【Java并发】【线程池】带你从0-1入门线程池
欢迎来到我的技术博客!我是一名热爱编程的开发者,梦想是编写高端CRUD应用。2025年我正在沉淀中,博客更新速度加快,期待与你一起成长。 线程池是一种复用线程资源的机制,通过预先创建一定数量的线程并管理其生命周期,避免频繁创建/销毁线程带来的性能开销。它解决了线程创建成本高、资源耗尽风险、响应速度慢和任务执行缺乏管理等问题。
241 60
【Java并发】【线程池】带你从0-1入门线程池
|
13天前
|
Java
java 多线程异常处理
本文介绍了Java中ThreadGroup的异常处理机制,重点讲解UncaughtExceptionHandler的使用。通过示例代码展示了当线程的run()方法抛出未捕获异常时,JVM如何依次查找并调用线程的异常处理器、线程组的uncaughtException方法或默认异常处理器。文章还提供了具体代码和输出结果,帮助理解不同处理器的优先级与执行逻辑。
|
1月前
|
Java 中间件 调度
【源码】【Java并发】从InheritableThreadLocal和TTL源码的角度来看父子线程传递
本文涉及InheritableThreadLocal和TTL,从源码的角度,分别分析它们是怎么实现父子线程传递的。建议先了解ThreadLocal。
72 4
【源码】【Java并发】从InheritableThreadLocal和TTL源码的角度来看父子线程传递
|
2月前
|
存储 网络协议 安全
Java网络编程,多线程,IO流综合小项目一一ChatBoxes
**项目介绍**:本项目实现了一个基于TCP协议的C/S架构控制台聊天室,支持局域网内多客户端同时聊天。用户需注册并登录,用户名唯一,密码格式为字母开头加纯数字。登录后可实时聊天,服务端负责验证用户信息并转发消息。 **项目亮点**: - **C/S架构**:客户端与服务端通过TCP连接通信。 - **多线程**:采用多线程处理多个客户端的并发请求,确保实时交互。 - **IO流**:使用BufferedReader和BufferedWriter进行数据传输,确保高效稳定的通信。 - **线程安全**:通过同步代码块和锁机制保证共享数据的安全性。
115 23