6 ms·
Probably a book needs to be written as in "Pragmatic Garbage Collection" summarizing some good practices to avoid surprises as the author of the article encount
by _ph_ 11y ago
Probably a book needs to be written as in "Pragmatic Garbage Collection" summarizing some good practices to avoid surprises as the author of the article encountered. Having used Java since its creation and other GCed languages, I would summarize them as follows:
- avoid allocating objects on the heap which you do not have to allocate. The less fresh allocations you have, the less the GC has to do. That does not mean you should write ugly and complex code, but if the tool described in the article was for example grep-like, then one should not have to allocate each line read separately on the heap just to discard it. If possible use a buffer for reading in, if the io libraries allow it.
- generational GCs try to work around this a bit, as the youngest generation is collected very quickly, assuming the majority of the objects is already "dead" when it happens, only the "survivors" are copied to older generations. Make sure that the youngest generation is large enough, that this assumption is true and only objects are promoted to older generations which indeed have a longer lifetime.
- language/library design makes a huge difference how much pressure there is on the GC system. Less heap allocations help, also languages, which try not to create too complex heap layouts. In Java, an array of objects means an array of pointers to objects which could be scattered around the heap, while in Go you can have an array of structs which is one contiguous block of memory which drastically reduces heap complexity (but of course, is more effort to reallocate for growing).
- good library design can bring a lot of efficiency. At some point in time, just opening a file in Java would create several separate objects which referred to each other (a buffered reader which points to the file object...). My impression is, "modern" Java libraries too often create even larger object chains for a single task. This can add to the GC pressure.
Of course, all these practices can be used equally "well" to bring down a program with manual allocation to a crawl. So in summary I am a strong proponend of GC, but one needs to be aware of at least the performance tradeoffs different factorings of one program can bring. Modern GCs are increadible fast, but that is not a magic property.
- Peaker 11y agoGreat comment. I disagree about manual memory management though, you don't have to traverse the heap, unless you use a semiautomatic scheme like reference counts. Complex heap structures often aren't a problem for manual memory management. The scenarios that kill GC aren't ones you typically worry about in manually managed code. GC frees you from some concerns and gives you much safety but makes performance harder and less predictable. For many programs this is a good trade off.
- tmd83 11y agoYup nice advices. The biggest trouble with GC is that there are so few absolute numbers and so the gotchas can be hard to understand for most people. Luckily most aren't usually affected by them. Often time bad allocation practices will survive until the system is pushed hard and by that time it gets harder to change those things. Its much easier if you just have a sense of bad allocation at the start of the design. It gets specially bad with libraries doing heavy allocation and with the level of abstractions & dependencies that we usually have today you can get very bad allocation even if your own code is neat.
- tyingq 11y ago> "avoid allocating objects on the heap" Sometimes I see this as a side effect of all of the available libraries for java. Abstraction gets to the point where it's not easy to predict the behavior of something you're using. Like, you're using Tomcat, with CXF client libs, which...under the covers, uses HttpURLConnection. Your app works great with http, but you need to switch to https. Unknown to you, when you switch to https...your object count doubles. Because the design decision all the way down at the bottom was to spin up a new object (per connection) to handle an SSL handshake.
- pjmlp 11y agoOne problem with Java is, for the time being, the lack of proper value types. Had a language like Eiffel, Oberon dialects or Modula-3 taken its role in the industry, I bet we wouldn't be having this constant discussions of GC vs manual, in terms of performance. Another issue, is that many developers even in languages with GC and value types, tend to be "new() happy" sometimes leading to designs that are very hard to refactor when the the need arises, given the differences in the type system between reference and value types. Eiffel is probably the only one I can remember, where the difference is a simple attribute.
- _ph_ 11y agoOne thing I like about Go is its strong Oberon heritage, picking up where those languages left.
- mhd 11y agoConsidering that Wirth's latest Oberup update took features away, I'm not sure whether he and Pike et al. would really agree about that... But yeah, it's an interesting language in that family, although I'd still much rather have a modern Modula-3 system...
- _ph_ 11y agoAs I only have used Modula-2 and Go, what are the features of Modula-3 missing in Go?
- pjmlp 11y agoQuite a few: - Enumerated types - Enumerations as array indexes - Generics - Classic OO with inheritance - Untraced references in unsafe packages - Unsafe packages for low level systems programming - Exceptions - Reference parameters - Bit packing - Since Modula-3 was a system programming language for SPIN OS, the runtime library was richer, including GUI components There a few other features.
- pjmlp 11y ago
- loevborg 11y agoThanks for writing this, I agree especially with the first point. Scott Oaks's "Java Performance" [1] does a good job of explaining the different GC's available in the JVM. He also goes into the many, many GC-related JVM settings you can tune. However, as he acknowledges, the default settings are often hard to improve upon. The reason why many programs display bad behavior under memory pressure is that they are not written with a clear understanding of Java's memory model. They allocate too many objects, or hold on to objects for too long even if they're no longer needed (e.g. "head retention" in Clojure). As powerful as the JVM is, it can't magically fix your broken programs. Unfortunately many memory pressure problems remain hidden until you encounter production workloads. What I'd like to see most is practical advice on how to avoid these problems in the first place, how to debug them if they occur, and how to effectively test your code for leaks/memory bugs. [1] http://www.amazon.com/Java-Performance-Definitive-Scott-Oaks/dp/1449358454/ref=sr_1_1?ie=UTF8&qid=1461409189&sr=8-1&keywords=java+performance http://www.amazon.com/Java-Performance-Definitive-Scott-Oaks...
- sievebrain 11y agoThis particular article isn't even about a Java problem. The author is just trying to use more memory than is actually available. If the program is written in C++ instead, what'd happen is it'd keep allocating memory beyond the 4 gig limit she imposed on the JVM, until it hit swap and the entire machine bogged down and became slow, or until the kernel OOM killer randomly killed some other (possibly important) program on her desktop to try and make space for it. If she tried to fix that with ulimit, then she'd get different behaviour - the program would die quickly without slowdown, but before actually using 4 gigabytes of heap, due to fragmentation. In the latest Java release there's a flag that makes the JVM exit as soon as there's an OutOfMemoryError (or do a heap dump for diagnostics), and there's also -XX:+UseGCOverheadLimit which makes the JVM give up sooner if it's spending more than 98% of its time garbage collecting (i.e. it's reached the limit of its heap but not quite).
- jstimpfle 11y agoThe thing you missed is that C++ doesn't allocate as many objects. For a start, a std::vector allocates exactly one "object", namely the underlying buffer. All the elements are placed in this contiguous memory. When the buffer is full it is reallocated.
- athenot 11y agoThese are all great points. Unfortunately most of these issues manifest themselves with a delay. Which makes for very fun conversations between the dev team and the ops team. My cynical self views garbage collection as technical debt for memory management. Sure, it's unfair because modern GCs will be way better at managing memory for medium complexity projects than any home-grown solution. But when the project gets complex—as many mature ones do—memory management which was so blissfully delegated to the GC becomes a sore issue. But by that time, it's in the context of a lot of complexity going on so it is not only harder to troubleshoot but harder to remediate.
- Roboprog 11y agoEnter threads. Now you cannot simply have a singleton built at the beginning. Maybe you can pool things in ThreadLocal, or maybe it's more complicated. I'm eventually gonna have to learn me some Erlang, I fear.