What Happens When I Call a Java Method?
A guide to Java method invocation basics, explaining what happens when you call a Java method, method signatures, pass-by-value, stack vs. heap memory and more.
Nov 8, 2024 • 9 Minute Read
In this article, we’re going to take an up-close look at what happens when you call a method in Java and in other languages in general. We’ll talk about signatures, modifiers, pass-by-value, and how these matter whenever a method is invoked.
Dinner with Friends
My partner and I had a nice dinner with friends the other night. The chef was Peruvian and very creative with the dishes he’d design. My favorite was this roasted asparagus with whipped feta and strawberry salsa; so yummy!
Anyway, I was thinking, as one does, that a menu at a restaurant is a bit like a set of Java methods. A menu item has a name; I give that name to the waiter, and they bring me back the requested food. A method also has a name; I call that name in a Java program, and Java invokes it, bringing me back its return value.
No, I didn’t spring that analogy on our dinner guests. We don’t pay them enough to stay our friends and hear my nerdy musings.
But you get to, so let’s dive in!
Menu Items and Method Signatures
Every restaurant has a menu (at least those I’ve been to), and on these menus are the things you can order. They usually look something like this:
Cheeseburger with Carrots …….. $13
And if we were to write a Java program that represents this (delightful?) menu item, it might look something like this:
public Cheeseburgers orderCheeseBurgerWithCarrots(int count,
double amount) throws OutOfStockException;
This is called the method signature or the thing that uniquely identifies this method from all other methods in the application runtime. It’s important that the method signature be unique, just like each menu item needs to be unique; otherwise, how would the waiter know which you want?
Ordering and Invoking
Even though the assumption is that I want just one of the item that I’m ordering at the restaurant, I can technically ask them to bring me 2 or 3, which would not be weird at all. Invoking a method also affords this same flexibility in two ways:
- You can pass a parameter that says how many you want
- You can call the method multiple times
The first one, given our example method above, would look something like this:
Cheeseburgers cheeseburgers = orderCheeseBurgerWithCarrots
(2, 26.00);
So, as you can see, to call a Java method you need to do at least two things:
- Type the name of the method
- Pass the needed parameters to it
Most of the time you need one more step first, and that is to have a reference to the class where the method is declared. For example, if the method is not static (like the one we declared), it would likely be declared in a class like this one:
public class Menu {
public Cheeseburgers orderCheeseBurgerWithCarrots(int count,
double amount)
throws OutOfStockException {
// … make count cheeseburgers
return cheeseburgers;
}
}
And that means that you need to first have a reference to an instance of Menu to invoke it:
Menu menu = new Menu();
Cheeseburgers cheeseburgers = menu.orderCheeseBurgerWithCarrots
(4, 52.00);
Once that’s settled, your waiter can take your order to the kitchen and Java can take your parameters to the method.
Stacks, Heaps, and Tickets
In the olden days before computers, waiters would write your order down… on paper… and give that paper (or ticket) to the kitchen. This part is important because the waiter needs to be able to leave and go take other orders from other tables. If the waiter needed to sit there and wait for the cook to be done, then customers could be left waiting.
Likewise, when you call a method in Java, the parameters are placed on a stack, each parameter representing an item in that stack, like so:
|-----------|
| 4 |
|-----------|
| 52.00 |
|-----------|
Because Java remembers the method’s signature, it knows that the first item in the stack is the number of burgers and the second item in the stack is the dollar amount.
Given your level of experience with Java, you may already know that each slot in the stack has a very specific size so that the JVM can locate them quickly in memory. But, you may also know that not all data has the same size; think arrays or objects.
Hold that thought, and we’ll come back to it shortly. For now, remember the term heap and that variables are stored on the stack and sometimes part of a variable’s data is stored on that heap.
Cooking and Executing
First, though, let’s talk about what happens in the kitchen. In a restaurant, the cook makes the meal, and in Java, the runtime runs the body of the method.
In Java, we are always inside the execution of a method, even from the beginning. To illustrate this, consider Java’s most famous method:
public static void main(String[] args) { … }
This method has a signature also, and it is the first method that the runtime invokes. To get other things to happen, that main method usually needs to call other methods (and those methods call other methods).
It might look something like this:
1: public static void main(String[] args) {
2: Menu menu = new Menu();
3: Cheeseburgers cheeseburgers =
menu.orderCheeseBurgerWithCarrots(4, 52.00);
4: Person person = new Person();
5: person.eat(cheeseburgers); // yum!
6: }
In the program above, it first creates a `Menu` instance on line 2. Then the program proceeds to line 3 to invoke the `orderCheeseBurgerWithCarrots` method. The runtime stops processing the `main` method and moves into the `orderCheeseBurgerWithCarrots` method.
The runtime then runs all the lines inside the order method, returning the cheeseburgers back to the `main` method where the runtime can then proceed to line 4.
What was that thing about stacks of heaps again?
Okay, so some programming metaphors can be a little abstract sometimes. A stack takes its imagery from things like a stack of pancakes; you add from the bottom up and you remove from the top down. In computer memory, you can imagine a stack of memory slots of equal size; each method parameter is placed into one of these slots. Because the slots are equal in size, a computer can do simple arithmetic to immediately locate a given slot.
The issue is that not all method parameters are going to be the same size. Some may even vary in size from one call to another, like if one of the parameters is a list or array. How Java and many other languages solve this is to place a pointer or reference on the stack, like a road sign. This road sign points the computer from one memory location -- the stack -- to another -- the heap, like so:
Stack Heap
fixed-size data dynamically-sized data
|---------|
| 4 |
|---------|
|52.00|
|---------|
|--->| cheese | tomatoes | carrots | brussel sprouts | sriracha |
|---------|
In this case, we’re imagining a method signature that can also take a list of toppings:
public Cheeseburgers orderCheeseBurgerWithCarrots(int count,
double amount, List<String> toppings)
throws OutOfStockException;
Because the list of toppings doesn’t fit on the stack, Java instead sends a pointer that the runtime can follow from the stack, where method parameters live, to the heap, where the values are.
Is Java Pass By Reference or By Value?
You may have heard the terms pass-by-reference and pass-by-value when talking about how a language passes variables to a method.
“By reference” means that the language sends references (pointers) to data instead of the actual data. “By value” means that the language sends copies of the data instead of the actual data.
Before I dramatically reveal what Java does, let’s look even closer at what happens when Java invokes a method.
When main invokes orderCheeseBurgerWithCarrots, it copies all of the values main has and moves them into orderCheeseBurgerWithCarrots’s stack:
main’s stack Heap order’s stack
fixed-size data dynamically-sized data fixed-size data
|---------| |---------|
| 4 | | 4 |
|---------| |---------|
|52.00| |52.00|
|---------| |---------|
| ->|cheese|tomatoes|carrots|brusselsprouts|sriracha|<- |
|---------| |---------|
The keyword is that it copies all of the stack values, meaning that Java is pass-by-value. Even though one of the values is an arrow, it still copies that arrow.
Just like the restaurant kitchen, though, languages are full of things that you may have felt better before you knew them.
Notice that the heap in the middle is shared by everyone. So, even though Java makes a copy of the pointer, if orderCheeseBurgerWithCarrots were to add to the topping list itself, then main would see the change:
1: public Cheeseburgers orderCheeseBurgerWithCarrots
(int quantity, double amount, List<String> toppings) {
2: toppings.add(“horseradish”); // everyone loves this, right??
3: // … kitchen stuff
4: return cheeseburgers;
5: }
6:
7: public static void main(String[] args) {
8: Menu menu = new Menu();
9: List<String> toppings = new ArrayList<>(List.of(“cheese”,
“lettuce”, “tomatoes”));
10: assert toppings.size() == 3; // pass!
11: Cheeseburgers cheeseburgers =
menu.orderCheeseBurgerWithCarrots
(4, 52.00, toppings);
12: assert toppings.size() == 3; // fail!
13: // …
Because of this Java exhibits pass-by-reference behavior with objects.
Serving and Returning
Once the kitchen is done, it’s time for the waiter to bring the food you ordered back to you. And when Java is done executing a method, it’s time for the runtime to give the final value back to the caller.
This is what is happening between line 4 and 11 in the most recent snippet. The program executes line 7 first (Java always enters from a main method), then 8-11. And then, it jumps to line 1-4 where it makes the cheeseburgers. Once it’s done, it returns to line 11, sets the variable to the result of the method and moves to line 12.
This is also done with pass-by-value. Java copies the return value back to main’s stack. Since Cheeseburgers is an object, that means that main gets a copy of the pointer.
Conclusion
In this article, you saw what happens when Java calls a method. Given a unique method signature, it takes the parameters from one stack and copies them into another. The method uses this copy to perform its operations and then returns its result. Because it copies these values, we say that Java is pass-by-value; however, since Java also uses the concept of a heap, it can sometimes look like pass-by-reference.
Wait! I have more questions!
What happens when a method experiences an error?
What about if there is more than one method with the same name, but different parameter lists? Or a method of the same name in a superclass?
And how does the waiter take other orders while mine is being made?
These are important questions for another day. You can learn more by reading my article on exception handling or by watching the following Java courses:
Scaling Java Applications Through Concurrency
Object-oriented Programming in Java
Java SE 17
Java SE 11 Developer Certification (1Z0-819)
Java Coding Practices
For now, though, maybe sit down and enjoy the four carrot cheeseburgers that you just paid $52 for.