Why I hate Java
warp.povusers.org
warp.povusers.org
Thing is, this doesn't usually matter. I have never gotten an out of memory error from a leak in Java. Now compare that to all the development time I've saved by not having to deal with pointer arithmetic. I consider it a huge win. It's all about the type of apps you're making.
Besides, there's too many variables in your anecdote. Is it a laptop from 1995? Is the OOM from a bug that could/should be fixed?
I've heard a story that Minecraft's RAM consumption got a lot worse after Notch stepped down. The new developers refactored the code for OOP best practices (such as passing a 3D coordinate as an object rather than "int x, int y, int z"), which tremendously increased the number of allocations and thus GC pressure and memory usage. So it's fair IMO to blame this to a language. Having good practices lead to such consequences is terrible design.
I don't do anything special to play; my sessions used to run into multiple hours, during my peak years of play.
I suspect that it's not fair to blame Java for whatever problem you were having, though.
The first generational GC was proposed for Lisp in 1983. See Liebermann&Hewitt. The heap is divided into generations based on the lifetime of objects. Typically only the youngest generation, which is kept small, is scanned. This is based on the observation that a lot of objects are only short-lived. Thus a GC does typically does only need to look at a fraction of the memory.
Also GCs might want to use regions of similar objects. That way only those regions need to be looked at, which may free up space for an ohject that wants to be allocated currently.
The JVM, however, is a national treasure.
I'd point out that afaik rustc never got a proper good GC implementation (which probably weighted heavily in the decision to remove it before 1.0), so @T/Gc<T> were mostly plain old refcounted pointers with some smoke and mirrors.
See http://words.steveklabnik.com/pointers-in-rust-a-guide for example (section "Managed pointers")
This is the TXR Lisp interactive listener of TXR 181.
Quit with :quit or Ctrl-D on empty line. Ctrl-X ? for cheatsheet.
1> (defstruct raii-class nil
(:init (me) (put-line "raii-class hello"))
(:fini (me) (put-line "raii-class goodbye")))
#<struct-type raii-class>
2> (with-objects ((o (new raii-class)))
(put-line "inside block"))
raii-class hello
inside block
raii-class goodbye
t
Constructor abort via non-local transfer: 3> (defstruct ctor-throws nil
(:init (me) (error "refuse to init"))
(:fini (me) (put-line "ctor-throws goodbye")))
#<struct-type ctor-throws>
4> (new ctor-throws)
ctor-throws goodbye
** refuse to init
** during evaluation at expr-3:2 of form (error "refuse to init")
:fini called by GC: 5> (progn (new raii-class) nil)
raii-class hello
nil
6> (sys:gc)
raii-class goodbye
t
with-objects is not just with structs but for anything with a GC finalizer, like the (1 2 3) list in this example: 6> (with-objects ((o (new raii-class))
(p [finalize (list 1 2 3) prinl]))
(put-line "inside block"))
raii-class hello
inside block
(1 2 3)
raii-class goodbye
t
with-objects can't be used for defining function arguments, but it can refer to variables in scope; resource not defined in the block can be finalized: 11> (let ((x (new raii-class)))
(with-objects ((x x))
(put-line "inside-block")))
raii-class hello
inside-block
raii-class goodbye
t
Parameter passing has reference semantics so the smart-pointer style RAII use of C++ across function interfaces is not really applicable.We don't want to copy a struct argument when a function is called and be incrementing refcounts on things the struct's slots point to; that's just stupid.
The rant about OOP and responsibilities is completely way off mark.
It is not a firm rule of OOP that a module has to clean up all the resources it allocates and never pass any of them off to be cleaned up elsewhere.
This is violated all over the place with layering. For instance, instead of allocating a buffer here and then having someone free it, we can create a Message class which has a buffer. We now send a Message from one module to another. Oh, the buffer is now a single responsibility: it is allocated by the Message module and freed by the Message module. But all we have done is wrap the resource in a trivial class; the design hasn't really changed. We can have a semantic leak if we start creating Messages and stuffing them into some queue which nobody dequeues, just like with the original buffers.
Layering of ownership creates the appearance that one module is managing a resource. That module's instances, though, can be created by one higher layer module and released by another.
https://docs.oracle.com/javase/tutorial/essential/exceptions...
...basically it's the same as .NET dispose pattern for achieving deterministic cleanup. Most IDEs and code analysis tools will throw a fit if you don't use an AutoCloseable/IDisposable class properly in a try-with-resources/using statement.
I much prefer destructors like in C++ (or VB or even PHP) that run when the reference count goes to 0.
That said, we need IDEs that sound the klaxons if a Closeable is not placed in a using statement.
You can deal with multiple Closeables by enhancing syntax.
But tying deallocation to disposal creates action at a distance that I'm really not a fan of, personally.
Resource allocation and deallocation can take time, can block, and can fail. Those semantics make them a terrible fit for constructors and destructors.
- Making it explict has the two distinct problems that a) the code for it must be written each time (I regularly encounter code that ignores Disposables) and b) is closed to change (adding a dispose semantics to a class essentially creates bugs throughout your codebase). Those two, I deem far worse than a destructor that is blocking.
- On the C++ side: If you encapsulate the resource properly, you're not really binding the two together (at least on one level of abstraction), since you're basically just managing a handle. Note that it is also really easy to split both up (by introducing some NULL-handle) and only dispose the resource in the destructor if it hasn't been disposed yet (of course, that is more bug-prone, but so is IDisposable).
- With move semantics (or std::swap before C++11) this also allows you to dispatch "cleanups" to other contexts, if necessary, which gives you exactly the same possibilities where necessary (of course, this also being explicit in those cases).
- Failed resource allocations in constructors should not be a problem (either use exceptions or NULL-handles).
- Failed resource deallocations are a problem in any implementation I know of and likely not "conquered" easily in the near future.
So, to sum it up, I think C++ gives you the better tooling because you can design the appropriate API yourself. C#'s implementation is very set in it's way and has a couple of problems that would be nice to avoid (e.g. the "you can't free resources in a finalizer" thing).
I'd prefer an API that does the right thing by default (albeit slow) and lets me optimize where needed instead of one that has little benefit (for most scenarios) but makes the code more error-prone. That means essentially: If you implement IDisposable in C++ and dispose in the destructor where necessary, you get the best of both approaches.
The remaining arguments that then can be made against C++ are either bad code, abstraction problems and/or cumbersome libraries.
https://web.archive.org/web/*/http://warp.povusers.org/grrr/...
"Now, in Java, if you use a data container provided by the language, you are forced to upcast all the time."
Which is solved by generics.
So, the syntax is similar, but the actual behavior is not because int's can't be null, but Integers can for example. This costs 8 ish bytes of memory.
I was in college around that time, using Java as a language for coursework. The professors jumped on generics right away, so between two quarters, the style that we wrote our Java in shifted drastically. I was nice to rip the band-aid off that fast.
Archive.org's first snapshot of this page is on 2006-02-08.
The HTTP response headers contain the entry: "Last-Modified: Sun, 14 Oct 2012 08:58:12 GMT"
https://web.archive.org/web/20060208015310/http://warp.povus...
Java as a language has some rough-edges: multiple inheritance and verbosity. Personally, I'd use Kotlin if there were a requirement to run on the JVM.
In terms of the JVM:
Erlang VM is under-appreciated: each "process" (not an OS process and lighter than an OS thread) has its own heap so there's no global GC pauses, share-nothing and let-it crash doesn't take out Erlang VM.
For anyone stuck on JVM, look at Azul's free Zing for shorter/less GC pauses and a generally faster JVM.