Every program, or every task in a multi-tasking system, has a chunk of memory called a stack. The size of the stack is typically fixed when the program is loaded or the task is created. Some operating systems might have a feature whereby a stack is allowed to grow, but in most cases this can't happen. When the stack is used up and the program or task tries to use more, then an exception, or corruption of the adjacent memory (and soon thereafter a crash) results. A stack is a place for for storing variables which are only needed by a method (or a function or procedure in other languages) while the method is running. When the method is called, the parameters passed by the caller, if any, are placed on the stack. Any local variables inside the method are allocated space on the stack as well. The stack is a chunk of memory which is filled from the top down. Each program or task has a stack pointer, which points to the end of the used portion of the stack. In order to add something to the stack, the pointer is moved down by the number of bytes corresponding to the size of what is to be put onto the stack. When the method returns, the stack pointer is moved back to where it was before the method was called. Incidentally, the previous position of the stack pointer is stored on the stack itself in a so-called stack frame, some basic information which goes onto the stack right at the start of the method call, before the parameters and variables.
There are some special instructions in most CPUs which combine moving the stack pointer and putting the contents of a register onto the stack. During a task switch, for example, many or all of the register contents are "pushed" onto the stack. When the task regains control, they are "popped" back off in reverse order. This is a so-called LIFO - last in, first out - type of buffer. Since each task has its own stack, the last part of a task switch is to change the stack pointer and the program counter (pointer to the task's code) to the stack and code of the task gaining control. So in addition to storing temporary variables, a stack is also used to store/restore a task's context. The heap is the left over memory after the program code has been loaded into memory and the stack(s) have been allocated. There is typically a lot more of it than would be used for a single stack. If the program needs some memory for something, it can request from the operating system a chunk of memory which is taken from the heap. The operating system has to keep track of which part of the heap has been given out and which part is still free. When the program doesn't need this memory any more, in some operating systems it must explicitly give it back. If the programmer forgets to do this, he/she has what is known as a "memory leak". Some function which is called intermittently allocates memory from the heap, storing a pointer to this memory on the stack. When the function completes, the pointer on the stack is discarded, and there is now no more reference to the location of the allocated memory. So every time the function is called, a little bit more of the heap is permanently taken away and wasted. When the heap runs out, the function can no longer do what it is supposed to do and somehow fails on the next call. The program may handle this gracefully or may simply crash. This is one of the major pitfalls in C programming. Whole industries have sprung up around making tools and test systems to find the source of memory leaks. Java tries to prevent this by NOT requiring or even allowing the programmer to explicity free up memory allocated from the heap. A garbage collector does this automatically. This is a practical requirement for any object-oriented language. While references (pointers in C) to objects may reside on the stack, when the actual objects are instantiated the memory is typically taken from the heap. It would be extremely tedious to have to manually allocate the memory for each object one wished to instantiate. In Java you don't manually allocate the memory from heap with a call to the operating system such as the C function malloc(). When you instantiate something with "new", this happens automatically. Note that you still aren't free of the problem of the heap running out just because it is managed automatically. If you use "new" to create enough objects, you can use up all the available heap.