This is the 17th day of my participation in the August Challenge


1. Memory overflow, whose pot?

OutOfMemoryError OutOfMemoryError OutOfMemoryError OutOfMemoryError OutOfMemoryError OutOfMemoryError OutOfMemoryError OutOfMemoryError OutOfMemoryError OutOfMemoryError OutOfMemoryError OutOfMemoryError OutOfMemoryError OutOfMemoryError OutOfMemoryError

Memory leaks are generally possible in one of the following ways: 1. Memory leaks An unintended code defect that creates an object that is never used but cannot be reclaimed by the GC, resulting in a memory leak.

2. Out of memory Too many objects are generated and memory is used up, either because of bad code or because the system really needs these objects but does not have enough physical memory.

Unfortunately, when I was working for my first company, I encountered frequent online system outages. After checking the logs, I found that they were caused by memory overflow. The server had 8GB of memory, we tried to increase the MEMORY of the JVM, allocated 4GB, and still crashed after a while. Then everyone started to get nervous, because the system is not complicated, 4G memory is absolutely enough, if it still runs out of memory, it is only a code problem.

We first is to set up the JVM parameter – XX: + HeapDumpOnOutOfMemoryError virtual machine in the memory leak generated when memory snapshot and export to disk, we get the snapshot file began after the analysis of objects in the heap, found that Auth class instance number very much, there are millions of. Auth class is the description of the system permissions, User class holds the reference to the Auth class, its purpose is the User request interface for permission verification, there is no interface permissions User request interface, the server is refused to perform. When will an Auth object be generated? Check the code and find that it is the login time. The system will query the basic information of the user first, then query the list of permissions the user has, and then add the user information into the Session.

I tried to reproduce this process in code, and the class diagram was designed as follows:Describing permissionAuthClass:

public class Auth {
	private String name;/ / permission
	private String uri;/ / interface URI

	public Auth(String name, String uri) {
		this.name = name;
		this.uri = uri; }}Copy the code

User class User:

@Data
public class User {
	private Long userId;
	private String userName;
	private String password;
	private Integer role;
	private List<Auth> auths;// List of permissions that the user has
}
Copy the code

AuthService can query the privileges of a role from the database:

public class AuthService {

	// Query the permission that role has
	public List<Auth> getByRole(Integer role){
		return mockData(role);
	}

	// Simulate data
	private List<Auth> mockData(Integer role) {
		List<Auth> list = new ArrayList<>();
		switch (role){
			case 1:// Common user
				list.add(new Auth("View"."/look.html"));
			case 2:/ / administrator
				list.add(new Auth("New"."/add.html"));
				list.add(new Auth("Change"."/update.html"));
				list.add(new Auth("Delete"."/delete.html"));
			case 3:// Super administrator
				list.add(new Auth("Omnipotent"."/almighty.html"));
			default:;
		}
		returnlist; }}Copy the code

Simulate Tomcat’s Session container, represented here by Map:

public class Session {
	private static final ConcurrentMap<String, User> MAP = new ConcurrentHashMap<>();

	public static void put(String sessionId, User user) { MAP.put(sessionId, user); }}Copy the code

UserService Saves the user information and permission list to the Session container after the user logs in:

public class UserService {
	private AuthService authService = new AuthService();

	public void login(String userName, String password) {
		User user = new User();
		user.setUserId(ThreadLocalRandom.current().nextLong());
		user.setUserName(userName);
		user.setPassword(password);
		user.setRole(ThreadLocalRandom.current().nextInt(2) + 1);
		// Query the permissions of the user
		user.setAuths(authService.getByRole(user.getRole()));
		// User information is stored in SessionSession.put(UUID.randomUUID().toString(), user); }}Copy the code

The client calls like this:

public class Client {
	public static void main(String[] args) {
		UserService userService = new UserService();
		// Simulate a million login requests from the client
		for (int i = 0; i < 1000000; i++) {
			userService.login("root"."123"); }}}Copy the code

This implementation function is normal, but let’s analyze, this program has any problems?

Permissions are related to roles, not users! This is the core of the problem. The current implementation is: whenever a user logs in, the system queries the user’s permission list once. From the perspective of the JVM, this generates N Auth objects and an ArrayList object. The number of roles is limited, and the number of users can be considered infinite. If you have tens of thousands of online users and many permissions, the JVM may need to generate millions of Auth objects and tens of thousands of ArrayList objects, running out of memory.

Since the company did not have the habit of Code Review, the problem was not found in the test. Because there was no load test, there were few online users in the test phase, so of course there would be no memory overflow. However, as the project went online, with the increase of the number of online users, the hidden Bug was immediately revealed.

Identify the problem, and the next step is to solve it. This problem is easily solved by using the “share element pattern”, where users in the same role share the same permission list, avoiding creating too many duplicate objects and reducing the memory footprint immediately.

The optimized class diagram design is as follows:Introduced aAuthFactoryPermission factory class, which will load the list of permissions corresponding to all roles at initialization and cache down, user login fromAuthFactoryGet the permission list corresponding to their role from the cache, so that all users with the same role can reuse the same permission list.

public class AuthFactory {
	/ / cache
	private static final ConcurrentMap<Integer, List<Auth>> map = new ConcurrentHashMap<>();
	private static AuthService authService = new AuthService();

	static{
		// Preloads the permissions of roles. Assume that there are only three roles
		map.put(1, authService.getByRole(1));
		map.put(2, authService.getByRole(2));
		map.put(3, authService.getByRole(3));
	}

	public static List<Auth> getByRole(Integer role) {
		returnmap.get(role); }}Copy the code

The UserService class has been modified to call AuthService to get the list of permissions, but now gets it from the AuthFactory cache instead of repeating the code. The client side of the call has not changed, but the optimized code, memory footprint is very small, there is no memory overflow, the customer is very satisfied.

usejmapAnalyzing heap memory, the following are the situations before and after optimization respectively: As shown in the figure, the effect is very obvious, this is enjoy yuan mode!

2. The definition of enjoy yuan mode

Using shared objects effectively supports a large number of fine-grained objects.

Enjoy meta-pattern generic class diagram

  • Flyweight: An abstraction of a role responsible for defining an object’s internal state and the interface or implementation of its external state.
  • ConcreteFlyweight: ConcreteFlyweight: an operation that implements an abstract role definition. Note that the shared object must not be modified, otherwise it would be messy.
  • UnsharedConcreteFlyweight: do not share the flyweight roles, safety requirements of the external state does not exist or cannot use Shared technology of object, the object does not generally appear in the flyweight factory.
  • FlyweightFactory: A weightfactory that creates a pool container and provides a method to get weightobjects from the container.

The core of the share mode is the use of “object pool” technology, so that objects can be reused, avoid creating repeated meaningless objects, reduce memory footprint, reduce the pressure of GC.

3. Advantages and disadvantages of enjoy yuan mode

Sharing mode makes objects reusable, greatly reducing the creation of repeated objects, reducing the occupation of program memory, reducing the pressure of GC, and improving the performance of the program. But the share pattern requires classes to separate the internal state from the external state, increasing the complexity of the system. When a large number of similar objects exist in the system, these similar attributes can be stripped out for object reuse.

4. To summarize

Sharing mode is an important way to realize “pool technology”, which puts forward two requirements for us: 1. Fine-grained objects. 2. Shared objects. Creating too many objects in an application can increase memory usage, reduce application performance, and possibly cause memory overflow. Using the share mode, separate the internal state and external state of the object, through the share element factory to cache the external state, so that the repeated external state can be reused, avoid the program to create meaningless repeated objects frequently. It is important to note that the meta-class must not modify the external state of the share, otherwise it will cause data chaos. Finally, since objects are shared, you need to pay special attention to the “thread-safe” issue!