عرض عملي: رسم stack وheap ثم التحقق في المصحّح (debugger)
في هذا الدرس 11 موضوعات
هذا الدرس متاح حاليًا باللغة الإنجليزية فقط، ولم تُنشر نسخته العربية بعد.
Lesson Overview
You now have a mental model: frames on the stack, objects on the heap, references as arrows, aliases when two arrows meet at one object, and eligibility when no arrows remain. A model is only useful if you can check it against reality.
This lesson is a guided demo. You write a short program, draw it before running it, then use the debugger to compare your drawing with what the JVM actually holds. The method is the same one the lab (BEG-038-LAB1, Draw it) asks of you for four programs, so this is your rehearsal.
The debugger tells you the truth about a program in a way that print statements cannot: you can pause on any line and look at every variable. You will also see the "planned mistake" that most learners make: drawing two objects where there is one.
Learning Objectives
By the end of this lesson you will be able to:
- Predict a program's variables and shared objects from a hand drawing before running it.
- Set a breakpoint and step through code with the debugger.
- Read the variables panel to tell primitives from references and to recognise the same object seen through two variables.
- Compare a drawing with the debugger and correct the drawing when they disagree.
- Observe a variable that holds
nulland a variable that was repointed. - Describe the drawing and checking workflow for the lab.
Written Explanation
The workflow: draw, predict, run, compare
Follow the same five steps for every program:
- Read the program once without running it.
- Draw frames and heap at the point you care about (a chosen line).
- Predict the values the program will print and the variables the debugger will show.
- Run it with a breakpoint on that line.
- Compare. Where the debugger and the drawing disagree, the debugger is right. Change the drawing and write down what you misunderstood.
This is the habit that matters, not the tool.
What the debugger shows
Debuggers differ between IDEs, and menus, shortcuts, and panel names vary by IDE and version, so use your IDE's help if a name here does not match. Typically you will find:
- a gutter next to the line numbers where a click sets a breakpoint (execution pauses before that line runs),
- a Debug (or Run in debug mode) action next to the usual Run,
- step over (run the current line, stay in this method), step into (enter the method being called), and resume (run to the next breakpoint),
- a frames or call stack list, with the newest frame on top,
- a variables panel for the selected frame.
For primitives the panel shows the value. For object variables it usually shows the type, a short description such as the contents of a list, and often an identifier such as @ followed by a number. That identifier is a debugger label for the object, and it differs from run to run and between IDEs. If two variables show the same label, they refer to the same object.
Reading the stack in the debugger
The frames list is the stack from lesson 1, shown as text. When the debugger pauses inside addAgain, the list shows addAgain on top and main below it. Selecting main in that list switches the variables panel to main's slots, so you can compare both frames at the same moment. This is the quickest way to see that a parameter and the caller's variable are two different slots that may hold the same reference.
A good first drawing is small. For this demo, three things are worth tracking: the two integers, the one list that a and b share, and the extra list created inside addAgain. Do not draw the internals of ArrayList (its internal array and size field). They are real, but they distract from the question you are answering, which is who points to what.
Finally, decide in advance which line you will pause on. Pausing on the last line of a method gives a different drawing from pausing on the first, because a frame's slots fill in as the lines run.
Our program
import java.util.*;
public class L05 {
static void addAgain(List<String> l) {
l.add("c");
l = new ArrayList<>();
l.add("lost");
}
public static void main(String[] args) {
int count = 2;
int copy = count;
copy = 3;
List<String> a = new ArrayList<>();
List<String> b = a;
a.add("a");
b.add("b");
addAgain(a);
System.out.println(a + " " + b + " " + (a == b));
b = null;
System.out.println(count + " " + copy + " " + a + " " + b);
}
}Examples
Example 1: draw before running (simple)
Choose the line l.add("lost"); inside addAgain. Before running, draw:
STACK HEAP
addAgain (top)
l [ ●--------------------> ArrayList [] <- new list, then "lost"
main
count [ 2 ]
copy [ 3 ]
a [ ●--------+
+----------> ArrayList ["a","b","c"]
b [ ●--------+My predictions: count stays 2 and copy is 3 (separate copies); a and b point to one list; l in addAgain was first an alias of that list (c was added through it), then moved to a brand new list.
Example 2: run and compare (practical)
Run with no debugger first. I compiled and ran this program with JDK 21:
[a, b, c] [a, b, c] true
2 3 [a, b, c] nullThis matches the prediction:
aandbprint the same contents, anda == bistrue: one object."lost"never appears in either list: it went into the new list that onlyaddAgain's parameter pointed to, and that list became unreachable when the method returned.- After
b = null;,astill holds the list;bshowsnull.
Now set a breakpoint on l.add("lost");, debug, and check the screen against the drawing. What you expect to see (labels will differ):
| Frame | Variable | Expected in the variables panel |
|---|---|---|
addAgain | l | a new empty list with its own label |
main | a | list with a, b, c, label X |
main | b | list with a, b, c, same label X |
main | count, copy | 2 and 3 |
Step over the line and watch l gain lost without touching a.
Example 3: the planned mistake and the correction (realistic)
A common first drawing of List<String> b = a; shows two lists, one for each variable, each holding the same text. It looks harmless, and it predicts that b.add("b") leaves a unchanged.
The debugger breaks that prediction in two ways: after b.add("b") the list seen through a also contains b, and the labels shown for a and b are the same. You correct the drawing: one list object, with two arrows. The same lesson applies to the parameter l: the debugger shows it has the same label as a at the start of addAgain (they are aliases), and a different label after l = new ArrayList<>();.
To check what happens after assigning null, step over b = null; and look at b: the panel shows null, and a is unchanged. Nothing else was freed or changed. The list remains reachable through a.
Write a one-line correction in your notes, for example: "Assignment copies the arrow, not the object." Treat these notes as part of your diagram quiz preparation.
Backend Insight
Real debugging sessions on a backend are the same exercise at larger scale. You pause a request-handling thread at a breakpoint, read the frames to see who called whom, and use object labels to answer "is this the same entity instance as the one in the other layer?" or "who else holds this list?". Some IDEs can also show which objects refer to a selected object, but that depends on the tool, so check your IDE's documentation.
Two cautions for real services:
- Do not debug production systems by pausing them. Use logs, and reproduce the issue locally.
- A debugger shows you what is reachable at that moment. It does not show you what garbage collection has done or will do. If a drawing says an object is eligible, the debugger cannot confirm that it has been freed.
Common Mistakes
Mistake 1: trusting the drawing over the debugger
What goes wrong: you assume the tool is confusing, and keep a wrong picture.
Fix: if they disagree, the debugger is right. Find the line where your picture first went wrong.
Mistake 2: drawing after running
What goes wrong: you copy what the debugger shows and learn nothing.
Fix: always predict first and write the prediction down.
Mistake 3: comparing object labels across runs
What goes wrong: you expect the same @ number every time, or between two IDEs.
Fix: labels only tell you whether two variables share an object within one pause.
Mistake 4: stopping at the wrong line
What goes wrong: the breakpoint pauses before the line runs, so the values you read are from before it executed.
Fix: step over the line, then look.
Exercises
Level A: Foundation
- Which of these is a debugger action: "step over", "compile", or "refactor"?
- In a variables panel, two list variables show the same label. What does that tell you?
- A breakpoint is set on line 10. Has line 10 run when the program pauses?
Level B: Application
- Draw and predict, then check by running. Write down your predicted values for
nums[0],nums[1], andother[0]:
int[] nums = {1, 2};
int[] other = nums;
other = new int[]{5};
nums[0] = 7;- Describe what you would see in the variables panel for
numsandotherafter the last line, and whether they share a label. - In your own words, state why
"lost"in the demo never appears ina.
Level C: Challenge
- Write your own four-line program in which an object becomes unreachable (for example by reassigning a variable). Draw it before and after the line that removes the last arrow. Run it in the debugger and note two things you can confirm and one thing the debugger cannot show you.
أظهر الحلول
- "Step over".
- They refer to the same object (aliases), at that moment.
- No. The program pauses just before line 10 runs.
nums[0]=7,nums[1]=2,other[0]=5. Afterother = new int[]{5};the variableotheris repointed to a new array, so the later writenums[0] = 7goes only to the original array. (This is the same pattern asreplacein lesson 2; I did not run this exact snippet, only reasoned from the verified lesson 2 behaviour.)numsshows[7, 2]andothershows[5]with different labels. They do not share an object.- The method reassigned its own parameter to a new list and added
"lost"there. That reassignment moves only the callee's slot;aandbstill pointed to the original list. - Answers vary. A valid example:
Person p = new Person("x"); p = new Person("y");. Confirm:pnow shows the second person; the first has no variable pointing to it. Cannot show: that the first person has actually been freed, because eligibility is not collection.
Mini Challenge
Do a first pass on lab BEG-038-LAB1. Write four tiny programs: (1) a primitive copy, (2) two aliases of one object, (3) a method that mutates a parameter, (4) an object that loses its last reference. For each one, draw the stack and heap, write the values you predict, and then check them in the debugger. Mark each as "matches" or "corrected", and write one sentence for every correction.
Summary & Next Lesson
Key takeaways
- Draw first, predict values, run in the debugger, compare, correct.
- Primitives show values; object variables show references, and a shared label means a shared object.
- A breakpoint pauses before the line runs.
- Reassigning a parameter inside a method affects only the method's own slot.
- The debugger shows reachable state; it cannot show that garbage collection has happened.
Next lesson
This is the last lesson of the module. The module's lab, Draw it, asks you to do this workflow for four programs.
Self-check
- Why draw before running the program?
- What does a shared label in the variables panel mean?
- What does
nullin a variable's panel entry mean? - When a method reassigns its parameter, what happens to the caller's variable?
- Why can't the debugger prove an object has been garbage collected?
أظهر الإجابات
- Prediction exposes misunderstandings; copying what the debugger shows teaches nothing.
- The two variables refer to the same object.
- The variable refers to no object.
- Nothing: the caller's variable still points to the same object it did before.
- It shows the reachable state at a pause. Collection timing is up to the JVM, and an eligible object may still be present.
References
- The Java Tutorials: Passing Information to a Method: verified that reference arguments are passed by value, so reassigning a parameter does not change the caller's variable, while changes to the object persist.
- java.lang.System (Java SE 8 API): verified that
System.gc()is only a suggestion; this supports the point that a debugger cannot confirm collection. - Debugger menu names, object label format (
@and a number), and any "who refers to this object" feature depend on the IDE and version; I did not check any specific IDE's documentation. The demo program was compiled and run with JDK 21 and its output copied.
التطبيق
طبّق هذا الدرس بمختبر الوحدة وتمارينها واختبارها.
تعليم الدرس كمكتمل اختياري ويمكنك التراجع عنه في أي وقت. يبقى تقدمك محفوظًا في هذا المتصفح.