preface vii
acknowledgments ix
about this book xi
about the author xiv
about the cover illustration xv
PART 1 THE BASICS OF INVESTIGATING A CODEBASE 1
1 Revealing an app’s obscurities 3
1.1 How to more easily understand your app 4
1.2 Typical scenarios for using investigation techniques 7
Demystifying the unexpected output 8
Learning certain technologies 13
Clarifying slowness 13
Understanding app crashes 15
What you will learn in this book 17
2 Understanding your app’s logic through debugging
techniques 19
2.1 When analyzing code is not enough 21
2.2 Investigating code with a debugger 24
What is the execution stack trace, and how do I use it? 29
Navigating code with the debugger 34
2.3 When using the debugger might not be enough 40
3 Finding problem root causes using advanced debugging
techniques 43
3.1 Minimizing investigation time with conditional
breakpoints 44
3.2 Using breakpoints that don’t pause the execution 48
3.3 Dynamically altering the investigation scenario 51
3.4 Rewinding the investigation case 54
4 Debugging apps remotely 61
4.1 What is remote debugging? 63
4.2 Investigating in remote environments 65
The scenario 66
Finding issues in remote environments 67
5 Making the most of logs: Auditing an app’s behavior 79
5.1 Investigating issues with logs 83
Using logs to identify exceptions 84
Using exception stack traces to identify what calls a method 85
Measuring time spent to execute a given instruction 86
Investigating issues in multithreaded architectures 87
5.2 Implementing logging 89
Persisting logs 89
Defining logging levels and using logging frameworks 90
Problems caused by logging and how to avoid them 97
5.3 Logs vs. remote debugging 101
PART 2 DEEP ANALYSIS OF AN APP’S EXECUTION 105
6 Identifying resource consumption problems using profiling
techniques 107
6.1 Where would a profiler be useful? 108
Identifying abnormal usage of resources 108
Finding out what code executes 109
Identifying slowness in an app’s execution 110
6.2 Using a profiler 110
Installing and configuring VisualVM 110
Observing the CPU and memory usage 113
Identifying memory leaks 123
7 Finding hidden issues using profiling techniques 129
7.1 Sampling to observe executing code 130
7.2 Profiling to learn how many times a method executed 139
7.3 Using a profiler to identify SQL queries an app executes 141
Using a profiler to retrieve SQL queries not generated by a
framework 141
Using the profiler to get the SQL queries generated by a framework 146
Using the profiler to get programmatically generated SQL queries 149
8 Using advanced visualization tools for profiled data 154
8.1 Detecting problems with JDBC connections 155
8.2 Understanding the app’s code design using call graphs 169
8.3 Using flame graphs to spot performance problems 171
8.4 Analyzing queries on NoSQL databases 175
9 Investigating locks in multithreaded architectures 178
9.1 Monitoring threads for locks 179
9.2 Analyzing thread locks 184
9.3 Analyzing waiting threads 193
10 Investigating deadlocks with thread dumps 202
10.1 Getting a thread dump 203
Getting a thread dump using a profiler 205
Generating a thread dump from the command line 207
10.2 Reading thread dumps 210
Reading plain-text thread dumps 210
Using tools to better grasp thread dumps 216
11 Finding memory-related issues in an app’s execution 221
11.1 Sampling and profiling for memory issues 222
11.2 Using heap dumps to find memory leaks 229
Obtaining a heap dump 230
Reading a heap dump 234
Using the OQL console to query a heap dump 239
PART 3 FINDING PROBLEMS IN LARGE SYSTEMS 247
12 Investigating apps’ behaviors in large systems 249
12.1 Investigating communication between services 250
Using HTTP server probes to observe HTTP requests 251
Using HTTP client probes to observe HTTP requests the app sends 254
Investigating low-level events on sockets 255
12.2 The relevance of integrated log monitoring 258
12.3 Using deployment tools in investigations 264
Using fault injection to mimic hard-to-replicate issues 266
Using mirroring to facilitate testing and error detection 267
appendix A Tools you’ll need 271
appendix B Opening a project 272
appendix C Recommended further reading 275
appendix D Understanding Java threads 277
appendix E Memory management in Java apps 293
index 305
Effectively reading and understanding existing code is a developer’s superpower. In this book, you’ll master techniques for code profiling, advanced debugging, and log evaluation to find and fix bugs and performance problems.
In Troubleshooting Java: Read, debug, and optimize JVM applications you will learn how to:
Searching for bugs, detangling messy legacy code, or evaluating your codebase for new features sucks up much of a developer's time. Troubleshooting Java: Read, debug, and optimize JVM applications teaches code investigation techniques that will help you efficiently understand how Java apps work, how to optimize them, and how to fix the bugs that break them. You’ll go from the basics of debugging to advanced methods for locating problems in microservices architectures, and save yourself hours—or even days—of time. Each new technique is explained with lively illustrations and engaging real-world examples.
Fact: Over the course of your career, you’ll spend far more time reading code than you will writing it. The code investigation skills in this book will radically improve your efficiency in understanding and improving Java applications.
Troubleshooting Java: Read, debug, and optimize JVM applications presents practical techniques for exploring and repairing unfamiliar code. In it, you’ll learn timesaving practices for discovering hidden dependencies, discovering the root causes of crashes, and interpreting unexpected results. Go beyond profiling and debugging and start understanding how Java applications really work.
For intermediate Java developers.