【JVM系列】- 挖掘·JVM堆内存结构

news2024/11/25 20:34:51

挖掘·JVM堆内存结构

在这里插入图片描述

文章目录

  • 挖掘·JVM堆内存结构
    • 堆的核心概念
      • 堆的特点
    • 堆的内存结构
      • 内存划分
        • 新生代/新生区(Young Generation)
        • 老年代(Tenured Generation)
        • 永久代(或元数据区)(PermGen 或 MetaSpace)
      • 设置堆空间的大小与OOM
        • 手动设置堆内存大小的值
        • OOM
      • 堆的内存分配
      • *对象分配过程
    • GC垃圾回收概念
      • Minor GC、Major GC、Full GC
      • 案例与日志分析
    • *内存分配策略
    • TLAB(Thread-Local Allocation Buffer)
      • 什么是TLAB
      • 为什么要有TLAB
      • 有TLAB对象的分配过程
    • *逃逸分析(Escape Analysis)
      • 逃逸分析的基本行为
      • 参数设置
      • 逃逸分析优化
        • 1) 代码优化-栈上分配
        • 2) 代码优化-同步消除
        • 3) 代码优化-标量替换
    • 堆空间的参数设置总结
    • 总结
    • 资料

堆的核心概念

JVM(Java虚拟机)中的 “堆” 是指Java程序运行时用来存储对象实例的一块内存区域。堆内存是JVM管理的最大一块内存区域之一,它的主要作用是存储对象实例,包括类实例、数组和其他对象。

以下是运行时数据区的整体结构图,其中方法区和堆区都是属于线程共有的,也就是一个进程中只有一个堆区和方法区。

未命名文件 (12).png
我们所运行的Java文件就可以看作为一个进程,而与之对应的是JVM实例,进程中会包含许多的线程,而这些线程就会共享方法区和堆区。

堆的特点

  • 一个JVM实例就只是对应着一个堆区,堆是Java内存管理的一大核心区域。
  • 堆内存的大小在JVM启动时可以指定,但通常是在运行时动态分配的。这意味着堆内存可以根据程序的需要动态增长或缩小,以容纳新创建的对象。
  • 堆的存储可以是在物理上不连续,但是在逻辑上连续。[1]
  • 虽然堆是线程共有的,但是可以划分线程私有缓冲区(Thread Local Allocation Buffer, TLAB)[2]
  • 对象和数组都不会存储在栈上,栈帧中保存的是引用,这个引用指向的是对象或数组在堆中的位置。
  • 在方法结束之后,堆的对象不会立马被清除,仅仅只会在垃圾收集的时候才被移除。
  • 堆是垃圾回收的重点区域。

[1]:对于物理不连续,逻辑连续,可以通过以下几个内容来认识。

  • 磁盘存储:在硬盘驱动器或固态硬盘等存储设备上,数据通常存储在不同的磁道、扇区或块上。物理上,这些数据存储单元可能不是紧密相连的,它们分布在存储介质上的不同位置。然而,逻辑上,操作系统和文件系统会将这些数据组织成文件和文件系统,以使它们在逻辑上看起来是连续的。
  • 内存分配:在计算机内存中,变量和数据结构可以分散存储在不同的内存地址上。这些内存地址在物理上可以是不连续的,但在编程中,我们可以通过变量名或指针引用来访问它们,从逻辑上看起来它们是连续的。
  • 数据库表:在数据库中,表中的行和列可以以物理上不连续的方式存储在磁盘上,但从查询和应用程序的角度来看,它们是逻辑上连续的。数据库管理系统负责处理物理存储和逻辑视图之间的映射。

[2]:TLAB旨在提高多线程环境中的对象分配性能,减少锁竞争和减小垃圾回收的开销。当数据存放在堆区,由于多线程对此数据进行操作,这就会导致并发问题。

堆的内存结构

堆内存是JVM管理的最大的内存区域之一,它通常用于存储在运行时创建的对象,包括类实例、数组等。
堆内存逻辑结构主要分为三部分:新生区、养老区、永久代/元数据区,在jdk7之前是叫永久代,而在jdk8之后就开始称为元数据区。

内存划分

新生代/新生区(Young Generation)
  • 新生代主要分为三个部分:Eden区、Survivor区1(S0或From区)和Survivor区2(S1或To区)(幸存者区[3])。
  • 新对象首先会被分配到Eden区。
  • 当Eden区满时,一部分对象会被移动到Survivor区1或Survivor区2,经过多次循环后,仍然存活的对象会被晋升到老年代。
  • Minor GC(年轻代垃圾收集)通常在新生代执行,回收不再存活的对象。
老年代(Tenured Generation)
  • 老年代是存储已经经过多次Minor GC并且存活的对象。
  • 老年代都是具有较长的生命周期。Full GC(全局垃圾收集)通常会在老年代执行,回收老年代中的垃圾对象。
永久代(或元数据区)(PermGen 或 MetaSpace)
  • 永久代(JDK1.7之前)用于存储类的元数据信息、常量池、静态变量等。
  • 在较新的JVM版本中(JDK1.8之后),永久代被元数据区(MetaSpace)取代,元数据区不再固定大小,而是由操作系统动态分配,从而减轻了永久代内存问题。
  • 这些区域中的对象通常具有较长的生命周期。

运行时数据区 (1).png

[3]: Survivor Space(幸存者区)是Java虚拟机(JVM)中新生代(Young Generation)的一部分,用于存储在垃圾收集过程中仍然存活的对象。幸存者区分为两部分,大小是相同的,Survivor区1(S0或From区)和Survivor区2(S1或To区),其中Survivor区1和Survivor区2一般被用来交替地存储幸存的对象。在执行垃圾回收的时候Eden区域不能被回收的对象被放入到空的survivor(也就是To Survivor,同时Eden区域的内存会在垃圾回收的过程中全部释放),另一个survivor(即From Survivor)里不能被回收的对象也会被放入这个survivor(即To Survivor),然后To Survivor 和 From Survivor的标记会互换,始终保证一个survivor是空的。

设置堆空间的大小与OOM

JVM中的堆空间是用来存储对象实例的,然而这个堆大小在JVM启动的时候就已经确定了大小了,我们可以通过-Xms-Xmx来设置。

  • -Xms: 用来设置堆空间(年轻代+老年代)的初始内存大小,等同-XX:InitialHeapSize
  • -Xmx: 用来设置堆空间(年轻代+老年代)的最大内存大小,等同-XX:MaxHeapSize

如果堆内存大于了堆空间的最大内存,那么就会出现OOM即抛出OutOfMemoryError异常。

*在开发中建议是将Xms和Xmx的值设置成一样,其目的是为了能够在垃圾回收机制清理完堆区后不再需要重新分隔计算堆的大小,从而提高性能。

如果没有设置堆空间大小,我们可以通过Java代码来查看默认的堆空间大小。通过Runtime,每个Java应用程序都有一个Runtime类实例,该类允许应用程序与运行应用程序的环境进行交互。

public class HeapSpaceInitial {
    public static void main(String[] args) throws InterruptedException {
        // JVM推内存容量
        long initialMemory = Runtime.getRuntime().totalMemory() / 1024 / 1024;
        // JVM最大堆内存量
        long maxMemory = Runtime.getRuntime().maxMemory() / 1024 / 1024;
        System.out.println("-Xms: " + initialMemory + "M");
        System.out.println("-Xmm: " + maxMemory + "M");
        System.out.println("系统内存大小: " + initialMemory * 64.0 / 1024 + "G");
        System.out.println("系统内存大小: " + maxMemory * 4.0 / 1024 + "G");

    }
}

在默认情况下,堆空间的大小都有各自的计算方式:

  • 初始内存大小:物理电脑内存大小 / 64
  • 最小内存大小:物理电脑内存大小 / 4
手动设置堆内存大小的值

通过-Xms和-Xmx,在idea中,我们可以更改运行配置,在VM options 中输入-Xms600m -Xmx600m,来设置600M的内存大小。我们直接运行以上代码,看一下7/8行所打印出来的数据。

-Xms: 575M
-Xmm: 575M

我们明明设置了600M,但为何只剩下575M了呢?
首先,可以通过在VM Options中追加配置-XX:+PrintGCDetails来打印GC的详细信息,他是基于程序运行结束之后显示的数据。运行以上代码出现的结果

-Xms: 575M
-Xmm: 575M
Heap
 PSYoungGen      total 179200K, used 12288K [0x00000000f3800000, 0x0000000100000000, 0x0000000100000000)
  eden space 153600K, 8% used [0x00000000f3800000,0x00000000f44001b8,0x00000000fce00000)
  from space 25600K, 0% used [0x00000000fe700000,0x00000000fe700000,0x0000000100000000)
  to   space 25600K, 0% used [0x00000000fce00000,0x00000000fce00000,0x00000000fe700000)
 ParOldGen       total 409600K, used 0K [0x00000000da800000, 0x00000000f3800000, 0x00000000f3800000)
  object space 409600K, 0% used [0x00000000da800000,0x00000000da800000,0x00000000f3800000)
 Metaspace       used 3315K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 364K, capacity 388K, committed 512K, reserved 1048576K

我们看一下如何计算
首先是新生代(PSYoungGen),其总数是179200K,这个179200K=153600K+25600K,也就是幸存者区只会计算一个区域的数值,因为会始终保持一个区域是为空的。
这样算的话,那么内存大小就是:(PSYoungGen(179200K)+ParOldGen(409600K)) / 1024 = 575M
我们设置的600M=(eden(153600K)+from(25600K)+to(25600K)+ParOldGen(409600K) )/ 1024

也就是说,计算内存大小的时候,并不会吧两个幸存者区都计算进去的,因为实际上有一个是不存储数据的。
补:还可以利用命令的方式来查看GC信息

jstat -gc 进程号
image.png

OOM

OutOfMemoryError(OOM),说明你的Java应用程序耗尽了可用的堆内存资源,导致无法继续分配更多的内存。
我们来做一个例子,让它能够导致OOM错误,并通过jvisualvm工具来查看具体情况,这个工具是jdk内置含有的,在Java#bin目录下。我们通过死循环去执行创建链表数组,并且每次new一个对象实例。设置内存大小为500M,-Xms500m -Xmx500m

public class OOMTest {
    public static void main(String[] args) throws InterruptedException {
        List<Images> list = new ArrayList<>();
        int i = 0;
        while (true) {
            Thread.sleep(10);
            list.add(new Images(1024 * 1024));
        }
    }
}
class Images {
    private byte[] bytes;
    public Images(int len) {
        this.bytes = new byte[len];
    }
}

打开jvisualvm工具,可以在概述页面看到程序的一些基础信息。
image.png
通过抽样器可以查看内存中,那些内容占据的大小是最多的。
image.png
通过Visual GC就可以看到内存的变化,直到最后会把old区域打满。(这里如果没有安装Visual GC插件,可以点击 工具->插件->可用插件->选择下载Visual GC)
image.png

堆的内存分配

在JVM中,存储的对象有的生命周期是比较短的,还有一些是比较长的。Java堆划分为年轻代和老年代。年轻代又划分为Eden(伊甸园)、Survivor区1(S0或From区)和Survivor区2(S1或To区)幸存者区。
在默认情况下,年轻代和老年代的比例是1:2,并且可以通过-XX:NewRation来设置它们的比例。
我们跑一个Java程序,设置内存大小为600M,然后Visual GC工具来查看其默认比例。
image.png
我们可以计算出年轻代:150+25+25=200M,老年代为400M,比例为1:2

  • 新生代中的 Eden:From Survivor:To Survivor 的比例是 8:1:1,可以通过-XX:SurvivorRatio来配置。
  • 还能够通过-XX:SurvivorRatio来设置新生代中Eden与Survivor的比例。
  • JDK中会自动默认开启了-XX:+UseAdaptiveSizePolicy:自适应的内存分配策略。
  • 可以通过-Xmn来设置新生代的最大内存大小(一般是不会去设置)
  • 需要注意的是 几乎所有的Java对象都是在Eden区中new出来的。
  • 绝大部分的Java对象的销毁都在新生代进行。

*对象分配过程

  1. new对象会先放在伊甸园区(Eden Space),这个区域会有大小限制。
  2. 当伊甸园的空间填满时,程序又需要创建对象,JVM 的垃圾回收器将对伊甸园区进行垃圾回收(Minor GC),将伊甸园区中的不再被其他对象所引用的对象进行销毁。再加载新的对象放到伊甸园区。
  3. 然后将伊甸园中的剩余对象移动到幸存者 0 区,年龄计数器将会被加1。
  4. 如果再次发生垃圾回收,上次幸存者0区的数据如果没被回收,就会移动到幸存者1区(每次的垃圾回收都会使得幸存者区域进行一次交换)
  5. 经过多次垃圾回收后,如果年龄计数器的值达到了一个阈值,那么,就会触发Promotion(晋升),将数据移动到养老区。这个可以通过-XX:MaxTenuringThreshold=<N>来修改

未命名文件 (14).png
说明一下对上图的理解

  • 第一阶段,当对象实例之后会存放在Eden区,直到Eden区满了之后,会触发垃圾回收(YGC/Minor GC),将不用的对象数据回收,如果还有用的就会被移动到S0区中,并且年龄计数器会加1。
  • 第二阶段,当Eden区再次满了之后,还会触发垃圾回收,剩余的对象会被移动到S1区(此时S1是空的,每次都会有一个空的区域,要么是S0,要么是S1),如果S0此时的对象还不能回收,那就会被移动到S1区中。
  • 第三阶段,这里代表过了多次垃圾回收后的情况。当Eden区还有需要保留的对象,都会放在S0/S1(看交换到谁,此图画的是移动到S0),然而当S1也有不能销毁的数据,并且年龄达到了阈值(图中设置为15),将会被晋升到老年代中。

*注:这里需要注意的是,幸存者区(S0、S1)在移动的时候是会进行交换的,都会有一个为空。在垃圾回收方面,幸存者区是不会主动触发垃圾回收的,而是在伊甸园区触发垃圾回收的时候进行垃圾回收。对于垃圾回收,频繁的在新生区收集,很少在养老区,几乎不会再永久代/元空间收集。

GC垃圾回收概念

Minor GC、Major GC、Full GC

在进行 GC 时,并非每次都对堆内存(新生代、老年代;方法区)区域一起回收的,大部分时候回收的都是指新生代。针对 HotSpot VM 的实现,它里面的 GC 按照回收区域又分为两大类:部分收集(Partial GC),整堆收集(Full GC)。
Minor GC:

  • Minor GC,也称为年轻代垃圾回收,是针对年轻代(Young Generation)的垃圾回收操作。
  • 年轻代包括伊甸园区(Eden Space)和幸存者区(Survivor Spaces)。
  • 在Minor GC中,Java虚拟机会检查并回收年轻代中不再被引用的对象,通常采用标记-复制(Mark and Copy)或标记-整理(Mark and Compact)算法。
  • Minor GC的目标是回收年轻代中的垃圾对象,将存活的对象晋升到老年代。
  • Minor GC会引发STW,暂停其他用户的线程,等待垃圾回收结束,用户线程才会恢复运行。

Major GC(或称为 “Tenured GC”):

  • Major GC 是对老年代(Old Generation)的垃圾回收操作。
  • 老年代中的对象具有较长的生命周期,通常经过多次Minor GC后才会被回收。
  • Major GC通常使用标记-清除(Mark and Sweep)或标记-整理(Mark and Compact)算法。
  • 目标是回收老年代中的不再被引用的对象,以释放内存。
  • 经常会伴随着至少一次的Minor GC,在老年代空间不足时,会先尝试触发Minor GC,如果空间还是不足,则触发Major GC。
  • Major GC的速度一般会比Minor GC慢10倍以上,STW的时间更长。
  • 如果垃圾回收后还是不足,就会出现OOM。

Full GC:

  • Full GC 是对整个堆内存的垃圾回收操作,包括年轻代和老年代,以及元空间(Metaspace)或永久代(在Java 8之前的版本)。
  • Full GC的执行通常需要暂停整个应用程序,因为它可能涉及到大量的内存回收和整理。
  • Full GC的目标是在发现内存不足的情况下,进行一次全面的内存回收,以避免OutOfMemoryError。
  • 导致触发的可能有:
    • 调用了System.gc(),系统建议执行Full GC,但是不必然执行。
    • 老年代空间不足、方法区空间不足。
    • 通过Minor GC后进入老年代的平均大小大于老年代的可用内存。
    • 有伊甸园区、幸存者区(S0/S1)向幸存者区(S1/S0)复制时,对象大于(S1/S0对应的区)可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小。

*注:Full GC是开发或调优中尽量要避免的。

案例与日志分析

我们先定义一个案例,死循环去给拼接字符串,并且存到一个ArrayList数组中。

public class GCTest {
    public static void main(String[] args) {
        int count = 0;
        try {
            // 定义数组
            ArrayList<String> list = new ArrayList<>();
            String text = "不断的拼接##"; // 字符串存在堆空间
            while (true) {
                list.add(text);
                text = count + text;
                count++;
            }
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("遍历次数: " + count);
        }
    }
}

这里需要配置VM Options设置为-Xms9m -Xmx9m -XX:+PrintGCDetails,把空间设置小一点,这样就能狗明显看到GC日志。

[GC (Allocation Failure) [PSYoungGen: 2040K->488K(2560K)] 2040K->943K(9728K), 0.0006030 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 2536K->504K(2560K)] 2991K->1961K(9728K), 0.0005728 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 2552K->512K(2560K)] 4009K->3178K(9728K), 0.0004624 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 2559K->512K(2560K)] 5225K->4140K(9728K), 0.0004935 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 2560K->512K(2560K)] 6188K->5163K(9728K), 0.0004562 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 2560K->512K(1536K)] 7211K->6191K(8704K), 0.0004587 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 1536K->960K(2048K)] 7215K->6991K(9216K), 0.0003252 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Ergonomics) [PSYoungGen: 960K->0K(2048K)] [ParOldGen: 6031K->6073K(7168K)] 6991K->6073K(9216K), [Metaspace: 3311K->3311K(1056768K)], 0.0047554 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 1024K->672K(2048K)] 7097K->6745K(9216K), 0.0003614 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Ergonomics) [PSYoungGen: 672K->0K(2048K)] [ParOldGen: 6073K->6457K(7168K)] 6745K->6457K(9216K), [Metaspace: 3311K->3311K(1056768K)], 0.0111496 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[Full GC (Ergonomics) [PSYoungGen: 1021K->0K(2048K)] [ParOldGen: 6457K->6928K(7168K)] 7478K->6928K(9216K), [Metaspace: 3311K->3311K(1056768K)], 0.0161660 secs] [Times: user=0.00 sys=0.00, real=0.02 secs] 
[Full GC (Ergonomics) [PSYoungGen: 1024K->492K(2048K)] [ParOldGen: 6928K->6928K(7168K)] 7952K->7421K(9216K), [Metaspace: 3313K->3313K(1056768K)], 0.0011506 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Ergonomics) [PSYoungGen: 1024K->756K(2048K)] [ParOldGen: 6928K->6928K(7168K)] 7952K->7685K(9216K), [Metaspace: 3320K->3320K(1056768K)], 0.0012778 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Ergonomics) [PSYoungGen: 1023K->886K(2048K)] [ParOldGen: 6928K->6928K(7168K)] 7951K->7814K(9216K), [Metaspace: 3320K->3320K(1056768K)], 0.0010096 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Ergonomics) [PSYoungGen: 1020K->946K(2048K)] [ParOldGen: 6928K->6928K(7168K)] 7948K->7874K(9216K), [Metaspace: 3320K->3320K(1056768K)], 0.0009717 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Ergonomics) [PSYoungGen: 1017K->976K(2048K)] [ParOldGen: 6928K->6928K(7168K)] 7945K->7904K(9216K), [Metaspace: 3320K->3320K(1056768K)], 0.0109962 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[Full GC (Ergonomics) [PSYoungGen: 1023K->996K(2048K)] [ParOldGen: 6928K->6928K(7168K)] 7951K->7924K(9216K), [Metaspace: 3320K->3320K(1056768K)], 0.0013137 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Ergonomics) [PSYoungGen: 1015K->1006K(2048K)] [ParOldGen: 6928K->6928K(7168K)] 7944K->7935K(9216K), [Metaspace: 3325K->3325K(1056768K)], 0.0013365 secs] [Times: user=0.02 sys=0.00, real=0.00 secs] 
[Full GC (Ergonomics) [PSYoungGen: 1021K->1006K(2048K)] [ParOldGen: 6928K->6928K(7168K)] 7950K->7935K(9216K), [Metaspace: 3325K->3325K(1056768K)], 0.0011589 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Ergonomics) [PSYoungGen: 1019K->1016K(2048K)] [ParOldGen: 6928K->6928K(7168K)] 7947K->7945K(9216K), [Metaspace: 3325K->3325K(1056768K)], 0.0012006 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Ergonomics) [PSYoungGen: 1023K->1007K(2048K)] [ParOldGen: 7164K->7063K(7168K)] 8187K->8070K(9216K), [Metaspace: 3325K->3325K(1056768K)], 0.0010010 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Ergonomics) [PSYoungGen: 1019K->1017K(2048K)] [ParOldGen: 7063K->7063K(7168K)] 8083K->8080K(9216K), [Metaspace: 3325K->3325K(1056768K)], 0.0009194 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Ergonomics) [PSYoungGen: 1019K->1007K(2048K)] [ParOldGen: 7165K->7124K(7168K)] 8184K->8131K(9216K), [Metaspace: 3325K->3325K(1056768K)], 0.0009735 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Ergonomics) [PSYoungGen: 1019K->1017K(2048K)] [ParOldGen: 7124K->7114K(7168K)] 8143K->8131K(9216K), [Metaspace: 3325K->3325K(1056768K)], 0.0009866 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Ergonomics) [PSYoungGen: 1019K->1017K(2048K)] [ParOldGen: 7165K->7144K(7168K)] 8184K->8162K(9216K), [Metaspace: 3325K->3325K(1056768K)], 0.0009173 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Ergonomics) [PSYoungGen: 1019K->1017K(2048K)] [ParOldGen: 7165K->7155K(7168K)] 8184K->8172K(9216K), [Metaspace: 3325K->3325K(1056768K)], 0.0008445 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Ergonomics) [PSYoungGen: 1019K->1017K(2048K)] [ParOldGen: 7165K->7155K(7168K)] 8184K->8172K(9216K), [Metaspace: 3325K->3325K(1056768K)], 0.0009346 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Ergonomics) [PSYoungGen: 1019K->1017K(2048K)] [ParOldGen: 7165K->7165K(7168K)] 8184K->8182K(9216K), [Metaspace: 3325K->3325K(1056768K)], 0.0009103 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 1017K->1017K(2048K)] [ParOldGen: 7165K->7147K(7168K)] 8182K->8165K(9216K), [Metaspace: 3325K->3325K(1056768K)], 0.0166929 secs] [Times: user=0.00 sys=0.00, real=0.02 secs] 
[Full GC (Ergonomics) [PSYoungGen: 1019K->1017K(2048K)] [ParOldGen: 7157K->7147K(7168K)] 8177K->8165K(9216K), [Metaspace: 3325K->3325K(1056768K)], 0.0011293 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Ergonomics) [PSYoungGen: 1019K->1017K(2048K)] [ParOldGen: 7157K->7157K(7168K)] 8177K->8175K(9216K), [Metaspace: 3325K->3325K(1056768K)], 0.0010992 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 1017K->1017K(2048K)] [ParOldGen: 7157K->7157K(7168K)] 8175K->8175K(9216K), [Metaspace: 3325K->3325K(1056768K)], 0.0010983 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Ergonomics) [PSYoungGen: 1021K->0K(2048K)] [ParOldGen: 7167K->731K(7168K)] 8189K->731K(9216K), [Metaspace: 3344K->3344K(1056768K)], 0.0029345 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 PSYoungGen      total 2048K, used 29K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
  eden space 1024K, 2% used [0x00000000ffd00000,0x00000000ffd077a0,0x00000000ffe00000)
  from space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
  to   space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
 ParOldGen       total 7168K, used 731K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)
  object space 7168K, 10% used [0x00000000ff600000,0x00000000ff6b6c70,0x00000000ffd00000)
 Metaspace       used 3356K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 368K, capacity 388K, committed 512K, reserved 1048576K
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at java.util.Arrays.copyOfRange(Arrays.java:3664)
	at java.lang.String.<init>(String.java:207)
	at java.lang.StringBuilder.toString(StringBuilder.java:407)
	at com.lyd.testboot.jvm.GCTest.main(GCTest.java:19)

我们来分析以上日志,首先会将对象存放在伊甸园区,但是伊甸园区直接爆满,就会触发YGC(Minor GC),第一行日志中

[GC (Allocation Failure) [PSYoungGen: 2040K->488K(2560K)] 2040K->943K(9728K), 0.0006030 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 

第一部分[PSYoungGen: 2040K->488K(2560K)]2040K代表GC之前新生代占用的内存大小,488K代表GC之后新生代占用的内存大小,这里没有变0是因为还有一些数据在幸存者区,(2560K)是记录着新生代的总空间大小。第二部分2040K->943K(9728K)2040K是堆空间在垃圾回收前占用的大小,943K是堆空间垃圾回收之后占用的大小,(9728K)是堆空间的总大小。
再看第七行日志

[GC (Allocation Failure) [PSYoungGen: 1536K->960K(2048K)] 7215K->6991K(9216K), 0.0003252 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 

很明显7215K->6991K(9216K),堆空间的占用却变大了,这是因为数据放大了老年代里面了。
再看第八行日志

[Full GC (Ergonomics) [PSYoungGen: 960K->0K(2048K)] [ParOldGen: 6031K->6073K(7168K)] 6991K->6073K(9216K), [Metaspace: 3311K->3311K(1056768K)], 0.0047554 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 

随着放到老年代的数据增多,老年代的空间也就不足了,就会触发Full GC,触发之后[PSYoungGen: 960K->0K(2048K)]新生代就被清空掉了。而元数据区[Metaspace: 3311K->3311K(1056768K)]是没有变化的,因为没有涉及到内存卸载的情况。
到最后一次GC日志

[Full GC (Ergonomics) [PSYoungGen: 1021K->0K(2048K)] [ParOldGen: 7167K->731K(7168K)] 8189K->731K(9216K), [Metaspace: 3344K->3344K(1056768K)], 0.0029345 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 

之后就内存不足了,也伴随着就会报OutOfMemoryError错误了。

*内存分配策略

这部分主要的是对象在伊甸园区创建并经过第一次Minor GC之后依然存活,并且能被幸存者区容纳,将被移动到幸存者空间之中,并且将对象年龄设置为1。对象在经过每次的Minor GC之后还存活,年龄就会增加1,当年龄达到一定的阈值(默认是15,每个JVM、每个GC都会有所不同),就会被晋升到老年代。修改这个阈值,可以通过-XX:MaxTenuringThreshold来设置。
对于不同的年龄段对象:

  • 优先分配到Eden
  • 大对象会直接分配到老年代
  • 长期存活的对象会分配到老年代
  • 动态对象年龄判断:如果幸存者区中相同年龄的所有对象大小的总和大于幸存者区空间的一半,年龄大于或者等于该年龄的对象可以直接进入老年代。

我们通过一个例子来看,如果幸存者空间不足以存放对象,对象将会放在老年代。

public class YoungOldAreaTest {
    public static void main(String[] args) {
        byte[] bytes = new byte[1024 * 1024 * 20]; // 20m
    }
}

在运行VM Option配置中设置参数-Xms60m -Xmx60m -XX:NewRatio=2 -XX:SurvivorRatio=8 -XX:+PrintGCDetails,运行之后,我们可以看到都没有执行过Minor GC,就已经将对象放在老年代了。

Heap
 PSYoungGen      total 18432K, used 3300K [0x00000000fec00000, 0x0000000100000000, 0x0000000100000000)
  eden space 16384K, 20% used [0x00000000fec00000,0x00000000fef39010,0x00000000ffc00000)
  from space 2048K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x0000000100000000)
  to   space 2048K, 0% used [0x00000000ffc00000,0x00000000ffc00000,0x00000000ffe00000)
 ParOldGen       total 40960K, used 20480K [0x00000000fc400000, 0x00000000fec00000, 0x00000000fec00000)
  object space 40960K, 50% used [0x00000000fc400000,0x00000000fd800010,0x00000000fec00000)
 Metaspace       used 3327K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 365K, capacity 388K, committed 512K, reserved 1048576K

因为后面参数带了-XX:+PrintGCDetails,如果有执行垃圾回收将会被打印出来。

TLAB(Thread-Local Allocation Buffer)

TLAB(Thread-Local Allocation Buffer)是一种用于提高多线程并发应用程序性能的内存分配优化技术。TLAB是专门为每个线程分配的小内存区域,用于分配对象实例。它的主要目的是减少线程之间的竞争和锁争用,以提高对象分配的效率。

什么是TLAB

  • 虽然堆是线程共有的,但里面含有一个线程私有的TLAB。
  • 从内存模型来看,它是对Eden区继续细分的一块区域,JVM 为每个线程分配了一个私有缓存区域。
  • 在多线程同时分配内存时,使用TLAB能够避免一些非线程安全的问题,同时还能够提升内存分配的吞吐量,这种内存分配也称为快速分配策略。

为什么要有TLAB

  • 堆区是线程共享的,任何线程都可以访问到堆区中的共享数据。
  • 由于对象实例的创建在 JVM 中非常频繁,因此在并发环境下从堆区中划分内存空间是线程不安全的。
  • 为避免多个线程操作同一地址,需要使用加锁等机制,进而影响分配速度

尽管不是所有的对象实例都能够在 TLAB 中成功分配内存,但 JVM 确实是将 TLAB 作为内存分配的首选。
通过-XX:UseTLAB设置是否开启TLAB空间。
默认情况下,TLAB 空间的内存非常小,仅占有整个 Eden 空间的 1%,我们可以通过 -XX:TLABWasteTargetPercent 设置 TLAB 空间所占用 Eden 空间的百分比大小。
一旦对象在 TLAB 空间分配内存失败时,JVM 就会尝试着通过使用加锁机制确保数据操作的原子性,从而直接在 Eden 空间中分配内存。

有TLAB对象的分配过程

我们再来回顾一下对象分配过程,当对象在伊甸园区创建之后,首先会分配到TLAB,如果TLAB分配得下,就会进行对象实例化,如果分配不下,就会在伊甸园区分配(在里面的一系列分配规则同此文上面所描述的过程)。

*逃逸分析(Escape Analysis)

逃逸分析(Escape Analysis)是一种编译器和运行时优化技术,用于确定在程序中创建的对象是否会"逃逸"出它们创建的方法或作用域,也就是说,它们是否会在方法的外部被引用或访问。逃逸分析的主要目标是帮助编译器进行更有效的优化,减少内存分配和提高程序的性能。是一种可以有效减少Java程序中同步负载和内存分配压力的跨函数全局数据流分析算法。

逃逸分析的基本行为

逃逸分析的基本行为就是分析对象动态作用域:

  • 当一个对象在方法中被定义后,对象只在方法内部使用,则认为没有发生逃逸。
  • 当一个对象在方法中被定义后,它被外部方法所引用,则认为发生逃逸。例如作为调用参数传递到其他地方中,称为方法逃逸。

我们可以通过以下代码来了解。

public static StringBuffer craeteStringBuffer(String s1, String s2) {
   StringBuffer sb = new StringBuffer();
   sb.append(s1);
   sb.append(s2);
   return sb;
}

如以上代码,StringBuffer sb是一个方法内部变量,上述代码中直接将sb返回,这样这个StringBuffer 有可能被其他方法所改变,这样它的作用域就不只是在方法内部,虽然它是一个局部变量,但是其能够逃逸到了方法外部。甚至还有可能被外部线程访问到,譬如赋值给类变量或可以在其他线程中访问的实例变量,称为线程逃逸。
如果要防止它逃逸出去,可以在返回的时候转成String对象。

public static String createStringBuffer(String s1, String s2) {
   StringBuffer sb = new StringBuffer();
   sb.append(s1);
   sb.append(s2);
   return sb.toString();
}

参数设置

  • 在JDK1.7之后,Hotspot就默认开启了逃逸分析。
  • 可以通过-XX:+DoEscapeAnalysis显式开启逃逸分析,通过-XX:+PrintEscapeAnalysis查看逃逸分析结果。

*注:在开发中能使用局部变量,就不要使用在方法外定义。

逃逸分析优化

开启逃逸分析后,编译程序能对代码进行优化,减少内存分配和提高程序的性能。

  • 栈上分配:如果逃逸分析确定一个对象不会逃逸出方法或作用域,编译器可以选择在栈上分配这个对象,而不是在堆上。栈上分配的对象具有较短的生命周期,不需要垃圾回收,因此可以提高程序的性能。
  • 同步消除:逃逸分析可以确定某些对象只能从一个线程访问到,不会被多个线程访问,从而可以避免不必要的同步操作,提高多线程程序的性能。
  • 分离对象或标量替换:有的对象可能不需要作为一个连续的内存结构存在也可以被访问到,那么对象的部分(或全部)可以不存储在内存,而存储在 CPU 寄存器中。

逃逸分析是一种复杂的分析技术,通常由编译器和运行时系统共同实施。编译器会在编译阶段分析代码以确定对象的逃逸情况,然后生成更有效的代码。在Java虚拟机中,逃逸分析通常会结合垃圾回收策略和即时编译技术来实现

1) 代码优化-栈上分配

JIT 编译器在编译期间根据逃逸分析的结果,发现如果一个对象并没有逃逸出方法的话,就可能被优化成栈上分配。分配完成后,继续在调用栈内执行,最后线程结束,栈空间被回收,局部变量对象也被回收。这样就无需进行垃圾回收了。

常见栈上分配的场景:成员变量赋值、方法返回值、实例引用传递

我们通过案例来看一下执行时间,首先我们先准备一个类,在主线程中循环1000万次,每次调用allocation()方法,此方法会new一个对象,这个对象是不会发生逃逸的。

public class StackAllocation {
    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        // 循环10000000次去执行allocation,相当于一直创建对象
        for (int i = 0; i < 10000000; i++) {
            allocation();
        }
        long end = System.currentTimeMillis();
        System.out.println("时间消耗:" + (end - start) + " ms");
        try {
            Thread.sleep(1000000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
    private static void allocation() {
        // 局部变量
        Show show = new Show();
    }
    static class Show {
    }
}

刚开始,我们在VM Options配置-Xmx1G -Xms1G -XX:-DoEscapeAnalysis -XX:+PrintGCDetails,先把逃逸分析关闭。运行之后,我们到Visual VM查看抽样器,可以看到这个Show对象实例达到了1000万个。
image.png
运行结束后,我们看一下控制台,时间消耗达到了92ms。

时间消耗:92 ms
Heap
 PSYoungGen      total 305664K, used 241173K [0x00000000eab00000, 0x0000000100000000, 0x0000000100000000)
  eden space 262144K, 92% used [0x00000000eab00000,0x00000000f9685588,0x00000000fab00000)
  from space 43520K, 0% used [0x00000000fd580000,0x00000000fd580000,0x0000000100000000)
  to   space 43520K, 0% used [0x00000000fab00000,0x00000000fab00000,0x00000000fd580000)
 ParOldGen       total 699392K, used 0K [0x00000000c0000000, 0x00000000eab00000, 0x00000000eab00000)
  object space 699392K, 0% used [0x00000000c0000000,0x00000000c0000000,0x00000000eab00000)
 Metaspace       used 9131K, capacity 9472K, committed 9728K, reserved 1058816K
  class space    used 1069K, capacity 1161K, committed 1280K, reserved 1048576K

接下来我们把逃逸分析打开,-Xmx1G -Xms1G -XX:+DoEscapeAnalysis -XX:+PrintGCDetails可以看到实例就没那么多了。
image.png
运行结束看看控制台,时间就经历了2ms。所以可以看到有了逃逸分析,在栈上分配会提高性能。

时间消耗:2 ms
Heap
 PSYoungGen      total 305664K, used 89129K [0x00000000eab00000, 0x0000000100000000, 0x0000000100000000)
  eden space 262144K, 34% used [0x00000000eab00000,0x00000000f020a518,0x00000000fab00000)
  from space 43520K, 0% used [0x00000000fd580000,0x00000000fd580000,0x0000000100000000)
  to   space 43520K, 0% used [0x00000000fab00000,0x00000000fab00000,0x00000000fd580000)
 ParOldGen       total 699392K, used 0K [0x00000000c0000000, 0x00000000eab00000, 0x00000000eab00000)
  object space 699392K, 0% used [0x00000000c0000000,0x00000000c0000000,0x00000000eab00000)
 Metaspace       used 9245K, capacity 9600K, committed 9984K, reserved 1058816K
  class space    used 1069K, capacity 1161K, committed 1280K, reserved 1048576K

2) 代码优化-同步消除

线程同步的代价是相当高的,同步的后果是降低并发性和性能。
在动态编译同步块的时候,JIT 编译器可以借助逃逸分析来判断同步块所使用的锁对象是否能够被一个线程访问而没有被发布到其他线程。如果没有,那么 JIT 编译器在编译这个同步块的时候就会取消对这个代码的同步。这样就能大大提高并发性和性能。这个取消同步的过程就叫做同步省略,也叫锁消除。
我们看以下代码,其中对one对象进行了加锁,但是one对象的生命周期只有在fun()方法里面,并不会被其他线程访问,所以在JIT编译阶段就会被优化。

public void fun() {
    Object one = new Object();
    synchronized (one) {
        System.out.println(one);
    }
}

会被优化成以下代码片段

public void fun() {
    Object one = new Object();
    System.out.println(one);
}

3) 代码优化-标量替换

标量(Scalar) 是指一个无法再分解成更小的数据的数据。Java 中的原始数据类型就是标量。
相对的,那些的还可以分解的数据叫做聚合量(Aggregate) ,Java 中的对象就是聚合量,因为其还可以分解成其他聚合量和标量。
在 JIT 阶段,通过逃逸分析确定该对象不会被外部访问,并且对象可以被进一步分解时,JVM 不会创建该对象,而会将该对象成员变量分解若干个被这个方法使用的成员变量所代替。这些代替的成员变量在栈帧或寄存器上分配空间。这个过程就是标量替换。

通过-XX:+EliminateAllocations可以开启标量替换(默认是开启的,允许将对象打散分配到栈上),-XX:+PrintEliminateAllocations查看标量替换情况。

我们通过一段代码来理解,同样的例子,只是我们内部有个Work类,里面有一个workId和workName字段,并且来查看统计消耗时间,首先我们在VM Options中配置-Xmx100m -Xms100m -XX:+DoEscapeAnalysis -XX:+PrintGC -XX:-EliminateAllocations先不开启标量替换。

public class ScalarReplace {
    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        // 循环10000000次去执行allocation,相当于一直创建对象
        for (int i = 0; i < 10000000; i++) {
            allocation();
        }
        long end = System.currentTimeMillis();
        System.out.println("时间消耗:" + (end - start) + " ms");
    }
    private static void allocation() {
        // 局部变量
        Work work = new Work(); // 未发生逃逸
        work.workId = 1;
        work.workName = "working";
    }
    static class Work {
        public int workId;
        public String workName;
    }
}

我们可以看一下运行结果,很明显能够看到,对象分配会经历GC垃圾回收,消耗时间为35ms

[GC (Allocation Failure)  25600K->1152K(98304K), 0.0009344 secs]
[GC (Allocation Failure)  26752K->1024K(98304K), 0.0005531 secs]
[GC (Allocation Failure)  26624K->976K(98304K), 0.0005057 secs]
[GC (Allocation Failure)  26576K->1040K(98304K), 0.0005935 secs]
[GC (Allocation Failure)  26640K->1008K(98304K), 0.0004587 secs]
[GC (Allocation Failure)  26608K->1072K(100864K), 0.0005802 secs]
[GC (Allocation Failure)  31792K->813K(100864K), 0.0005920 secs]
[GC (Allocation Failure)  31533K->813K(100864K), 0.0002328 secs]
时间消耗:35 ms

当我们把标量替换打开,我们可以看到就没有出现垃圾回收,所消耗的时间也就小很多。

时间消耗:2 ms

逃逸分析的效果取决于具体的代码和编译器实现,不是所有的逃逸都可以被消除,但它可以在一些情况下提供显著的性能提升,特别是在高性能和低延迟应用程序中。

堆空间的参数设置总结

这里来整理一下堆空间的一些参数设置

  • -XX:PrintFlagsInitial:查看所有的参数的默认初始值。
  • -XX:PrintFlagsFinal:查看所有的参数的最终值。
  • -XX:PrintGCDetails:输出详细的GC处理日志。
    • 简要信息:-XX:PrintGC or -verbose:gc
  • -Xms:初始化堆空间内存(默认物理内存的1/64)
  • -Xmx:设置最大堆空间内存(默认物理内存的1/4)
  • -Xmn:设置新生代的大小()初始值即最大值。
  • -XX:NewRatio:配置新生代和老年代在堆结构的占比。
  • -XX:SurvivorRatio:设置新生代Eden和S0/S1空间比例。
  • -XX:MaxTenuringThreshold:设置新生代垃圾的最大年龄。
  • -XX:HandlePromotionFailure:是否设置空间分配担保
  • -XX:+DoEscapeAnalysis:开启逃逸分析(+开启,-关闭)
  • -XX:+EliminateAllocations:开启标量替换(+开启,-关闭)
  • -Server:启动server模式,开启了才能使用逃逸分析,默认开启

总结

本次学习了堆的结构以及对象的分配过程,了解新生代、老年代以及永久代(元数据区)各自结构以及作用,对象分配优先经过那些地方,以及对Minor GC、Major GC、Full GC三种GC的使用和触发阶段。也透过了逃逸分析来了解三个优化代码方式,学到了几种参数配置的使用。

资料

JVM中新生代为什么要有两个Survivor(form,to)? - 知乎

👍创作不易,可能有些语言不是很通畅,如有错误请指正,感谢观看!记得点赞哦!👍

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1173605.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

学习视频剪辑:巧妙运用中画、底画,制作画中画,提升视频效果

随着数字媒体的普及&#xff0c;视频剪辑已经成为一项重要的技能。在视频剪辑过程中&#xff0c;制作画中画可以显著提升视频效果、信息传达和吸引力。本文讲解云炫AI智剪如何巧妙运用中画、底画批量制作画中画来提升视频剪辑水平&#xff0c;提高剪辑效率。 操作1、先执行云…

两种MySQL OCP认证应该如何选?

很多同学都找姚远老师说要参加MySQL OCP认证培训&#xff0c;但绝大部分同学并不知道MySQL OCP认证有两种&#xff0c;以MySQL 8.0为例。 一种是管理方向&#xff0c;叫&#xff1a;Oracle Certified Professional, MySQL 8.0 Database Administrator&#xff08;我考试的比较…

“我“何去何从?2024年软件测试职业发展方向?运筹帷幄方能决胜千里之外...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 测试的职责无非就…

《进化优化》第6章 进化策略

文章目录 6.1 &#xff08;11) 进化策略6.2 1/5规则&#xff1a;推导走廊问题 6.3 μ1进化策略6.4 μλ和&#xff08;μ,λ&#xff09;进化策略6.5 自身自适应进化策略协方差阵自适应CMA 6.1 &#xff08;11) 进化策略 假设f(x)是随机向量x的函数,我们想要最大化适应度f(x).…

Mysql进阶-SQL优化篇

插入数据 insert 我们需要一次性往数据库表中插入多条记录&#xff0c;可以从以下三个方面进行优化。 批量插入数据 一条insert语句插入多个数据&#xff0c;但要注意&#xff0c;每个insert语句最好插入500-1000行数据&#xff0c;就得重新写另一条insert语句 Insert into…

YOLO系列环境配置及训练

目录 前言 一、下载所需 1、Anaconda安装 2、NVIDIA 驱动程序安装 3、CUDA安装 4、CUDNN下载及配置 二、环境配置 1、虚拟环境创建 2、Pytorch安装 3、pycharm环境切换及剩余库的安装 4、YOLO代码的测试及训练配置步骤 &#xff08;1&#xff09;测试 &#xff08…

国际阿里云香港服务器!!!

轻量应用服务器&#xff08;Simple Application Server&#xff09;是可以快速搭建且易于管理的轻量级云服务器&#xff0c;面向单台服务器提供了一键部署应用、一站式域名解析、安全管理以及运维监控等服务。轻量应用服务器操作简单便捷&#xff0c;能让您快速上手部署简单的应…

AVL树

欢迎来到Cefler的博客&#x1f601; &#x1f54c;博客主页&#xff1a;那个传说中的man的主页 &#x1f3e0;个人专栏&#xff1a;题目解析 &#x1f30e;推荐文章&#xff1a;题目大解析&#xff08;3&#xff09; 目录 &#x1f449;&#x1f3fb;AVL树概念&#x1f449;&am…

专业媒体播放软件Movist Pro中文

Movist Pro是一款专为Mac用户设计的专业媒体播放器。它支持广泛的视频和音频格式&#xff0c;包括MP4、AVI、MKV等&#xff0c;并提供了高级播放控件和定制的视频设置。其直观易用的用户界面&#xff0c;使得播放高清视频更为流畅&#xff0c;且不会卡顿或滞后。同时&#xff0…

海康Visionmaster-全局触发:使用全局触发功能执行流 程的方法

我们这里以 TCP 通讯为例&#xff0c;视觉作为 TCP 服务端&#xff0c;与视觉交互的第三方设备作为 TCP 客户端。当 TCP 客户端连接上视觉服务端后&#xff0c;客户端发送字符串 T1,视觉执行流程 1&#xff1b; 客户端发送字符串 T2&#xff0c;视觉执行流程 2。 这样的需求我…

在 CelebA 数据集上训练的 PyTorch 中的基本变分自动编码器

摩西西珀博士 一、说明 我最近发现自己需要一种方法将图像编码到潜在嵌入中&#xff0c;调整嵌入&#xff0c;然后生成新图像。有一些强大的方法可以创建嵌入或从嵌入生成。如果你想同时做到这两点&#xff0c;一种自然且相当简单的方法是使用变分自动编码器。 这样的深度网络不…

Markdown语法教程

Markdown&#xff1a;一种轻量级语言&#xff0c;有简洁的编写方式&#xff0c;能够提高大家的工作效率。 一、标题 1.1 标题 标题的编写格式以#号开始&#xff0c;分别表示h1 ~ h6&#xff0c;注意&#xff1a;# 后面有空格&#xff01; # 一级标题 ## 二级标题 ### 三级标题…

K8s学习笔记——认识理解篇

1. K8s诞生背景 回顾应用的部署&#xff0c;经历了以下几个阶段&#xff1a; 传统部署&#xff1a;物理服务器上运行应用程序。虚拟机部署&#xff1a;物理服务器上安装虚拟机&#xff0c;在虚拟机上运行应用程序。容器部署&#xff1a;物理服务器上安装容器运行时&#xff0…

MINIO minio 安装 报错 问题

minio MINIO 安装 报错 问题 前言问题1问题产生原因分析解决方案 问题2原因分析解决方案 问题3问题产生原因分析解决方案 问题4问题产生 原因分析解决方案 问题5问题产生 原因分析解决方案 关键词&#xff1a; 1: WARNING: MINIO_ACCESS_KEY and MINIO_SECRET_KEY are deprecat…

全能数据分析软件 Tableau Desktop 2019 mac中文版功能亮点

Tableau Desktop 2019 mac是一款专业的全能数据分析工具&#xff0c;可以让用户将海量数据导入并记性汇总&#xff0c;并且支持多种数据类型&#xff0c;比如像是编程常用的键值对、哈希MAP、JSON类型数据等&#xff0c;因此用户可以将很多常用数据库文件直接导入Tableau Deskt…

2019 ICPC 银川题解(A)

赛时没发挥好6题金尾&#xff08;rank38&#xff09;&#xff0c;剩下很多能写的题&#xff0c;其中四个dp&#xff0c;傻眼ing A Girls Band Party&#xff08;背包&#xff09; 有点迷惑的题&#xff0c;当时看只要 5 5 5 张牌一下子想到暴力枚举&#xff0c;结果发现是不…

【漏洞复现】Redis未授权

感谢互联网提供分享知识与智慧&#xff0c;在法治的社会里&#xff0c;请遵守有关法律法规 文章目录 1.1、漏洞描述1.2、影响版本1.3、漏洞复现环境搭建写入计划任务获取反弹shell利用SSH写公钥&#xff0c;免密登陆利用Redis写WebShell 3、检测未授权访问漏洞检测工具参考 1.1…

《 Hello 算法 》 - 免费开源的数据结构与算法入门教程电子书,包含大量动画、图解,通俗易懂

这本学习算法的电子书应该是我看过这方面最好的书了&#xff0c;代码例子有多种编程语言&#xff0c;JavaScript 也支持。 《 Hello 算法 》&#xff0c;英文名称是 Hello algo&#xff0c;是一本关于编程中数据解构和算法入门的电子书&#xff0c;作者是毕业于上海交通大学的…

零代码复现-TCGA联合GEO免疫基因结合代谢基因生信套路(三)

前面的分析中&#xff0c;整理好的关键基因集表达谱矩阵&#xff0c;接下来就准备分子亚型的相关分析。 六、一致性聚类构建分子亚型 在6.TCGA和GEO差异基因获取和预后数据的整理\TCGA文件中获取文件 准备一个生存数据和表达谱矩阵&#xff0c;这里需要注意的是&#xff0c;…

基于Jenkins实现接口自动化持续集成,学完涨薪5k

一、JOB项目配置 1、添加描述 可选选项可填可不填 2、限制项目的运行节点 节点中要有运行环境所需的配置 节点配置教程&#xff1a;https://blog.csdn.net/YZL40514131/article/details/131504280 3、源码管理 需要将脚本推送到远程仓库中 4、构建触发器 可以选择定时构建…