The big jump for most people moving from Java to lower-level languages is the lack of garbage collection. Objective-C 2 on the Mac (but not the iPhone) has a form of garbage collection, but it is less useful than Java's.
Java has references, rather than pointers. These are very similar, but with one important difference. You cannot perform arithmetic on references or construct them from arbitrary integers. That means that the VM can keep track of every single object reference easily. Objective-C inherits the C memory model, which means that you can hide pointers in a variety of ways.
The main implication of this is that Objective-C with garbage collection doesn't benefit from fast allocation in the same way that Java does. With Java, allocating memory for an object just requires implementing a pointer. After a while, the VM will go through the recently-allocated objects, move the ones that still exist to a different place in memory, update all references to them, and continue. Because the Objective-C garbage collector can't track all pointers, it can't move objects, so you get the same kind of memory fragmentation and performance that you'd get with malloc() in C. Collecting garbage also requires quite a lot of effort.
If you're not using automatic GC, then you need to use reference counting. Whenever you want to assign an object pointer to a variable, you send the object a -retain message, then send the old value a -release message, like this:
id tmp = [new retain]; [old release]; old = tmp;
Typically, you'd hide this in a macro. Note that even this isn't perfect. Apple's documentation recommends using -autorelease instead of -release. This uses an autorelease pool, which is an object designed to solve one of the big problems with reference counting.
When you send an object a -release message, its reference count is decremented. If there are no references to it left, then it may be deallocated. So what happens when you want to return a temporary object from a function or method? You aren't keeping a reference to it, but the caller might not be, either. The solution is to send it an -autorelease message before you return it. This adds it to the current autorelease pool, and when that pool is destroyed, then the object will be sent a release message.
This leaves one problem: retain cycles. With pure reference counting, two objects that reference each other are never freed. The only solution to this is to not retain the object in one direction. For example, most objects don't retain their delegates (while their delegates should retain them). This is far from ideal, but it usually works.
Memory management is another part of Objective-C's no magic philosophy. Objects are usually created by sending two messages: +alloc and -init. The first is a class message (the + and - prefixes are used to distinguish class methods from instance methods). This returns an uninitialized instance of the object. The -init method then performs the initialization. When you create an Objective-C class, you typically override -init, but not +alloc.
There is no equivalent of Java's new keyword in Objective-C. You can send a +new message to classes, but that's just a method (which calls +alloc then -init), not a part of the language.
You can override the +alloc method and implement some custom memory allocation (for example, to use memory pools instead of the standard allocation), but it's quite rare. When an object is destroyed, it is sent either a -finalize or -dealloc method, depending on whether the garbage collector is destroying it. NSObject's -dealloc method is responsible for actually freeing the memory used by the object.