I do not use C# (in fact, I do my best to avoid Microsoft products). But I can tell you that there are obvious workarounds C# employs to work around its lackluster GC. Stack allocation and spans immediately come to mind.
As far as I know, Java offers no way to mark objects as stack-allocated, but C# does. Spans in C# allow programmers to produce subarrays without copying. Enums in C# are stack-allocated, unlike Java. So on, so forth. None of this is a huge deal for Java since some of the best GCs in the world are implemented atop the JVM. But I do think C# offers its workarounds when GC performance gets in the way.
But these are all tredeoffs, that make C# almost as complex as C++. Sure, there are cases where these low-level optimizations allow for better performance, but don’t forget that the more things we specify, the less freedom does the runtime have. SQL is a good example here, it specifies the what and not the how, and this makes a good db very hard to beat on complex queries.
The way I have seen it described somewhere: C# has a slightly higher performance ceiling, but a naive application may very well run faster in Java.