Before Creating the Enhancement Request
Summary
Remove two per-message allocation sources on the proxy gRPC path: the MessageDigest.getInstance("MD5") lookup for every delivered message, and the getBytes(UTF_8) calls used only for length validation on every received message.
Motivation
GrpcConverter#buildSystemProperties computes the body digest for every message delivered to a gRPC consumer, and BinaryUtil.calculateMd5 performs a MessageDigest.getInstance("MD5") provider lookup plus a fresh digest instance on every call.
SendMessageActivity#buildMessageProperty (and validateMessageGroup) call str.getBytes(StandardCharsets.UTF_8) on every user-property key/value, the tag, each message key, and the message group — only to read .length for size validation; the byte arrays are discarded immediately. That is 2N+ transient arrays per received message (N = user property count).
Solution
BinaryUtil: keep one MessageDigest per thread in a ThreadLocal (MessageDigest is not thread safe) and reset() before each use.
SendMessageActivity: add a utf8Length(String) helper that computes the UTF-8 encoded length without materializing the array, matching String.getBytes(UTF_8).length exactly, including the single-byte replacement for unpaired surrogates; replace the five call sites.
Verification
BinaryUtilTest (new): digest matches a fresh MessageDigest and stays stable across interleaved calls on the reused per-thread instance. SendMessageActivityTest#testUtf8Length: sample-by-sample equality with getBytes(UTF_8).length covering ASCII, CJK, supplementary (emoji), and unpaired surrogates; full class 12/12.
- Dedicated gRPC A/B on a 4-node cluster: proxy (cluster mode) plus a loopback load tool built on
rocketmq-client-java 5.0.7 (producer 8 threads with multi-byte user properties exercising utf8Length, SimpleConsumer 4 threads exercising the digest path), swapping the proxy's rocketmq-common/rocketmq-proxy jars per arm; 3 interleaved trials plus 1 reversed-order control: ~9.4k send TPS / ~5.1k consume TPS, zero failures in all 8 arms; proxy young GC showed a pure positional artifact (first arm of each pair always 9, second always 10, independent of the jar — confirmed by the reversed-order control), i.e. parity after correction. No regression; the allocation saving itself is below GC-count resolution, so this is a cleanup-level optimization on the proxy hot path.
Before Creating the Enhancement Request
Summary
Remove two per-message allocation sources on the proxy gRPC path: the
MessageDigest.getInstance("MD5")lookup for every delivered message, and thegetBytes(UTF_8)calls used only for length validation on every received message.Motivation
GrpcConverter#buildSystemPropertiescomputes the body digest for every message delivered to a gRPC consumer, andBinaryUtil.calculateMd5performs aMessageDigest.getInstance("MD5")provider lookup plus a fresh digest instance on every call.SendMessageActivity#buildMessageProperty(andvalidateMessageGroup) callstr.getBytes(StandardCharsets.UTF_8)on every user-property key/value, the tag, each message key, and the message group — only to read.lengthfor size validation; the byte arrays are discarded immediately. That is 2N+ transient arrays per received message (N = user property count).Solution
BinaryUtil: keep oneMessageDigestper thread in aThreadLocal(MessageDigestis not thread safe) andreset()before each use.SendMessageActivity: add autf8Length(String)helper that computes the UTF-8 encoded length without materializing the array, matchingString.getBytes(UTF_8).lengthexactly, including the single-byte replacement for unpaired surrogates; replace the five call sites.Verification
BinaryUtilTest(new): digest matches a freshMessageDigestand stays stable across interleaved calls on the reused per-thread instance.SendMessageActivityTest#testUtf8Length: sample-by-sample equality withgetBytes(UTF_8).lengthcovering ASCII, CJK, supplementary (emoji), and unpaired surrogates; full class 12/12.rocketmq-client-java5.0.7 (producer 8 threads with multi-byte user properties exercisingutf8Length, SimpleConsumer 4 threads exercising the digest path), swapping the proxy'srocketmq-common/rocketmq-proxyjars per arm; 3 interleaved trials plus 1 reversed-order control: ~9.4k send TPS / ~5.1k consume TPS, zero failures in all 8 arms; proxy young GC showed a pure positional artifact (first arm of each pair always 9, second always 10, independent of the jar — confirmed by the reversed-order control), i.e. parity after correction. No regression; the allocation saving itself is below GC-count resolution, so this is a cleanup-level optimization on the proxy hot path.