for the non-erlangers:
the 'simple' way (ie i have a tiny change i want to make, so i'll just ssh in and do it) is to just edit your code, then attach to an existing node, and do make:all([load]) to pick up the code changes. we added a makefile "make attach" that does this for us.
the 'reltool' way is you package your application into releases. We use jenkins build numbers as the third digit in our releases, so each one is packaged into a zip, encrypted, and then is ready to deploy. at deploy time, the zip is copied to all machines, then we use some escript to attach to the running nodes, and use the 'release_handler' bring up the N+1 version of our app. it's a pain but once it's configured it's amazing...
I still have to find anybody shipping whole updatable releases with erlang - couchdb, rabbitmq, any big open source project, nobody is doing it. I think it's a bit overrated, because realistically nobody will want to do this in production, unless it is audited and tested in every possible way. If you have to update external resources with the code_change callback it can fail miserably. I have no problem using it in production for small changes that I fully understand, but big release - wouldn't do it. Too many things can go wrong at the same time to react. You have to audit and test the upgrade and the rollback, which means extra code in code_change to handle the rollback too. Quite heavy.
The gen_server itself passes its module name to sys:handle_system_msg[2] when it receives a system message like a code-change notification, which ends up being the Module:Function type of call that always refers to the new version of the module, so it always ends up running the latest version when you upgrade gen_server.
[1] http://www.erlang.org/doc/man/gen_server.html#Module:init-1
[2]file:///C:/Program%20Files/erl5.9.1/lib/stdlib-1.18.1/doc/html/sys.html#handle_system_msg-6
Not quite as advanced as erlang, but in PHP's target use cases, it is quite effective.
The article does at least demonstrate that processes can transition between two versions of the same code w/o resetting state, which is, at its core, the very thing that makes code upgrades remotely practical.
Other comments mention some more sophisticated machinery like release upgrades. Erlang also has many code upgrade options baked into the OTP as well. I make use of many of these features both during development but also in production with some careful review. I'm always disappointed when going back to a system that has to "reboot" itself after getting used to hot upgrades and distributed erlang (version discrepancies in a cluster can present a similar problem if you don't want to pause your system).
In a prod env, how do you make sure that different versions of your code coexist peacefully?
Easy hot code reloading is one of the great benefits from CSP.
In terms of correctness, I'm not aware of any work that does type checking or serious analysis across version upgrades. Even Erlang's dialyzer will only check each version in isolation. So in practice this means you either test the upgrades with things like continuous integration or even manually, OR do so in very careful and small steps that are easy to reason about locally (very useful for critical applications that can't spare downtime).
Fast forward almost 30 years: in Erlang light-weight processes are (most of the time) tail recursive functions handling messages... endless loops. Those light-weight processes are running those endless loops.
The described module upgrade functionality allows for uninterrupted system upgrades of servers/services in the back-end which btw. is often serious business in production and the cause of the reltool complexities.
BUT: the same hot code reloading system allows for very sleek development experience. You start your first version of the service and from there on update the source and the service changes its behavior most of the time with no restart, no lost state etc. (with the help of some monitoring tools source changes can be picked up automagically...)
This kind of dev-environment is simply flow-inducing.