[JAVA] Be aware of garbage collection and avoid memory leaks

To avoid memory leaks, I've listed as far as I know when garbage collection works and when it doesn't.

About garbage collection

A function that automatically destroys an object at Java's discretion when it is no longer handled by any variable. The "finalize () method" destroys the object, but in Java, even if this method is defined, the timing cannot be managed.

Cases where garbage collection works

Since I confirmed it with a simple process, I lowered the heap area to make it easier for memory leaks. (Like a memory leak at 2M) Initial heap size-Xms1m Maximum heap size-Xmx2m

① When all variables that refer to the object become null

Since "assigning null to a variable = the variable does not represent any object", it works when all the variables representing a certain object become null. Substitute null = It's like saying "I don't use this anymore", so Garbage Collection will judge that it is unnecessary.

public class AddList01 {
	public static void main(String[] args) {
		//Process 1
		List<String> list1 = new ArrayList<>();
		addList(list1);
		list1 = null;
		//Process 2
		List<String> list2 = new ArrayList<>();
		addList(list2);
		list2 = null;
	}

	private static void addList(List<String> list) {
		for (int i = 0; i < 100000; i++) {
			list.add("a");
		}
	}
}

② When the object is no longer handled

Garbage collection works when the variable that handles the object is no longer listed. "I saw the whole sauce, but I haven't dealt with it since then, so it's okay to throw away the contents."

public class AddList02 {
	public static void main(String[] args) {
		List<String> list1 = new ArrayList<>();
		List<String> list2 = new ArrayList<>();

		//Process 1
		for (int i = 0; i < 100000; i++) {
			list1.add("a");
		}

		//Process 2
		for (int i = 0; i < 100000; i++) {
			list2.add("b");
		}
	}
}

③ Local variables in loops and try-catch

Garbage collection works when a completed object in a loop, such as declared in a loop or instantiated, exits the loop. This is because variables declared inside the loop cannot be handled outside the loop.

public class AddList03 {
	public static void main(String[] args) {
		try {
			//Process 1
			for (int i = 0; i < 10; i++) {
				List<String> list1 = new ArrayList<>();
				for (int j = 0; j < 100000; j++) {
					list1.add("a");
				}
			}

			//Process 2
			List<String> list2 = new ArrayList<>();
			for (int i = 0; i < 10; i++) {
				list2 = new ArrayList<>();
				for (int j = 0; j < 100000; j++) {
					list2.add("b");
				}
			}

			//Process 3
			int i = 0;
			while (i < 10) {
				List<String> list3 = new ArrayList<>();
				for (int j = 0; j < 100000; j++) {
					list3.add("c");
				}
				i++;
			}

			//Process 4
			List<String> list4 = new ArrayList<>();
			for (int j = 0; j < 100000; j++) {
				list4.add("d");
			}
		} catch (Exception e) {
			e.printStackTrace();
		}

		//Process 5
		List<String> list5 = new ArrayList<>();
		for (int j = 0; j < 100000; j++) {
			list5.add("e");
		}
	}
}

By the way, when the variable "list2" is handled after the loop, only the instance of the last loop is left.

public class AddList031 {
	public static void main(String[] args) {
		List<String> list2 = new ArrayList<>();
		for (int i = 0; i < 10; i++) {
			list2 = new ArrayList<>();
			for (int j = 0; j < 50000; j++) {
				list2.add("b");
			}
		}
		System.out.println(String.join("", list2));
	}
}

④ Method local variable

It is the same principle as the loop of ③. However, the value referred to by the argument and the return value remain.

public class AddList04 {
	public static void main(String[] args) {
		addList();
		addList();
		addList();
	}

	private static void addList() {
		List<String> list = new ArrayList<>();
		for (int j = 0; j < 100000; j++) {
			list.add("a");
		}
	}
}

Case where garbage collection does not work

(1) Hold the instance in the main method and refer to the object in another method.

In case (2) that works, the object that is no longer handled was discarded, but if you refer to the object with another method, garbage collection will not work. Why is this?

public class AddList05 {
	public static void main(String[] args) {
		//Process 1
		List<String> list1 = new ArrayList<>();
		addList(list1);

		//Process 2
		List<String> list2 = new ArrayList<>();
		addList(list2);
	}

	private static void addList(List<String> list) {
		for (int i = 0; i < 100000; i++) {
			list.add("a");
		}
	}
}

(2) An instance of the list is generated in the field

Since it is used in various places in the class, it is a case where it is described in the field so that there are not many arguments. It seems that the scope of the list is too wide to decide that garbage collection is unnecessary.

public class AddList06 {
	private List<String> list1 = new ArrayList<>();
	private List<String> list2 = new ArrayList<>();

	public static void main(String[] args) {
		AddList06 addList06 = new AddList06();

		//Process 1
		for (int i = 0; i < 100000; i++) {
			addList06.list1.add("a");
		}

		//Process 2
		for (int i = 0; i < 100000; i++) {
			addList06.list2.add("a");
		}
	}
}

③ What was received as the return value of the method

Even if the instance is not created by the main method, the instance is created by the called method and the one received as the return value remains.

public class AddList07 {
	public static void main(String[] args) {
		//Process 1
		List<String> list = addList();

		//Process 2
		list = addList();

		//Process 3
		list = addList();
	}

	private static List<String> addList() {
		List<String> list = new ArrayList<>();
		for (int i = 0; i < 50000; i++) {
			list.add("a");
		}
		return list;
	}
}

④ Instead of substituting null, I try not to use it in another way.

Even if you use the list method to delete a value that is no longer used in the list you are using, it does not mean that it is null, so the object seems to remain referenced.

public class AddList08 {
	public static void main(String[] args) {
		//Process 1
		List<String> list1 = new ArrayList<>();
		addList(list1);
		list1.clear();

		//Process 2
		List<String> list2 = new ArrayList<>();
		addList(list2);
		for (int i = 0; i < list2.size(); i++) {
			list2.remove(i);
		}

		//Process 3
		List<String> list3 = new ArrayList<>();
		addList(list3);
	}

	private static void addList(List<String> list) {
		for (int i = 0; i < 50000; i++) {
			list.add("a");
		}
	}
}

Summary

You might think it's not that simple, but for the time being ・ "As much as possible, divide the process into small methods and have it locally" ・ "Use where you can reuse without creating variables and instances in vain" ・ "Refrain from variable declaration in the field" I felt that.

After that, if there is processing such as SQL acquisition and registration from DB, will it be divided according to the processing purpose? It may take time if there are multiple SQLs, but I thought it was better than the processing slowing down.

Recommended Posts

Be aware of garbage collection and avoid memory leaks
To be aware of easy-to-read code
Be aware of the internal state (invariance) of objects and the side effects of methods
[Java] Contents of Collection interface and List interface
5 things new programmers should be aware of
Things to be aware of when writing Java
[Java] Be aware of short circuits (short-circuit evaluation)
Difference between member and collection of rails routes.rb
Java Servlet should be aware of multithreaded environment