Different Parts of the Java Memory
Do you know the phenomenon of having to restart an application to boost the performance of that application? If so, you may have experienced the outcome of poor memory management: the memory getting full and the application slowing down. This is not always why applications slow down – other causes such as processing data from a server or a bottleneck in the network, among other things, play a role – but memory management problems are a usual suspect of degrading application performance.
You’ve probably heard of memory in the field of computer science before. That makes sense because computers have memory and they use this memory to store and access data while running programs (which in their turn are data too!).
So, when does an application use memory? Well, for example, let’s say you’d like to run an application that is going to process a huge video file. If you do this with your activity monitoring application...
Understanding computer memory and Java memory
First things first – running applications, Java or not, requires computer memory. The application’s memory is the physical memory of the computer. Having more knowledge about the memory of the computer is going to help in our understanding of Java memory. Therefore, let’s discuss the concept of memory and Java memory in a bit more detail.
Computer memory
Chances are that you already know this, but just to reiterate: a computer has memory. This is the part of the computer that is used for storing information that is used for executing processes. We also call this the main memory or sometimes primary storage. An important point to make here is that this is different from computer storage, where long-term information is stored. This storage is long-term because the HDD storage stores the information magnetically and the SDD can be qualified as Electrically Erasable Programmable Read-Only Memory (EEPROM). They don...
Creating variables in Java
Creating variables in Java means that we have to declare a variable. If we also want to use it, we have to initialize it. As you most likely know, declaration is the process of assigning a type and a name. Initializing is about giving the variable an actual value:
int number = 3;
char letter = 'z';
Here, we declare the variable and initialize it on the same line. We declare it with the type and name. The types here are int
and char
and the variable names are number
and letter
. This can also be separated over multiple lines as follows:
double percentage;
percentage = 8.6;
The JVM doesn’t check the types anymore – this is done by the compiler prior to running the application. There is actually a difference between the storage of primitive types and reference types. This is what we’re going to look at now.
Primitives and reference types
The JVM deals with two types of variables: primitives and reference...
Storing variables on the stack
Variables used in a method are stored on the stack. The stack memory is the memory that is used for executing methods. In Figure 1.7, we have shown a stack area for three threads, each containing several frames.
Figure 1.7 – Overview of the frames in the stack area for three threads
Inside a method, primitives and references exist. Every thread in the application has its own stack. The stack consists of frames. Every method that gets invoked comes with a new frame on the stack. When the method execution is finished, the frame is removed.
If the stack memory is too small to store what is needed for the frame, StackOverFlowError
is thrown. When there is not enough space for a new stack for a new thread, OutOfMemoryError
is thrown. The method that currently is being executed by a thread is called the current method and its data is held in the current frame.
Current frame and current method
Creating objects in Java
Objects are a bundle of values. In Java, they can be created by instantiating classes using the new
keyword.
Here is a very basic Person
class:
public class Person {
private String name;
private String hobby;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getHobby() {
return hobby;
}
public void setHobby(String hobby) {
this.hobby = hobby;
}
}
If we want to instantiate it, we’ll use the following:
Person p =...
Storing objects on the heap
Storing objects on the heap is very different from storing values on the stack. As we’ve just seen, references to places on the heap are stored on the stack. These references are memory addresses and these memory addresses translate to a certain place on the heap where the object is being stored. Without this object reference, we would have no way to access an object on the heap.
Object references have a certain type. There are very many built-in types in Java that we can use, such as ArrayList
, String
, all the wrapper classes, and more, but we can also create our own objects and these objects will be stored on the heap too.
The heap memory holds all the objects that exist in the application. Objects on the heap can be accessed from everywhere in the application using the address of the object, the object reference. The objects contain the same things as the blocks on the stack: the primitive values directly and the addresses for other objects...
Exploring the Metaspace
The Metaspace is the memory space that holds the class metadata
that is necessary for runtime. It is the method area in the JVM specification and in most popular Java implementations after Java SE 7, this area is called the Metaspace.
If you know about PermGen, or you come across it, just know that this is the old memory area where all class metadata
was stored. It had some limitations and has been replaced by the Metaspace.
So, back to this class metadata
. What even is that? Class metadata
is the runtime representation of the Java classes that are necessary to run the program. It actually contains a lot of things, such as the following:
- The Klass structure (we’ll see more in Chapter 5 when we take a deep dive into the Metaspace!)
- Bytecode of methods
- The constant pool
- Annotations and more
That’s it! These are the basics of Java memory management. There is a lot more to say about the specific parts. We are going...
Summary
In this chapter, we have gone through an overview of Java memory. We started with computer memory and learned that the computer has main memory and secondary storage. The main memory is most important for us since this is what is used to run programs, including Java programs.
The main memory consists of RAM and ROM. Java applications use RAM to run. Java applications are executed by the JVM. This JVM executes Java applications and in order to do so, it has three components: a class loader, runtime data areas, and an execution engine.
We focused on the different components of the runtime data area: the heap, stack, method area, PC register, and native method stack.
The stack is the memory area that is used to store variables and values of methods in frames. The heap is used for storing objects. The stack holds references to objects on the heap. The heap is accessible from everywhere within the application and whoever has an object’s address on the heap can access...