什么?!通过方法句柄进行无操作 254 元方法调用为 3 MiB 与 36 MiB

Ali*_*vei -1 java memory jvm version methodhandle

可以看出(请参见下面重写的测试用例),当由其工具编译和运行时,254 元方法的方法句柄在 Java 17 中占用的内存少于 Java 11 中的内存。

由于 Java 版本1117的发行摘要中没有宣传与方法句柄或反射相关的功能,所以我很好奇:哪些变化有助于减少内存消耗?


这是一个重写的测试用例ArityLimits.java

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Supplier;

class ArityLimits
{
    public static void main(String[] args) throws Throwable
    {
        // Pick an implementation with the 1st argument, e.g. core.
        final Invocable invocable = (args.length > 0
                    && args[0].equalsIgnoreCase("core"))
            ? new CoreInvoker(ArityLimits::new)
            : new HandleInvoker(ArityLimits::new,
                MethodHandles.privateLookupIn(
                        ArityLimits.class,
                        MethodHandles.lookup()));

        // Pick which methods to call with the 2nd argument, e.g.
        // $(( 1|2|4|8 )).
        final int agenda = (args.length > 1)
            ? 0xf & Integer.parseInt(args[1])
            : (1 | 2 | 4 | 8);
        final Map<String, List<?>> arguments = new HashMap<>(8);

        if ((agenda & 1) != 0)
            arguments.put("passCrunchLongsFix128",
                Collections.nCopies(128 - 2 /* (handle, this) */, 0L));

        if ((agenda & 2) != 0)
            arguments.put("passClassCrunchLongsFix128",
                Collections.nCopies(128 - 1 /* (handle) */, 0L));

        if ((agenda & 4) != 0)
            arguments.put("passCrunchIntsFix255",
                Collections.nCopies(255 - 2 /* (handle, this) */, 0));

        if ((agenda & 8) != 0)
            arguments.put("passClassCrunchIntsFix255",
                Collections.nCopies(255 - 1 /* (handle) */, 0));

        final Consumer<Method> announcer = ("Linux".equalsIgnoreCase(
                    System.getProperty("os.name", "")))
            ? method -> System.err.format("\033[7m%s\033[0m%n",
                            method.getName())
            : method -> System.err.println(method.getName());

        for (Method method : ArityLimits.class.getDeclaredMethods()) {
            final List<?> methodArgs = arguments.get(
                            method.getName());

            if (methodArgs == null)
                continue;

            invocable.invoke(method, methodArgs);
            announcer.accept(method);
        }
    }

    private interface Invocable
    {
        void invoke(Method method, List<?> args) throws Throwable;

        static boolean warnTooManyArgs(Method method, List<?> args)
        {
            if (method.isVarArgs() || args.size() < 256)
                return false;

            System.err.println(method.getName()
                    .concat(": too many arguments"));
            return true;
        }
    }

    private static final class CoreInvoker implements Invocable
    {
        private final Supplier<?> instanceer;

        CoreInvoker(Supplier<?> instanceer)
        {
            this.instanceer = instanceer;
        }

        public void invoke(Method method, List<?> args)
                    throws ReflectiveOperationException
        {
            if (Invocable.warnTooManyArgs(method, args))
                return;

            method.invoke((Modifier.isStatic(method.getModifiers()))
                    ? null
                    : instanceer.get(),
                args.toArray());
        }
    }

    private static final class HandleInvoker implements Invocable
    {
        private final Supplier<?> instanceer;
        private final Lookup lookup;

        HandleInvoker(Supplier<?> instanceer, Lookup lookup)
        {
            this.instanceer = instanceer;
            this.lookup = lookup;
        }

        public void invoke(Method method, List<?> args) throws Throwable
        {
            // Here a handle shall check its parameter constraints.
            final MethodHandle mh1 = lookup.unreflect(method);

            if (Invocable.warnTooManyArgs(method, args))
                return;

            final MethodHandle mh2 = (Modifier.isStatic(
                            method.getModifiers()))
                ? mh1
                : mh1.bindTo(instanceer.get());
            mh2.invokeWithArguments(args);
        }
    }

    void passCrunchLongsFix128(/* MethodHandle mh, ArityLimits al, */
            long _001, long _002, long _003, long _004, long _005,
            long _006, long _007, long _008, long _009, long _010,
            long _011, long _012, long _013, long _014, long _015,
            long _016, long _017, long _018, long _019, long _020,
            long _021, long _022, long _023, long _024, long _025,
            long _026, long _027, long _028, long _029, long _030,
            long _031, long _032, long _033, long _034, long _035,
            long _036, long _037, long _038, long _039, long _040,
            long _041, long _042, long _043, long _044, long _045,
            long _046, long _047, long _048, long _049, long _050,

            long _051, long _052, long _053, long _054, long _055,
            long _056, long _057, long _058, long _059, long _060,
            long _061, long _062, long _063, long _064, long _065,
            long _066, long _067, long _068, long _069, long _070,
            long _071, long _072, long _073, long _074, long _075,
            long _076, long _077, long _078, long _079, long _080,
            long _081, long _082, long _083, long _084, long _085,
            long _086, long _087, long _088, long _089, long _090,
            long _091, long _092, long _093, long _094, long _095,
            long _096, long _097, long _098, long _099, long _100,

            long _101, long _102, long _103, long _104, long _105,
            long _106, long _107, long _108, long _109, long _110,
            long _111, long _112, long _113, long _114, long _115,
            long _116, long _117, long _118, long _119, long _120,
            long _121, long _122, long _123, long _124, long _125,
            long _126) { }

    static void passClassCrunchLongsFix128(/* MethodHandle mh, */
            long _001, long _002, long _003, long _004, long _005,
            long _006, long _007, long _008, long _009, long _010,
            long _011, long _012, long _013, long _014, long _015,
            long _016, long _017, long _018, long _019, long _020,
            long _021, long _022, long _023, long _024, long _025,
            long _026, long _027, long _028, long _029, long _030,
            long _031, long _032, long _033, long _034, long _035,
            long _036, long _037, long _038, long _039, long _040,
            long _041, long _042, long _043, long _044, long _045,
            long _046, long _047, long _048, long _049, long _050,

            long _051, long _052, long _053, long _054, long _055,
            long _056, long _057, long _058, long _059, long _060,
            long _061, long _062, long _063, long _064, long _065,
            long _066, long _067, long _068, long _069, long _070,
            long _071, long _072, long _073, long _074, long _075,
            long _076, long _077, long _078, long _079, long _080,
            long _081, long _082, long _083, long _084, long _085,
            long _086, long _087, long _088, long _089, long _090,
            long _091, long _092, long _093, long _094, long _095,
            long _096, long _097, long _098, long _099, long _100,

            long _101, long _102, long _103, long _104, long _105,
            long _106, long _107, long _108, long _109, long _110,
            long _111, long _112, long _113, long _114, long _115,
            long _116, long _117, long _118, long _119, long _120,
            long _121, long _122, long _123, long _124, long _125,
            long _126, long _127) { }

    void passCrunchIntsFix255(/* MethodHandle mh, ArityLimits al, */
            int _001, int _002, int _003, int _004, int _005,
            int _006, int _007, int _008, int _009, int _010,
            int _011, int _012, int _013, int _014, int _015,
            int _016, int _017, int _018, int _019, int _020,
            int _021, int _022, int _023, int _024, int _025,
            int _026, int _027, int _028, int _029, int _030,
            int _031, int _032, int _033, int _034, int _035,
            int _036, int _037, int _038, int _039, int _040,
            int _041, int _042, int _043, int _044, int _045,
            int _046, int _047, int _048, int _049, int _050,

            int _051, int _052, int _053, int _054, int _055,
            int _056, int _057, int _058, int _059, int _060,
            int _061, int _062, int _063, int _064, int _065,
            int _066, int _067, int _068, int _069, int _070,
            int _071, int _072, int _073, int _074, int _075,
            int _076, int _077, int _078, int _079, int _080,
            int _081, int _082, int _083, int _084, int _085,
            int _086, int _087, int _088, int _089, int _090,
            int _091, int _092, int _093, int _094, int _095,
            int _096, int _097, int _098, int _099, int _100,

            int _101, int _102, int _103, int _104, int _105,
            int _106, int _107, int _108, int _109, int _110,
            int _111, int _112, int _113, int _114, int _115,
            int _116, int _117, int _118, int _119, int _120,
            int _121, int _122, int _123, int _124, int _125,
            int _126, int _127, int _128, int _129, int _130,
            int _131, int _132, int _133, int _134, int _135,
            int _136, int _137, int _138, int _139, int _140,
            int _141, int _142, int _143, int _144, int _145,
            int _146, int _147, int _148, int _149, int _150,

            int _151, int _152, int _153, int _154, int _155,
            int _156, int _157, int _158, int _159, int _160,
            int _161, int _162, int _163, int _164, int _165,
            int _166, int _167, int _168, int _169, int _170,
            int _171, int _172, int _173, int _174, int _175,
            int _176, int _177, int _178, int _179, int _180,
            int _181, int _182, int _183, int _184, int _185,
            int _186, int _187, int _188, int _189, int _190,
            int _191, int _192, int _193, int _194, int _195,
            int _196, int _197, int _198, int _199, int _200,

            int _201, int _202, int _203, int _204, int _205,
            int _206, int _207, int _208, int _209, int _210,
            int _211, int _212, int _213, int _214, int _215,
            int _216, int _217, int _218, int _219, int _220,
            int _221, int _222, int _223, int _224, int _225,
            int _226, int _227, int _228, int _229, int _230,
            int _231, int _232, int _233, int _234, int _235,
            int _236, int _237, int _238, int _239, int _240,
            int _241, int _242, int _243, int _244, int _245,
            int _246, int _247, int _248, int _249, int _250,

            int _251, int _252, int _253) { }

    static void passClassCrunchIntsFix255(/* MethodHandle mh, */
            int _001, int _002, int _003, int _004, int _005,
            int _006, int _007, int _008, int _009, int _010,
            int _011, int _012, int _013, int _014, int _015,
            int _016, int _017, int _018, int _019, int _020,
            int _021, int _022, int _023, int _024, int _025,
            int _026, int _027, int _028, int _029, int _030,
            int _031, int _032, int _033, int _034, int _035,
            int _036, int _037, int _038, int _039, int _040,
            int _041, int _042, int _043, int _044, int _045,
            int _046, int _047, int _048, int _049, int _050,

            int _051, int _052, int _053, int _054, int _055,
            int _056, int _057, int _058, int _059, int _060,
            int _061, int _062, int _063, int _064, int _065,
            int _066, int _067, int _068, int _069, int _070,
            int _071, int _072, int _073, int _074, int _075,
            int _076, int _077, int _078, int _079, int _080,
            int _081, int _082, int _083, int _084, int _085,
            int _086, int _087, int _088, int _089, int _090,
            int _091, int _092, int _093, int _094, int _095,
            int _096, int _097, int _098, int _099, int _100,

            int _101, int _102, int _103, int _104, int _105,
            int _106, int _107, int _108, int _109, int _110,
            int _111, int _112, int _113, int _114, int _115,
            int _116, int _117, int _118, int _119, int _120,
            int _121, int _122, int _123, int _124, int _125,
            int _126, int _127, int _128, int _129, int _130,
            int _131, int _132, int _133, int _134, int _135,
            int _136, int _137, int _138, int _139, int _140,
            int _141, int _142, int _143, int _144, int _145,
            int _146, int _147, int _148, int _149, int _150,

            int _151, int _152, int _153, int _154, int _155,
            int _156, int _157, int _158, int _159, int _160,
            int _161, int _162, int _163, int _164, int _165,
            int _166, int _167, int _168, int _169, int _170,
            int _171, int _172, int _173, int _174, int _175,
            int _176, int _177, int _178, int _179, int _180,
            int _181, int _182, int _183, int _184, int _185,
            int _186, int _187, int _188, int _189, int _190,
            int _191, int _192, int _193, int _194, int _195,
            int _196, int _197, int _198, int _199, int _200,

            int _201, int _202, int _203, int _204, int _205,
            int _206, int _207, int _208, int _209, int _210,
            int _211, int _212, int _213, int _214, int _215,
            int _216, int _217, int _218, int _219, int _220,
            int _221, int _222, int _223, int _224, int _225,
            int _226, int _227, int _228, int _229, int _230,
            int _231, int _232, int _233, int _234, int _235,
            int _236, int _237, int _238, int _239, int _240,
            int _241, int _242, int _243, int _244, int _245,
            int _246, int _247, int _248, int _249, int _250,

            int _251, int _252, int _253, int _254) { }
}
Run Code Online (Sandbox Code Playgroud)

这是一个参数文件args

-XX:+UnlockExperimentalVMOptions
-XX:+UseEpsilonGC
-XX:+AlwaysPreTouch
-Xms136m
-Xmx136m
-Xlog:heap*=info,gc=info
Run Code Online (Sandbox Code Playgroud)

将文件保存在例如/tmp目录中,然后启动 Java 11 版本的 Docker 容器:

docker run --entrypoint /bin/sh --interactive \
  --rm --tty --volume="/tmp:/tmp" \
  --workdir="/tmp" eclipse-temurin:11-jdk-alpine    # It may pull in ~200 MiB.

javac -Xdiags:verbose -Xlint ArityLimits.java
java @args ArityLimits handle $(( 1 | 2 | 4 | 8 ))  # 91/136 MiB of heap used
java @args ArityLimits handle 8                     # 36/136 (see title)
java @args ArityLimits core $(( 1 | 2 | 4 | 8 ))    #  1/136
java @args ArityLimits core 8                       #  1/136
rm *.class
exit
Run Code Online (Sandbox Code Playgroud)

现在,启动一个Java 17版本的Docker容器:

docker run --entrypoint /bin/sh --interactive \
  --rm --tty --volume="/tmp:/tmp" \
  --workdir="/tmp" eclipse-temurin:17-jdk-alpine    # It may pull in ~200 MiB.

javac -Xdiags:verbose -Xlint ArityLimits.java
java @args ArityLimits handle $(( 1 | 2 | 4 | 8 ))  #  5/136 MiB of heap used
java @args ArityLimits handle 8                     #  3/136 (see title)
java @args ArityLimits core $(( 1 | 2 | 4 | 8 ))    #  1/136
java @args ArityLimits core 8                       #  1/136
rm *.class
exit
Run Code Online (Sandbox Code Playgroud)

Hol*_*ger 6

当然,高内存消耗的主要原因是选项-XX:+UseEpsilonGC,因为关闭垃圾收集器意味着将所有临时对象保留在内存中而不是被回收。

\n

您可以简单地在程序末尾进行堆转储来查看主导对象并使用分析工具来跟踪分配。与 Java 17(4,000 个实例)相比,我在 Java 11 中发现了相当多的StringBuilder实例(170,000 个),进而发现了byte[]数组StringBuilderasync-profiler揭示的分配来源之一是Java 中嵌入的ASM 库jdk.internal.org.objectweb.asm.Type的一个类。

\n

Java 11 使用该库的版本 6,而 Java 17 使用版本 8。在这些版本之间,有一个更改记录为

\n
\n

asm.Type 中的小优化

\n
\n

该类中经常调用的方法之一是

\n
\n
public String getDescriptor() {\n    StringBuilder buf = new StringBuilder();\n    getDescriptor(buf);\n    return buf.toString();\n}\n
Run Code Online (Sandbox Code Playgroud)\n
\n

我们不需要深入研究实际的实现方法就可以看到有一个无条件分配的xe2x80x99 StringBuilder。相比之下,新的实现看起来像

\n
\n
public String getDescriptor() {\n    if (sort == OBJECT) {\n        return valueBuffer.substring(valueBegin - 1, valueEnd + 1);\n    } else if (sort == INTERNAL) {\n        return \'L\' + valueBuffer.substring(valueBegin, valueEnd) + \';\';\n    } else {\n        return valueBuffer.substring(valueBegin, valueEnd);\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n
\n

仅在某些情况下才会分配 a StringBuilderint这尤其适用于有很多或参数的情况long,这些参数将在最后一个分支处结束,只需调用方法签名即可生成等于orsubstring的字符串。相比之下,旧代码除了结果字符串之外,还为每次调用分配一个实例和一个长度为 16(默认容量)的数组。"I""J"StringBuilderbyte[]

\n

可能有更多的分配站点,在 Java 版本之间有所不同,但我认为这为您提供了有关在哪里搜索以及如何搜索的必要信息。

\n

  • 是的,这样的测试用例是合理的,但是你仍然要做好高内存消耗的准备。30MB 并不算多,而且参数数量最大化也是一种特殊情况。尽管如此,我还是发现深入研究这个问题很有趣。 (2认同)