There's a few things to consider. If you're talking about "Communicating" inside of a single application, the usual route is to create an object or data structure that holds the data you want to communicate, and simply giving it to the class/method that will use it.
It's fairly common to want to share information between different threads, in which case all threads need to have access to the variable somehow, and you need to deal with concurrency issues.
MOMs usually don't work on the intra-process level, because passing objects/data is far more convenient when considering just one process. A MOM will simplify communication in the case where information has to be shared amongst various processes.
It's sort of like this: You and I use the same language. If you tell me to go refill your cup of coffee, I know how to do it. I might do it begrudgingly, but I know what to do. This kind of communication is what you have inside your own process - everyone understands each other. You just need to figure out a way of getting the right data from one entity to another.
A MOM comes into play when you want to talk to someone who won't understand you(another process). There's a lot of work that goes into transmitting information. You might think it's simple to send a Boolean or an array of integers, but it can get very involved. What is a Boolean? A bit? A byte? An integer? What's an Integer(let alone an integer array)? Are they 8 bits, 16 bits, 32 bits, 64 bits? The MOM on one end will basically take care of the hassle of sending a message that says "I'm sending you an array with 20 values which are 32-bit integers" followed by said array. The receiving MOM understands the message and can process it. The reason you don't use a MOM inside a process is because these kinds of messages take up more space than what's necessary.
If I'm one subroutine and you're another, all I need to do is write the array to an agreed-upon location in memory(a variable), and you'll be able to see the array. We may have agreed upon the size of the array ahead of time(ie pre-compilation) but even that doesn't need to be communicated in some cases. You're effectively using the language, compilation, and typing system as your MOM in this case, because those are the things that give meaning to "The array starts at address 0xDEADBEEF and uses 512 bytes of memory".
Now, you could still use a MOM to send things inside the same process, by pointing the sender and receiver at the loopback address, but this is a lot of overhead when simply passing variables/parameters is what you're really doing.
Edit: A concrete example with Java
Per OP's coupling concerns, I'd like to present a concrete example of sharing messages in Java that has minimal coupling between components. I've used this on several projects and it's fairly straight forward.
The base of it is to use an object like a BlockingQueue. If you don't want to read the documentation the take-away is that this is a thread-safe queue that allows multiple threads to put things in, and take them out without you having to worry about concurrency. So you'd end up with something like BlockingQueue<Message>. The next step is to get this object in the hands of the threads that will use it. That means you either pass the reference to the object to each thread, or you can wrap the queue itself in a singleton.
A simple implementation where you can pass the reference to a thread:
public class Producer implements Runnable {
BlockingQueue<Message> queue;
@Override
public void run() {
// TODO : Produce messages
}
public void initialize(BlockingQueue<Message> q) {
queue = q;
}
}
And the calling code:
// Just a compile-able example of initializing such a queue:
BlockingQueue<Message> queue = new ArrayBlockingQueue<Message>(200);
// ...
Producer producer = new Producer();
producer.initialize(queue);
Thread t = new Thread(producer);
t.start();
Consumers of these messages would look very similar. But you can already see that it becomes very simple to make multiple types of producers(and consumers), and all of these producers/consumers are merely coupled to a queue. Whether it's a MockProducer, DemoProducer, RealProducer, RealConsumer, etc, the only coupling between them is the queue. The 'communication' between producers and consumers is basically whether the queue is full, empty, or somewhere in between. (And what you would do in each of these states is another discussion entirely)
Of course there are other options besides queues - you can utilize any data structure you want, with any messaging protocol you can implement. You just need to be concerned about the thread-safety of your reads/writes to the data structure. I chose the queue though because it closely resembles my experience with reading/writing from a MOM - Is there a Message? Then I'll consume it. Can I put something there? Then I'll produce a message.