With #2, you don't need a 'refresh token', the JWT itself is the refresh token, you can just sign a new one some time before the old one is set to expire. You don't need to store a separate session object on the back end, that's the whole point, just have the valid JWT token attached to the connection or request; if the user has an existing valid JWT attached to their socket or request, you can just use that one as the basis to create and sign a new one with the same data but a new expiry date. I find this works really well with stateful connections like WebSockets (especially since each frame has little overheads in terms of headers). You can re-issue JWTs in real time periodically (e.g.) with short 10 minute expiries or you can make the user request a new one periodically as well. There are pros and cons to either approach though the second one is more common.
So to summarize, JWTs save you from having to store and maintain a session object. You just don't need one. A blacklist or ban list is a lot easier to manage than session data, only a small number of users will be blacklisted. You could also add an isBanned field to the user's account object itself. No need to maintain a separate 'session'.
For log out, you just make the client remove their JWT from wherever they have it stored; e.g. memory, localStorage or sessionStorage. Once all valid signed JWTs have been physically deleted from wherever they were held, the user is logged out.
Even if you consider edge cases; e.g. If an attacker managed to grab hold of a user's JWT just before the user logged out; it begs the question, how did the attacker manage to do that? An XSS vulnerability in your front end? Well, once resolved, you'd probably want to invalidate all your previously issued JWT tokens (for all users) by changing your auth signing key on the back end... Just to be safe right? It's kind of neat that you can invalidate all previously issued JWT just by changing the signing key. It's instant and doesn't cost any resources unlike deleting large numbers of session objects from a datastore.
I can't think of any situation where you'd urgently want to invalidate a single JWT/session that couldn't be handled better by adding a single isBanned field to an account object or via some kind of IP blacklist. If you're using sessions with session IDs and the user is banned, you'd want to add some kind of flag to their account object itself right? Not just the session... With JWTs, you just need this flag to exist in 1 place, not 2.