انتقل إلى المحتوى
أمين أبوزيد
منشورBEG-038-L05

عرض عملي: رسم stack وheap ثم التحقق في المصحّح (debugger)

  • الدرس 5 من 5
  • 10 دقيقة قراءة
  • مبتدئ
في هذا الدرس 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 null and 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:

  1. Read the program once without running it.
  2. Draw frames and heap at the point you care about (a chosen line).
  3. Predict the values the program will print and the variables the debugger will show.
  4. Run it with a breakpoint on that line.
  5. 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

java
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:

text
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:

text
[a, b, c] [a, b, c] true
2 3 [a, b, c] null

This matches the prediction:

  • a and b print the same contents, and a == b is true: one object.
  • "lost" never appears in either list: it went into the new list that only addAgain's parameter pointed to, and that list became unreachable when the method returned.
  • After b = null;, a still holds the list; b shows null.

Now set a breakpoint on l.add("lost");, debug, and check the screen against the drawing. What you expect to see (labels will differ):

FrameVariableExpected in the variables panel
addAgainla new empty list with its own label
mainalist with a, b, c, label X
mainblist with a, b, c, same label X
maincount, copy2 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

  1. Which of these is a debugger action: "step over", "compile", or "refactor"?
  2. In a variables panel, two list variables show the same label. What does that tell you?
  3. A breakpoint is set on line 10. Has line 10 run when the program pauses?

Level B: Application

  1. Draw and predict, then check by running. Write down your predicted values for nums[0], nums[1], and other[0]:
java
int[] nums = {1, 2};
int[] other = nums;
other = new int[]{5};
nums[0] = 7;
  1. Describe what you would see in the variables panel for nums and other after the last line, and whether they share a label.
  2. In your own words, state why "lost" in the demo never appears in a.

Level C: Challenge

  1. 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.
أظهر الحلول
  1. "Step over".
  2. They refer to the same object (aliases), at that moment.
  3. No. The program pauses just before line 10 runs.
  4. nums[0] = 7, nums[1] = 2, other[0] = 5. After other = new int[]{5}; the variable other is repointed to a new array, so the later write nums[0] = 7 goes only to the original array. (This is the same pattern as replace in lesson 2; I did not run this exact snippet, only reasoned from the verified lesson 2 behaviour.)
  5. nums shows [7, 2] and other shows [5] with different labels. They do not share an object.
  6. The method reassigned its own parameter to a new list and added "lost" there. That reassignment moves only the callee's slot; a and b still pointed to the original list.
  7. Answers vary. A valid example: Person p = new Person("x"); p = new Person("y");. Confirm: p now 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

  1. Why draw before running the program?
  2. What does a shared label in the variables panel mean?
  3. What does null in a variable's panel entry mean?
  4. When a method reassigns its parameter, what happens to the caller's variable?
  5. Why can't the debugger prove an object has been garbage collected?
أظهر الإجابات
  1. Prediction exposes misunderstandings; copying what the debugger shows teaches nothing.
  2. The two variables refer to the same object.
  3. The variable refers to no object.
  4. Nothing: the caller's variable still points to the same object it did before.
  5. 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.

التطبيق

طبّق هذا الدرس بمختبر الوحدة وتمارينها واختبارها.

المختبر والتمارين الاختبار

تعليم الدرس كمكتمل اختياري ويمكنك التراجع عنه في أي وقت. يبقى تقدمك محفوظًا في هذا المتصفح.

اكتب للبحث في المستويات والمراحل والوحدات والدروس.