Over a million developers have joined DZone.

Getting Real About Memory Leaks

Memory leaks are still a problem with modern-day languages. Just because you have a garbage collector doesn't mean you can't have a leak. We should all strive to make our code plumb perfect.

· DevOps Zone

The DevOps Zone is brought to you in partnership with Sonatype Nexus. The Nexus Suite helps scale your DevOps delivery with continuous component intelligence integrated into development tools, including Eclipse, IntelliJ, Jenkins, Bamboo, SonarQube and more. Schedule a demo today

Modern programming languages tend to separate the programmer from memory management; Java programmers don’t have to deal with pointers; they just declare variables and let the built-in garbage collector do its thing.

These garbage collectors are smart, but not perfect; they typically work by object reference. When all the references to an object go out of memory, that object can go out of memory too. Yet if two objects point to each other, they will always have a reference count, and never go away. That means the code written in Javascript to run in a browser, or Objective-C to run on a hand-held phone, or ASP to run on a server could very well have a memory leak.

When memory leaks happen on modern Operating Systems, the Operating System isn’t going to throw an error. Instead, it will just use more and more memory, eventually going to virtual memory on the disk. When that happens, performance falls apart. People in operations reboot the server and that “seems to fix it”; users close and restart their browser, or reboot their phone.

Consider the Following Example

My first job in software was to create an electronic audit of a telecommunications bill. Some other software, maintained by my coworker Jeff, would read accounts, phones, and calls from a database and then create a text file that could be interpreted by a printer, called a post-script file. If we were smart, we’d have had written the tool in two parts, first to create a bunch of excel-like text files, then a second piece to combine those into bills.

One day, about four months in, the President of our 30-person software company came downstairs to my desk and said “Matt, I want you to take over the telecom bills”, doing maintenance and new feature development.

The program was written in C. It read a list of accounts and looped through every account. Not every active account, every account, because an inactive account could still have an unpaid balance. We calculated the balance by adding up all the debits and credits through all time. If that amount added up to zero, we could skip the account. That was no big deal when we were helping a new, competitive regional carrier get started, with forty or fifty accounts and billing for five or six months.

The competitive carrier, however, was good at, well, competing. By the time I took over they had eighteen hundred accounts, and would be in the tens of thousands (and three states) before I left. Performance would become a real problem, but not for the reasons I expected.

The first two problems that hit us were a pointer out of range, then a memory leak.

Memory Out Of Bounds

The software needed to store a great deal of data in memory for each account (the buildings, phones, calls, costs, and taxes) then spit it out to disk in post-script format. There are two basic ways to do this in C: Either allocate memory with pointers, which is hard to track, or simulate it using arrays. We didn’t have objects, Test Driven Development wasn’t really a thing, and I was inheriting code designed to support a very small customer base. What we did have was arrays. So to support two thousand calls, we could do something like this:

struct call {
  char inOrOutbound;
  datetimestamp timeStart;
  datetimestamp timeEnd;
  integer billedMinutes;
  double cost;
}
char buildings  [200][200];
char phones [200][200][1000][1000];
call calls [200][200][1000][1000];

What’s represented here is up to 200 buildings per account, each of which can have 200 phones, each of which can have up to 1000 calls. The variable calls [5][10][25] would contain the fifth call for the tenth phone at the twenty fifth building. Actually, since C is zero-based, it would be the 6th call at the third phone at the eleventh building.

The software was basically a loop. For each account, for each building, for each phone, for each call. The calls needed to be sorted, then the results printed. Everything worked fine until in one run I got a memory out of bounds error. That’s probably because the code was trying to access building 201, phone 201, or call 1001. The compiler has range checking built-in by default. To get around this, I moved the size of the data structure up as large as possible, which worked until we got an account that was too big for that.

Then I turned off range checking and declared a large amount of empty data structures, matching the originals in type, below the originals. That is an incredibly foolish thing to do, but it worked. Because the data structures were built into the compiled program (the “.exe file”) and would be re-used for every account. Our little program would declare all the memory it needed one time, at the beginning, and use that.

Until we found the system slowing to a crawl on big accounts, like it was choking.

Enter The Memory Leaks

The data structures above are an over-simplification. We also needed to store the addresses of the building, the payments since the last bill, and so on. Like the examples above some of these were declared variables, put in the compiled code, “on the stack”. Others were grabbed at run-time, using a C program call named “malloc” and its friend “free.” Instead of declaring an array, we’d declare a character pointer, then malloc() some space at that pointer. When the function was done, we could free() the disk space.

If we remembered to do that.

As you’ve probably guessed, we had too many calls to malloc() and not enough to free(). That meant that memory use slowly crept up as we used the software. That’s fine for fifty or even five hundred accounts; the memory use would go away when the program finished running. But, we started to get some very big accounts. I noticed that one big account would run, and performance would go down. The kicker, for me, was an out of memory error for our user. I went to Rich, the sysadmin, and asked for more memory for the user. He suggested I tune it instead. After carefully poring over the code, I found some mallocs() that weren’t being freed. Adding the calls to free, the program did eventually finish. That account still took too long, but that’s enough for today.

You might see how I struggled, but you might not see what that has to do with modern software delivery. Allow me to share just a bit more.

The Lesson For Today

These problems are silent killers, and fixing them is no longer as easy as searching through a program making sure every malloc() has a matching free().

If you write embedded medical software, or avionics, or anything in C, you are probably very familiar with these problems. If you don’t, you might not be, but the problems are still there.

If your application degrades over time, the problem might just be memory leaks. The next logical question is how to find them.

The DevOps Zone is brought to you in partnership with Sonatype Nexus. Use the Nexus Suite to automate your software supply chain and ensure you're using the highest quality open source components at every step of the development lifecycle. Get Nexus today

Topics:
memory allocation ,memory leaks ,memory management

Published at DZone with permission of Matt Heusser, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

SEE AN EXAMPLE
Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.
Subscribe

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}