Brian & Chuck, 42 38 32 bytes
_#Jgnnq."Yqtnf#_{? #{<{>-?>--.>? Introducing my latest esolang, originally submitted for Create a programming language that only appears to be unusableCreate a programming language that only appears to be unusable.
Each of the two lines defines a Brainfuck-like program which operates on the other program's source code - the first program is called Brian and the second is called Chuck. That makes "Hello, World!" about as simple as it is in Self-modifying BrainfuckSelf-modifying Brainfuck (compared to Brainfuck itself).
I said that looping was too expensive in B&C to be worthwhile for a simple "Hello, World!", but it turns out I was wrong. Now I'm much less convinced that the code is optimal as it stands...
Explanation
One note about the source code: when parsing it, the interpreter replaces all _ with null bytes to make it easier to insert zero cells into the tapes.
Notice that Jgnnq."Yqtnf# is Hello, World! shifted by two characters. Why is it shifted? Because the , in Hello, World! is a valid command which would set a cell on Chuck to -1. We could shift it by one character (either way), but then the , would turn into either + or - which are also valid commands. We could reverse those at the end of Brian but the code as above has the same byte count and it seems a bit neater: we shift them by two characters, such that . becomes , which is a no-op for Brian.
So, when the program begins, Brian ignores everything on the tape until {? which switches control to Chuck, starting on the second command.
{<{> on Chuck finds the first non-zero cell on Brian (initially the #, which is just a dummy no-op). We decrement it with -. If that didn't make the cell zero yet, ? switches control back to Brian. Brian again ignores all the "code" in Jgnnq."Ypynf#_ and resets the loop on Chuck with {?.
Once that first cell has been zeroed, ? is a no-op. >--. moves to the next cell, subtracts 2 (to correct the offset) and prints it. Then we check if there's another character left to print by moving one to the right with >. If this reaches the null byte after the string (the _ on Brian's tape), then ? is a no-op and the program terminates. If that isn't a null byte yet, we've got more printing to do, and start over by switching to Brian who resets the loop with {? once more.