当前位置: 首页 > 知识库问答 >
问题:

为什么在没有垃圾收集开销的情况下,这个测试需要更长的时间?

闻慎之
2023-03-14

我在为异步消息传递开发轻量级库的过程中遇到了这种情况。为了了解创建大量中等大小的短生命周期对象的成本,我编写了下面的测试:

import java.nio.ByteBuffer;
import java.util.Random;


public class MemPressureTest {
    static final int SIZE = 4096;
    static final class Bigish {
        final ByteBuffer b;


        public Bigish() {
            this(ByteBuffer.allocate(SIZE));
        }

        public Bigish(ByteBuffer b) {
            this.b = b;
        }

        public void fill(byte bt) {
            b.clear();
            for (int i = 0; i < SIZE; ++i) {
                b.put(bt);
            }
        }
    }


    public static void main(String[] args) {
        Random random = new Random(1);
        Bigish tmp = new Bigish();
        for (int i = 0; i < 3e7; ++i) {
            tmp.fill((byte)random.nextInt(255));
        }
    }
}

在我的笔记本电脑上,使用默认的GC设置,它运行大约需要95秒:

/tmp$ time java -Xlog:gc MemPressureTest
[0.006s][info][gc] Using G1

real    1m35.552s
user    1m33.658s
sys 0m0.428s

这就是事情变得奇怪的地方。我调整了程序,为每次迭代分配一个新对象:

...
        Random random = new Random(1);
        for (int i = 0; i < 3e7; ++i) {
            Bigish tmp = new Bigish();
            tmp.fill((byte)random.nextInt(255));
        }
...

理论上,这应该会增加一些小开销,但是没有一个对象应该被提升到Eden之外。充其量,我希望运行时接近相同。然而,这个测试在大约17秒内完成:

/tmp$ time java -Xlog:gc MemPressureTest
[0.007s][info][gc] Using G1
[0.090s][info][gc] GC(0) Pause Young (Normal) (G1 Evacuation Pause) 23M->1M(130M) 1.304ms
[0.181s][info][gc] GC(1) Pause Young (Normal) (G1 Evacuation Pause) 76M->1M(130M) 0.870ms
[0.247s][info][gc] GC(2) Pause Young (Normal) (G1 Evacuation Pause) 76M->0M(130M) 0.844ms
[0.317s][info][gc] GC(3) Pause Young (Normal) (G1 Evacuation Pause) 75M->0M(130M) 0.793ms
[0.381s][info][gc] GC(4) Pause Young (Normal) (G1 Evacuation Pause) 75M->0M(130M) 0.859ms
[lots of similar GC pauses, snipped for brevity]
[16.608s][info][gc] GC(482) Pause Young (Normal) (G1 Evacuation Pause) 254M->0M(425M) 0.765ms
[16.643s][info][gc] GC(483) Pause Young (Normal) (G1 Evacuation Pause) 254M->0M(425M) 0.580ms
[16.676s][info][gc] GC(484) Pause Young (Normal) (G1 Evacuation Pause) 254M->0M(425M) 0.841ms

real    0m16.766s
user    0m16.578s
sys 0m0.576s

我运行了两个版本几次,结果与上述结果几乎相同。我觉得我一定错过了一些非常明显的东西。我疯了吗?什么可以解释这种性能差异?

===编辑===

按照阿潘金和丹1st的建议,我用JMH重写了测试:

import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.infra.Blackhole;

import java.nio.ByteBuffer;
import java.util.Random;


public class MemPressureTest {
    static final int SIZE = 4096;

    @State(Scope.Benchmark)
    public static class Bigish {
        final ByteBuffer b;
        private Blackhole blackhole;


        @Setup(Level.Trial)
        public void setup(Blackhole blackhole) {
            this.blackhole = blackhole;
        }

        public Bigish() {
            this.b = ByteBuffer.allocate(SIZE);
        }

        public void fill(byte bt) {
            b.clear();
            for (int i = 0; i < SIZE; ++i) {
                b.put(bt);
            }
            blackhole.consume(b);
        }
    }

    static Random random = new Random(1);


    @Benchmark
    public static void test1(Blackhole blackhole) {
        Bigish tmp = new Bigish();
        tmp.setup(blackhole);
        tmp.fill((byte)random.nextInt(255));
        blackhole.consume(tmp);
    }

    @Benchmark
    public static void test2(Bigish perm) {
        perm.fill((byte) random.nextInt(255));
    }
}

不过,第二次测试要慢得多:

> Task :jmh
# JMH version: 1.35
# VM version: JDK 18.0.1.1, OpenJDK 64-Bit Server VM, 18.0.1.1+2-6
# VM invoker: /Users/xxx/Library/Java/JavaVirtualMachines/openjdk-18.0.1.1/Contents/Home/bin/java
# VM options: -Dfile.encoding=UTF-8 -Djava.io.tmpdir=/Users/xxx/Dev/MemTests/build/tmp/jmh -Duser.country=US -Duser.language=en -Duser.variant
# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable)
# Warmup: 5 iterations, 10 s each
# Measurement: 5 iterations, 10 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Benchmark: com.xxx.MemPressureTest.test1

# Run progress: 0.00% complete, ETA 00:16:40
# Fork: 1 of 5
# Warmup Iteration   1: 2183998.556 ops/s
# Warmup Iteration   2: 2281885.941 ops/s
# Warmup Iteration   3: 2239644.018 ops/s
# Warmup Iteration   4: 1608047.994 ops/s
# Warmup Iteration   5: 1992314.001 ops/s
Iteration   1: 2053657.571 ops/s3s]
Iteration   2: 2054957.773 ops/sm 3s]
Iteration   3: 2051595.233 ops/sm 13s]
Iteration   4: 2054878.239 ops/sm 23s]
Iteration   5: 2031111.214 ops/sm 33s]

# Run progress: 10.00% complete, ETA 00:15:04
# Fork: 2 of 5
# Warmup Iteration   1: 2228594.345 ops/s
# Warmup Iteration   2: 2257983.355 ops/s
# Warmup Iteration   3: 2063130.244 ops/s
# Warmup Iteration   4: 1629084.669 ops/s
# Warmup Iteration   5: 2063018.496 ops/s
Iteration   1: 1939260.937 ops/sm 33s]
Iteration   2: 1791414.018 ops/sm 43s]
Iteration   3: 1914987.221 ops/sm 53s]
Iteration   4: 1969484.898 ops/sm 3s]
Iteration   5: 1891440.624 ops/sm 13s]

# Run progress: 20.00% complete, ETA 00:13:23
# Fork: 3 of 5
# Warmup Iteration   1: 2228664.719 ops/s
# Warmup Iteration   2: 2263677.403 ops/s
# Warmup Iteration   3: 2237032.464 ops/s
# Warmup Iteration   4: 2040040.243 ops/s
# Warmup Iteration   5: 2038848.530 ops/s
Iteration   1: 2023934.952 ops/sm 14s]
Iteration   2: 2041874.437 ops/sm 24s]
Iteration   3: 2002858.770 ops/sm 34s]
Iteration   4: 2039727.607 ops/sm 44s]
Iteration   5: 2045827.670 ops/sm 54s]

# Run progress: 30.00% complete, ETA 00:11:43
# Fork: 4 of 5
# Warmup Iteration   1: 2105430.688 ops/s
# Warmup Iteration   2: 2279387.762 ops/s
# Warmup Iteration   3: 2228346.691 ops/s
# Warmup Iteration   4: 1438607.183 ops/s
# Warmup Iteration   5: 2059319.745 ops/s
Iteration   1: 1112543.932 ops/sm 54s]
Iteration   2: 1977077.976 ops/sm 4s]
Iteration   3: 2040147.355 ops/sm 14s]
Iteration   4: 1975766.032 ops/sm 24s]
Iteration   5: 2003532.092 ops/sm 34s]

# Run progress: 40.00% complete, ETA 00:10:02
# Fork: 5 of 5
# Warmup Iteration   1: 2240203.848 ops/s
# Warmup Iteration   2: 2245673.994 ops/s
# Warmup Iteration   3: 2096257.864 ops/s
# Warmup Iteration   4: 2046527.740 ops/s
# Warmup Iteration   5: 2050379.941 ops/s
Iteration   1: 2050691.989 ops/sm 35s]
Iteration   2: 2057803.100 ops/sm 45s]
Iteration   3: 2058634.766 ops/sm 55s]
Iteration   4: 2060596.595 ops/sm 5s]
Iteration   5: 2061282.107 ops/sm 15s]


Result "com.xxx.MemPressureTest.test1":
  1972203.484 ±(99.9%) 142904.698 ops/s [Average]
  (min, avg, max) = (1112543.932, 1972203.484, 2061282.107), stdev = 190773.683
  CI (99.9%): [1829298.786, 2115108.182] (assumes normal distribution)


# JMH version: 1.35
# VM version: JDK 18.0.1.1, OpenJDK 64-Bit Server VM, 18.0.1.1+2-6
# VM invoker: /Users/xxx/Library/Java/JavaVirtualMachines/openjdk-18.0.1.1/Contents/Home/bin/java
# VM options: -Dfile.encoding=UTF-8 -Djava.io.tmpdir=/Users/xxx/Dev/MemTests/build/tmp/jmh -Duser.country=US -Duser.language=en -Duser.variant
# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable)
# Warmup: 5 iterations, 10 s each
# Measurement: 5 iterations, 10 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Benchmark: com.xxx.MemPressureTest.test2

# Run progress: 50.00% complete, ETA 00:08:22
# Fork: 1 of 5
# Warmup Iteration   1: 282751.407 ops/s
# Warmup Iteration   2: 283333.984 ops/s
# Warmup Iteration   3: 293785.079 ops/s
# Warmup Iteration   4: 268403.105 ops/s
# Warmup Iteration   5: 280054.277 ops/s
Iteration   1: 279093.118 ops/s9m 15s]
Iteration   2: 282782.996 ops/s9m 25s]
Iteration   3: 282688.921 ops/s9m 35s]
Iteration   4: 291578.963 ops/s9m 45s]
Iteration   5: 294835.777 ops/s9m 55s]

# Run progress: 60.00% complete, ETA 00:06:41
# Fork: 2 of 5
# Warmup Iteration   1: 283735.550 ops/s
# Warmup Iteration   2: 283536.547 ops/s
# Warmup Iteration   3: 294403.173 ops/s
# Warmup Iteration   4: 284161.042 ops/s
# Warmup Iteration   5: 281719.077 ops/s
Iteration   1: 276838.416 ops/s10m 56s]
Iteration   2: 284063.117 ops/s11m 6s]
Iteration   3: 282361.985 ops/s11m 16s]
Iteration   4: 289125.092 ops/s11m 26s]
Iteration   5: 294236.625 ops/s11m 36s]

# Run progress: 70.00% complete, ETA 00:05:01
# Fork: 3 of 5
# Warmup Iteration   1: 284567.336 ops/s
# Warmup Iteration   2: 283548.713 ops/s
# Warmup Iteration   3: 294317.511 ops/s
# Warmup Iteration   4: 283501.873 ops/s
# Warmup Iteration   5: 283691.306 ops/s
Iteration   1: 283462.749 ops/s12m 36s]
Iteration   2: 284120.587 ops/s12m 46s]
Iteration   3: 264878.952 ops/s12m 56s]
Iteration   4: 292681.168 ops/s13m 6s]
Iteration   5: 295279.759 ops/s13m 16s]

# Run progress: 80.00% complete, ETA 00:03:20
# Fork: 4 of 5
# Warmup Iteration   1: 284823.519 ops/s
# Warmup Iteration   2: 283913.207 ops/s
# Warmup Iteration   3: 294401.483 ops/s
# Warmup Iteration   4: 283998.027 ops/s
# Warmup Iteration   5: 283987.408 ops/s
Iteration   1: 278014.618 ops/s14m 17s]
Iteration   2: 283431.491 ops/s14m 27s]
Iteration   3: 284465.945 ops/s14m 37s]
Iteration   4: 293202.934 ops/s14m 47s]
Iteration   5: 290059.807 ops/s14m 57s]

# Run progress: 90.00% complete, ETA 00:01:40
# Fork: 5 of 5
# Warmup Iteration   1: 285598.809 ops/s
# Warmup Iteration   2: 284434.916 ops/s
# Warmup Iteration   3: 294355.547 ops/s
# Warmup Iteration   4: 284307.860 ops/s
# Warmup Iteration   5: 284297.362 ops/s
Iteration   1: 283676.043 ops/s15m 57s]
Iteration   2: 283609.750 ops/s16m 7s]
Iteration   3: 284575.124 ops/s16m 17s]
Iteration   4: 293564.269 ops/s16m 27s]
Iteration   5: 216267.883 ops/s16m 37s]


Result "com.xxx.MemPressureTest.test2":
  282755.844 ±(99.9%) 11599.112 ops/s [Average]
  (min, avg, max) = (216267.883, 282755.844, 295279.759), stdev = 15484.483
  CI (99.9%): [271156.731, 294354.956] (assumes normal distribution)

JMH黑洞应该阻止代码删除,而JMH现在负责运行独立迭代的事实应该阻止并行化,对吗?黑洞不也应该阻止物体被限制在堆栈里吗?此外,如果hotspot仍然在进行大量的优化,那么预热迭代之间不会有更多的变化吗?

共有3个答案

孟翰海
2023-03-14

你最初的问题和编辑过的JMH版本实际上略有不同。

在编辑的版本中,就像@apangin提到的,存储在静态字段中的指针烫发会阻止代码得到优化。

在你最初的问题中,是因为你忘记热身了。这是一个修改后的版本:

    public static void main(String[] args) {
        var t1 = System.currentTimeMillis();
        var warmup = Integer.parseInt(args[0]);
        for (int i = 0; i < warmup; i++) { test(1); }  // magic!!!
        test(1000000);
        var t2 = System.currentTimeMillis();
        System.out.println(t2 - t1);
    }
    
    private static void test(int n) {
        Random random = new Random(1);
        Bigish tmp = new Bigish();
        for (int i = 0; i < n; ++i) {
            tmp.fill((byte) random.nextInt(255));
        }
    }

它需要一个int参数热身来帮助JVM决定哪些方法应该内联。

在我的机器上,也就是OpenJDK Runtime Technology Zulu17.28 13-CA(build 17 35-LTS)在Windows上,当预热8000时,输出是不可预测的。通常需要~2.7秒,但偶尔只需要110毫秒。

当< code>warmup设置为< code>8500时,它几乎总是在110~120毫秒内完成。

您还可以使用-XX: UnlockDiagnostiVMOptions-XX: PrintInline选项来查看JVM如何内联方法。如果所有内容都完全内联,您应该能够看到类似的内容

  @ 24   A$Bigish::<init> (11 bytes)   inline (hot)
    @ 4   java.nio.ByteBuffer::allocate (20 bytes)   inline (hot)
      @ 16   java.nio.HeapByteBuffer::<init> (21 bytes)   inline (hot)
        @ 10   java.nio.ByteBuffer::<init> (47 bytes)   inline (hot)
          @ 8   java.nio.Buffer::<init> (105 bytes)   inline (hot)
            @ 1   java.lang.Object::<init> (1 bytes)   inline (hot)
            @ 39   java.nio.ByteBuffer::limit (6 bytes)   inline (hot)
              @ 2   java.nio.ByteBuffer::limit (8 bytes)   inline (hot)
                @ 2   java.nio.Buffer::limit (65 bytes)   inline (hot)
            @ 45   java.nio.ByteBuffer::position (6 bytes)   inline (hot)
              @ 2   java.nio.ByteBuffer::position (8 bytes)   inline (hot)
                @ 2   java.nio.Buffer::position (52 bytes)   inline (hot)
          @ 17   java.nio.ByteOrder::nativeOrder (4 bytes)   inline (hot)
    @ 7   A$Bigish::<init> (10 bytes)   inline (hot)
      @ 1   java.lang.Object::<init> (1 bytes)   inline (hot)

靠近输出的底部。

请注意,只有当BigishByteBufferconstructor完全内联时,JVM才能断言底层缓冲区对另一个线程永远不可见,这允许对缓冲区的写入被安全矢量化。

顺便说一句,这是又一个显示基准测试是多么棘手的案例。如果不深入细节,很难判断哪个部分是真正的性能瓶颈。甚至JMH也可能会误导人。

龚弘业
2023-03-14

以下只是一个理论,他们可能完全错误。我既不是JIT也不是GC专家。

我认为JIT优化掉了(一些)你的代码。如果是这种情况,它检测到您实际上没有使用存储的值,只是删除了分配/填充对象的代码。像JmH黑洞这样的东西可能会帮助你。

也可能是它并行化了代码的情况。由于循环不同的迭代彼此独立,因此可以并行执行多个迭代。

另一种可能性是,它检测到对象受到堆栈限制,范围非常窄,并立即删除。因此,它可能已将对象移动到堆栈中,在那里可以快速分配/推送和取消分配/弹出对象。

JIT 可能总是会做意想不到的事情。不要过早地优化,也不要猜测瓶颈在哪里。在您进行任何更改之前和之后衡量您的表现。性能可能不会丢失您期望的性能。在其他语言中也是如此,尤其是在Java中。

而且,正如评论中提到的,你真的应该使用JMH。

凌俊语
2023-03-14

在填充之前创建新的ByteBuffer确实有助于JIT编译器在使用相对put方法时生成更好的优化代码,原因如下。

  1. JIT编译单元是一种方法。HotSpot JVM不执行整个程序优化,这在理论上也是很困难的,因为Java的动态特性和开放世界的运行时环境
  2. 当JVM编译test1方法时,缓冲区实例化与填充出现在同一编译范围内:
Bigish tmp = new Bigish();
tmp.setup(blackhole);
tmp.fill((byte)random.nextInt(255));

JVM知道关于创建的缓冲区的一切:它的确切大小和支持数组,它知道缓冲区尚未发布,其他线程也看不到它。因此,JVM可以极大地优化填充循环:使用AVX指令将其矢量化,并将其展开以一次设置512字节

  0x000001cdf60c9ae0:   mov    %r9d,%r8d
  0x000001cdf60c9ae3:   movslq %r8d,%r9
  0x000001cdf60c9ae6:   add    %r11,%r9
  0x000001cdf60c9ae9:   vmovdqu %ymm0,0x10(%rcx,%r9,1)
  0x000001cdf60c9af0:   vmovdqu %ymm0,0x30(%rcx,%r9,1)
  0x000001cdf60c9af7:   vmovdqu %ymm0,0x50(%rcx,%r9,1)
  0x000001cdf60c9afe:   vmovdqu %ymm0,0x70(%rcx,%r9,1)
  0x000001cdf60c9b05:   vmovdqu %ymm0,0x90(%rcx,%r9,1)
  0x000001cdf60c9b0f:   vmovdqu %ymm0,0xb0(%rcx,%r9,1)
  0x000001cdf60c9b19:   vmovdqu %ymm0,0xd0(%rcx,%r9,1)
  0x000001cdf60c9b23:   vmovdqu %ymm0,0xf0(%rcx,%r9,1)
  0x000001cdf60c9b2d:   vmovdqu %ymm0,0x110(%rcx,%r9,1)
  0x000001cdf60c9b37:   vmovdqu %ymm0,0x130(%rcx,%r9,1)
  0x000001cdf60c9b41:   vmovdqu %ymm0,0x150(%rcx,%r9,1)
  0x000001cdf60c9b4b:   vmovdqu %ymm0,0x170(%rcx,%r9,1)
  0x000001cdf60c9b55:   vmovdqu %ymm0,0x190(%rcx,%r9,1)
  0x000001cdf60c9b5f:   vmovdqu %ymm0,0x1b0(%rcx,%r9,1)
  0x000001cdf60c9b69:   vmovdqu %ymm0,0x1d0(%rcx,%r9,1)
  0x000001cdf60c9b73:   vmovdqu %ymm0,0x1f0(%rcx,%r9,1)
  0x000001cdf60c9b7d:   mov    %r8d,%r9d
  0x000001cdf60c9b80:   add    $0x200,%r9d
  0x000001cdf60c9b87:   cmp    %r10d,%r9d
  0x000001cdf60c9b8a:   jl     0x000001cdf60c9ae0
Bigish tmp = new Bigish();
volatileField = tmp;  // publish
tmp.setup(blackhole);
tmp.fill((byte)random.nextInt(255));

循环优化中断;现在,数组字节被逐个填充,位置字段也相应地递增。

  0x000001829b18ca5c:   nopl   0x0(%rax)
  0x000001829b18ca60:   cmp    %r11d,%esi
  0x000001829b18ca63:   jge    0x000001829b18ce34           ;*if_icmplt {reexecute=0 rethrow=0 return_oop=0}
                                                            ; - java.nio.Buffer::nextPutIndex@10 (line 721)
                                                            ; - java.nio.HeapByteBuffer::put@6 (line 209)
                                                            ; - bench.MemPressureTest$Bigish::fill@22 (line 33)
                                                            ; - bench.MemPressureTest::test1@28 (line 47)
  0x000001829b18ca69:   mov    %esi,%ecx
  0x000001829b18ca6b:   add    %edx,%ecx                    ;*getfield position {reexecute=0 rethrow=0 return_oop=0}
                                                            ; - java.nio.Buffer::nextPutIndex@1 (line 720)
                                                            ; - java.nio.HeapByteBuffer::put@6 (line 209)
                                                            ; - bench.MemPressureTest$Bigish::fill@22 (line 33)
                                                            ; - bench.MemPressureTest::test1@28 (line 47)
  0x000001829b18ca6d:   mov    %esi,%eax
  0x000001829b18ca6f:   inc    %eax                         ;*iinc {reexecute=0 rethrow=0 return_oop=0}
                                                            ; - bench.MemPressureTest$Bigish::fill@26 (line 32)
                                                            ; - bench.MemPressureTest::test1@28 (line 47)
  0x000001829b18ca71:   mov    %eax,0x18(%r10)              ;*putfield position {reexecute=0 rethrow=0 return_oop=0}
                                                            ; - java.nio.Buffer::nextPutIndex@25 (line 723)
                                                            ; - java.nio.HeapByteBuffer::put@6 (line 209)
                                                            ; - bench.MemPressureTest$Bigish::fill@22 (line 33)
                                                            ; - bench.MemPressureTest::test1@28 (line 47)
  0x000001829b18ca75:   cmp    %r8d,%ecx
  0x000001829b18ca78:   jae    0x000001829b18ce14
  0x000001829b18ca7e:   movslq %esi,%r9
  0x000001829b18ca81:   add    %r14,%r9
  0x000001829b18ca84:   mov    %bl,0x10(%rdi,%r9,1)         ;*bastore {reexecute=0 rethrow=0 return_oop=0}
                                                            ; - java.nio.HeapByteBuffer::put@13 (line 209)
                                                            ; - bench.MemPressureTest$Bigish::fill@22 (line 33)
                                                            ; - bench.MemPressureTest::test1@28 (line 47)
  0x000001829b18ca89:   cmp    $0x1000,%eax
  0x000001829b18ca8f:   jge    0x000001829b18ca95           ;*if_icmpge {reexecute=0 rethrow=0 return_oop=0}
                                                            ; - bench.MemPressureTest$Bigish::fill@14 (line 32)
                                                            ; - bench.MemPressureTest::test1@28 (line 47)
  0x000001829b18ca91:   mov    %eax,%esi
  0x000001829b18ca93:   jmp    0x000001829b18ca5c

这正是测试2中发生的事情。由于 ByteBuffer 对象位于编译范围之外,因此 JIT 无法像本地尚未发布的对象那样自由地优化它。

好消息是,这是可能的。只需使用绝对< code>put方法,而不是相对方法。在这种情况下,< code>position字段保持不变,JIT可以轻松地对循环进行矢量化,而没有破坏ByteBuffer不变量的风险。

for (int i = 0; i < SIZE; ++i) {
    b.put(i, bt);
}

通过这种改变,在两种情况下循环都将被矢量化。更好的是,现在< code>test2变得比< code>test1快得多,证明对象创建确实有性能开销。

Benchmark               Mode  Cnt      Score     Error   Units
MemPressureTest.test1  thrpt   10   2447,370 ± 146,804  ops/ms
MemPressureTest.test2  thrpt   10  15677,575 ± 136,075  ops/ms
  1. 违反直觉的性能差异是由于 JVM 无法在 ByteBuffer 对象创建不在编译范围内时无法对填充循环进行矢量化。
  2. 在可能的情况下,更喜欢绝对的获取/放置方法而不是相对方法。绝对方法通常要快得多,因为它们不会更新 ByteBuffer 的内部状态,并且 JIT 可以应用更积极的优化。
  3. 对象创建确实有开销,正如修改后的基准测试所示。
 类似资料:
  • 在我们的kafka broker设置中,GC平均需要20毫秒,但随机增加到1-2秒。极端情况持续9秒。这种情况的发生频率相当随机。平均每天发生15次。我尝试过使用GCEasy,但没有给出任何见解。我的内存使用率为20%,但进程仍然使用交换,尽管内存可用。感谢您对如何将其最小化的任何意见 JVM选择: GC日志:

  • 问题内容: 在下面的示例中,new Thread()没有任何引用。可能是被废弃的垃圾收集了吗?同样,在不扩展Thread类或实现可运行的情况下,我们如何创建线程? 问题答案: 如果尚未启动的新线程无法正常访问,则将对其进行垃圾回收。 已经启动的新线程成为垃圾回收“根”。它不会(直到)完成才被垃圾收集。 在下面的示例中,new Thread()没有任何引用。可能是被废弃的垃圾收集了吗? 不。它已经启

  • 我使用dotMemory来分析我的应用程序,我注意到下面的行为:在我的代码中有一些点,我通过使用手动执行垃圾回收机制 在dotMemory内部,我看到内存实际上在这些点上被释放了,但是如果在那之后我点击“强制气相色谱”,就会收集更多的垃圾。他们这样做的方式是什么,为什么我的代码没有收集内存,是否有可能实现相同级别的收集? 我还尝试执行多个集合,即。 尽管它似乎回收了更多的内存,但它从未接近dotM

  • 问题内容: 我很难处理Java垃圾回收问题并解释日志。 我的应用程序要求GC的时间不要超过2秒,理想情况下是少于100ms。 根据先前的一些建议,我正在尝试以下命令行选项: 该应用程序具有大量长期存储的对象,这些对象保存在ConcurrentLinkedHashMap中。我偶尔会出现长时间的停顿,在最坏的情况下可能会长达10秒(这是倒数第二次,如下面的GC日志所示)! 这是我得到的一些输出: 我已

  • 问题内容: 是什么决定了垃圾收集器何时真正收集?它是在一定时间之后还是在一定数量的内存用完之后发生的吗?还是还有其他因素? 问题答案: 它在确定是时候运行时运行。在世代垃圾收集器中,一种常见的策略是在第0代内存分配失败时运行收集器。也就是说,每次你分配一小块内存(大块通常直接放置在“旧”代中)时,系统都会检查gen-0堆中是否有足够的可用空间,如果没有,则运行GC释放空间以使分配成功。然后将旧数据

  • 问题内容: 我经常读到,在Sun JVM中,短寿命对象(“相对较新的对象”)比长寿命对象(“相对较旧的对象”)可以更有效地进行垃圾回收。 为什么呢? 这是特定于Sun JVM还是由一般的垃圾回收原理导致? 问题答案: 大多数Java应用程序都会创建Java对象,然后很快将其丢弃。您可以在方法中创建一些对象,然后一旦退出该方法,所有对象都会死亡。大多数应用程序都是以这种方式运行的,并且大多数人倾向于