I think the often missed context of "C# vs Java" discussion is that the comparison itself is flawed. In the public perception they are bundled together but this couldn't be more wrong.

Due to decisions in how their respective platforms/runtimes are implemented, and subsequent decisions in the languages themselves, the current day situation is that Java is the more "focused" one, excelling in "business programming" kind of codebases - OpenJDK, on average, tolerates extremely heavy codebases better. C# on the other hand has historically been more of a "swiss army knife" that cowered wider range of paradigms (including FP: higher order functions, pattern matching, LINQ) but ended up leaning quite heavily towards lower-level, or more expressive, constructs: structs, generic monomorphization (with structs), async/await and tasks, different flavours of AOT, fast FFI.

It also made key choices that did lend themselves to nicer performance foundation: all method calls are direct unless explicitly marked as virtual/abstract, cheaper virtual and interface dispatch, (already mentioned) true generics, monomorphized for structs and unsafe subset of syntax that allowed to overcome compiler weakness quite a bit or make more calls for fast path in C++ where-as the breakout point in slower JNI was higher.

As a technology, C#/.NET was in many ways better, but as a product it was worse - it was close source, source available at best. OpenJDK also caught up and outpaced .NET Framework's JIT with HotSpot, massively improved tiered compilation capabilities and escape analysis, and GC implementations that Java ecosystem offered ended up being superior at the time.

Mind you, performance was still miles ahead of the likes of Python, Ruby and Node.js, but it was quite bad for a compiled language that sometimes looked like strange, if nicer, C++.

This, however, has completely changed when what today is called .NET was open-sourced. There were multiple events occurring in the ecosystem at the time (WinRT not doing as well, .NET Native UWP runtime, etc.), but the crux is - .NET Core 1, 1.1, 2, 2.1 up until 3.1 saw a huge amount of msft internal performance work (both .NET and e.g. Midori project) adopted in the open source .NET Core.

All areas saw improvement: GC, JIT codegen quality, new performance primitives (Span<T>, hardware intrinsics and co.), standard library and surrounding frameworks (ASP.NET Core, EF Core). I remember to this day seeing how latency of a particular request dropped from 2s to about 350ms with identical code after migrating a service from .NET Framework 4.6.2 to .NET Core 3.1. The improvements were massive.

In total, .NET saw significant perf. improvements in one area or another for every subsequent release, for 8 versions straight. As a result, it managed to close the gap with OpenJDK on allocation and abstraction heavy enterprise code, remaining just slightly worse, and started performing really well in other areas that do not exist in Java or only as incubator features (structs, Vector API which is nowhere near close).

By becoming well-behaved FOSS citizen .NET did away with its troublesome past, and the way I tend to describe C# today to people unfamiliar with it is it's a strange secret higher-level Rust with OOP features, GC and low-level escape hatch to push performance everyone is looking for but does not realize it's right next to them. For your question on "is GC an issue?" it rarely is, and you will find applications written in C# of today, if they have at least an ounce of engineer's care, to have an order of magnitude lower allocation traffic, and much smaller memory footprint.