14 ms·
My favorite bug: segfaults in Java
- gizmo686 12y agoI remember this bug. Our workaround was to parse the output of toString() for 0x0000000, and simply drop the frame when it happens.
- threeseed 12y agoI'm curious. Was it something that existing across many JVM updates ? Given the stability of the language if I have ever seen an issue (rare) there's always been a choice of 10-20 JVM versions to pick from.
- gizmo686 12y agoThe competition gives us six weeks to build the robot, before we have one or two three day competitions. Apart from possibly attending non-official off-season competitions, that is the only time we would use the code before the next years competition, which is completely different, and for which we are required to right entirely new code[1]. Once we got a hack working, we ran with it and rarely tried to clean up. In this case, we didn't bother trying a different JVM. [1] Unless the code is open source (as ours was), in which case we were explicitly allowed to use it.
- maaaats 12y agoIt's not really a JVM bug, but an artifact of dealing with native code through Java without thinking about how the memory of the native.
- lamuerteflaca 12y agoThat is a huge bug. I hope it's been fixed.
- threeseed 12y agoIt was from 2 years ago i.e. early Java 7 days. There have been 116 updates since then (33 public). Given that segfaults almost never happen in the JVM (I've never seen one) I can imagine it would be the highest priority to be fixed.
- xxxyy 12y agoThis is not a Java bug.
- mlvljr 12y agoHow dare you.
- saltysugar 12y ago> Given that segfaults almost never happen in the JVM (I've never seen one) I've seen 5 segfaults over the last week in our system. Unfortunately we don't enable core dump in our system (yet) so we haven't really had a good sense of what might cause the system. We've been seeing about 30 SIGSEV's over the last three months, but we haven't been able to root cause it yet.
- Gravityloss 12y agoHow many jvm:s are you running and what version?
- kppullin 12y agoI've faced similar issues in the .NET world. There is a built in method, `GC.KeepAlive()`, that does nothing other than keep the reference alive to the eyes of the garbage collector. Raymond Chen explains it well here: http://blogs.msdn.com/b/oldnewthing/archive/2010/08/13/10049634.aspx http://blogs.msdn.com/b/oldnewthing/archive/2010/08/13/10049...
- lamuerteflaca 12y agoIt completely defeats the whole point of a garbage collector if you have to resort to hacks like this. I understand why it is done. A lot easier to use the work around then to fix the root problem. Unfortunatelly these types of bugs have a nasty way of showing up in other places where it can be even harder to debug. Long term these bugs need to be fixed otherwise your code will just be a ticking bomb. The worst part is when they combine with other bugs and they become even harder to debug.
- dietrichepp 12y agoI wouldn't say "completely defeats". The hacks are usually there only when you need to manage the lifetime of something that's not managed by the garbage collector. I've seen it used for mutexes, for example, to keep a mutex alive and locked. You could say that the problem is that people abuse the garbage collector to manage something it shouldn't manage, or you could say that the problem is that garbage collectors are bad at managing anything that isn't memory. (I would lean towards the latter.)
- nraynaud 12y agonope, when you have handles on resources outside managed memory, the GC is out and you need to have an acquire/release mechanism. This resource will probably have a bit of state visible on the managed side, so there will be some pinned memory.
- greenleafjacob 12y agoTeam 1024! Always an easy name to remember. Hello from team 166!
- TheLoneWolfling 12y agoSounds like a particularly nasty bug. Especially as there's nothing preventing the "fix" from being optimized out by the JVM, which would reintroduce the problem all over again.
- hawkice 12y agoThe issue is an unsafe optimization. If you change optimization to re-introduce the bug, that's a pretty straight-forward regression.
- TheLoneWolfling 12y agoI meant the author's workaround of adding a call to `this.getSize()`, not the JVM fix.
- hawkice 12y agoAh, yes, the instability of fake fixes to compiler bugs. I recall a friend of mine once had a bit of C code that would compile and run correctly only if debugging was enabled. His fix, to get it to run with full optimizations without segfauting was what he referred to as "the God code": int god = 1; To this day no one knows why that did anything at all, but at some point the compiler bug was fixed without anyone filing this bug that we know of.
- TheLoneWolfling 12y agoMay not even be a compiler bug in that case. That's a classic symptom (and "fix") for an array that was {over}/{under}run by one (or some small number of) element(s): the extra allocated space on the stack may have prevented it walking into unallocated memory (and segfaulting). Now, if he'd run it under valgrind and it showed no problems of that sort, and it still segfaulted...
- hawkice 12y agoMy understanding is that it was reversed from that, where with stack canaries and debug tracing nothing odd was happening, but when things are run without defenses it _stops_ working, instead of starting to work because you're paying less attention. That was why it was so odd -- obviously the array overrunning (or passing (void*) &local_var into subroutines where it's cast to a different type, etc) would be a pretty obvious candidate.
- btown 12y agoFrom the article: public type1 getFrame() { type2 child = this.getChild(); type3 var = this.something(); // `this` may now be garbage collected return child.somethingElse(var); // segfault comes here } > Where the destructor method of this calls a method that will free() native memory that is also accessed by child; if this is garbage collected before child.somethingElse() runs, the backing native code will try to access memory that has been free()ed, and receive a segmentation fault. If child needs some block of shared memory contained in the parent (this)... shouldn't there be a shared object referenced by both parent and child that owns the block of shared memory? Then as long as child isn't GC'd, even if the parent/this is GC'd, the shared memory-owner won't be GC'd, and the memory will be safe. For the C++ equivalent of this pattern, you'd want to scope the shared memory to a reference-counted RAII resource (say, a shared_ptr), and make sure that any actor that interacts with it holds a reference for the actor's lifetime. It's unreasonable for Java to "guess" that you meant anything different from this pattern - what if you actually wanted the parent object to be eligible for GC (say, if it's hogging other resources the child doesn't need) between this.something() and child.somethingElse()? So I'd consider this a WontFix if I were a JVM developer, and maybe write a blog post about it and put it on HN if I had the time. Something something feature not a bug, etc. EDIT: To be clear, the child is trying to have low-level access to a resource it has no ownership stake in, implicitly or explicitly. So there's no reason for that resource to stick around for the child. It's like a spouse taking the house in a divorce - the "prenup" in Java is that only reference-holders have any expectation for an object to be accessible to them. If the "child" didn't want to find itself segfaulted on the streets, it should have thought about writing that "house" (the shared memory) into its prenup, hmm? EDIT 2: The analogy kind of fell apart, I think. But my point still stands. Apologies to any divorced HN'ers I may have offended.
- jryan49 12y agoIf child needs this then why is it being free()d in the first place? I don't get what's so strange about the behavior here.
- kdrakon 12y agoI only have an amateur interest in JVM development, but doesn't this bug break some rules around GC 'object roots'? Even if 'child' and 'something' are referentially copied to local variables on the calling stack, I believe 'this' should continue to act as a root for both of them. I think.
- adevine 12y agoNot necessarily. Those "getChild()" and "something()" methods could, for example, return new objects. this is only the root of child and something if this retains references (i.e. member variables) to these objects, which I'm guessing was not the case in this example.
- kdrakon 12y agoAh, yes, didn't consider that. But what about 'this'?; for what reason could the JVM choose to GC an object that the calling thread is currently running in? The example method exists in an instance of 'this' and isn't static.
- Dylan16807 12y agoExecution in Java does not have a concept of "current object". 'this' is passed in a magic way, but it does not exist in a magic way. It's like any other variable, and can be collected as soon as it's not needed. Which can be extremely early, because optimizations can reduce the 'need' for a variable to only its side effects. Go pick apart aggressively optimized and inlined code and I bet you can find situations where 'this' never even exists.
- dietrichepp 12y ago"this" can be collected in the body of a method, if the last reference dies before the method returns. It is treated no differently from the explicit method parameters.
- amit-bansil 12y agoI've done a fair bit of work interfacing memory managed languages like Java & Python with "unsafe" native code and found that this problem actually crops up often. The first issue is that finalizers should NEVER be relied upon to clean up system resources the way that you might in a language with deterministic garbage collection like C++. Instead, expect clients of interfaces that use system resources to manually call some 'close' like method. Use finalizers to warn clients that they are leaking. Josh Bloch has an excellent writeup on this[†]: http://www.informit.com/articles/article.aspx?p=1216151&seqNum=7 http://www.informit.com/articles/article.aspx?p=1216151&seqN... The second thing, as I believe @btown is saying, is that the child object seems to depend on memory that it doesn't own a strong reference to. I bet the whole memory graph here is just a tangled mess. I used to think that that was just the nature of graphs. When I first started working with GC that couldn't handle these kind of cyclical structures without being explicitly told I thought that it was an absurd premature optimization. Then, this wise old hacker peered at my code, smacked me on the back of the head, and grumbled that if I couldn't clearly explain the reference hierarchy it wasn't just the software that would be befuddled, it was too complicated for his old brain as well. I'm sure that this can't be true, but I am starting to suspect that all the tremendous work that has gone into fast pause-less concurrent GC has been for naught. I wish that when GC encountered a cycle it would just tell the programmer that they now have a leak, maybe even suggest where it could be fixed with one of those neat little IDE quick fixes, and continue chugging along. tl;dr When you have a hard problem let someone else deal with it. [†] Granted, Bloch does make an exception for 'native peers'. My understanding of that pattern, however, is that the key is not letting some other object walk off with a reference to your peer, which is exactly what has gone wrong here. (Aside: Josh Bloch's Effective Java & Java Puzzlers are fantastic. They are worth reading for programmers who use any language.)
- btown 12y ago> the child object seems to depend on memory that it doesn't own a strong reference to Exactly. Having made similar types of robotics interfaces myself, the memory graph for OP probably isn't as complicated/cyclic as you might think: multiple types of frames (at various stages of visualization processing) look into each others' memory buffers for efficiency. It's probably best modeled as an acyclic directed graph where frame objects reference (many-to-many) memory buffers. The problem is that unless you abstract out those buffers into garbage-collectible objects themselves, it's very tempting just to pass a weak reference (like a pointer from JNI) to the buffer owned by an EarlyStageFrame to the processing code in a LaterStageFrame. Thus the segfault when the EarlyStageFrame is garbage-collected.
- pdeva1 12y agothis is not a bug. author doesn't seem to understand how jvm or managed memory works. he uses memory from parent object in child code without holding a reference to it in child object. If his code was in pure java this simply won't be possible. The only reason he is able to do this is via native code. Even in native code, it is wrong to have a pointer to managed memory without using the Global/Local Ref() methods that the jvm provides. In summary, the jvm is behaving correctly with respect to GC. Author doesn't know how to use JNI and has caused the crash.
- xxxyy 12y agoThe author has not written the bad code. From what I understand he was just debugging it. >The dashboard was written in Java, and the source was available (under a 3-clause BSD license), so I dove in, (...) Also, nowhere in the post he says that it is JVM's fault.
- im2w1l 12y ago>The issue was that Java was making an unsafe optimization (I never bothered to figure out if it is the compiler or the JVM making the mistake, I was satisfied once I had a work-around). He says it was the JVM or the compiler's fault. He doesn't blame the code.
- coryfklein 12y agoIMO, whether he though this explicitly or not, he was really just disagreeing with the Java spec. Of course, effectively any bug you encounter could be dealt with by blaming the spec writers, but in this case I may agree with him. Depends on how much this particular optimization saves.
- DerekL 12y agoThis isn't a bug in the compiler or the JVM. The Java spec explicitly allows this optimization to happen. From The Java Language Specification (SE 7), section 12.6.1: > Optimizing transformations of a program can be designed that reduce the number of objects that are reachable to be less than those which would naively be considered reachable. For example, a Java compiler or code generator may choose to set a variable or parameter that will no longer be used to null to cause the storage for such an object to be potentially reclaimable sooner. Also, the "this" keyword is treated like a parameter. (See the end of section 2.6.1 of the JVM spec.)
- divs1210 12y agoso then why do you think the segfault is occurring?
- barrkel 12y agoFully agree. This is a well known issue that users of native interop from managed languages commonly encounter when they first start out. One of the first bugs I found in .NET related to this; the Ping component didn't expect that Finalize could be called on the same instance before a method on that instance finished executing. What is less convenient is that Java doesn't have a handy method to ensure that an object is reachable, that the JVM won't optimize away or eliminate. .NET has GC.KeepAlive; AFAIK, Java doesn't have an equivalent. I asked a question about this on SO[1] some time ago. An approximate equivalent can be cobbled together, with probabilistic assignments to a volatile variable. [1] http://stackoverflow.com/questions/22457002/whats-the-java-equivalent-of-nets-gc-keepalive http://stackoverflow.com/questions/22457002/whats-the-java-e...
- deepsun 12y agoDo not implement finalize() with side effects. Better to not implement it at all, as recommended everywhere.
- maweki 12y agoI am very disappointed that this.getSize(); at the end fixes the issue. Since it has no side effects and the value is never used anywhere, I would expect the compiler to remove that call.
- tormeh 12y agoThe Java compiler does almost no optimization. I think it's an idealistic thing: All optimization is supposed to be done by the JVM. Personally I think that sounds stupid.
- xxxyy 12y agoThis is a very sound approach. The main benefit is that all JVM languages can utilize optimizations if the optimizations are in the JVM, thus logic duplication is reduced. Also, please note that both Clang and GCC use the same approach, except in their cases the programmer typically does not see the intermediary code (which is LLVM for Clang, and a family of intermediate languages for GCC). http://en.wikipedia.org/wiki/Intermediate_language http://en.wikipedia.org/wiki/Intermediate_language
- Terr_ 12y agoThe compiler doesn't (shouldn't) know what the runtime system will look like, so it cannot make optimizations as safely or effectively as the JVM can.
- TheLoneWolfling 12y agoExcept that, as evidenced by this case, the JVM isn't doing the optimization either!
- TheLoneWolfling 12y agoExcept that, as evidenced by this case, the JVM isn't doing the optimization either! (Also: it's not a stupid approach. Due to the way the JVM loads things, most of the time you don't know that any particular method won't be overridden later by something that does, in fact, have side effects. There are very few optimizations that could actually be done by the Java compiler.)
- Hermel 12y agoOne more reason not to use "finalize". Did you know that there is no guarantee that it every gets called? That's because there is no guarantee for objects to be garbage collected as long as there is enough memory.
- sorokod 12y agoI think that this.getSize(); // bogus call to keep `this` around could be optimized away since the value is never used.