[Java Interview Questions] "Java Big Factory Interview Questions in Shang Silicon Valley Season 2, Interview Must Brush, Job-hopping Big Factory Artifact" Study Notes

Article Directory

what is volatile

Volatile is a lightweight synchronization mechanism provided by the java virtual machine. Three major features: (code examples in the next section)

  • Guaranteed visibility
  • atomicity not guaranteed
  • Prohibition of command rearrangement

JMM memory model

1. It does not really exist, it is a rule, a specification, which defines the access method of each variable in the program (including instance fields, static fields and elements that constitute array objects).

2, three major characteristics:

  • visibility
  • atomicity
  • orderliness

3. JMM's regulations on synchronization:

  • Before the thread is unlocked, the value of the shared variable must be refreshed back to the main memory
  • Before the thread is locked, it must read the latest value of the main memory to its own working memory
  • Locking and unlocking is the same lock

4. The operation of the thread on the variable (reading and assignment, etc.) must be performed in the working memory. First, the variable must be copied from the main memory to its own working memory space, and then the variable is operated. After the operation is completed, the variable is written back to the main memory. Memory

insert image description here
insert image description here

5. Visibility:

  • Visibility means that after another thread modifies the variable and writes it back to the main memory, it is visible to the current thread
  • code:
public class P03 {
    
    
    volatile int num;//加volatile程序才能正常停止
    // int num;

    void add1(){
    
    
        num++;
    }

    public static void main(String[] args) {
    
    
        P03 p = new P03();

        //开启一个线程,改变num的数值
        new Thread(() -> {
    
    
            try {
    
    
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }

            p.add1();
            System.out.println("add1完成");
        }).start();

        //按理,当上面的线程执行完之后,应该立即结束,但未加volatile修饰变量时一直没结束
        while (p.num == 0){
    
    

        }
    }
}

6. Atomicity:

  • Indivisible, complete, what you are doing should not be interrupted by other threads; either succeed at the same time, or fail at the same time
  • volatile does not guarantee atomicity, solution:
    • synchronized
    • lock
    • AtomicInteger
  • code:
public class P05 {
    
    
    volatile int num;
    
    void  add1(){
    
    
        num++;
    }
    
    public static void main(String[] args) {
    
    
        P05 p = new P05();
        
        //10个线程,每个加1000
        for (int i = 0; i < 10; i++) {
    
    
            new Thread(() ->{
    
    
                for (int j = 0; j < 1000; j++) {
    
    
                    p.add1();
                }
            }).start();
        }
        
        // 2个线程时,是指:main线程、gc线程
        while (Thread.activeCount() > 2){
    
    
            Thread.yield();
        }
        
        // 理论上时10 * 1000,但测试结果小于该值; 说明volatile不保证原子性
        // 想要结果是10000,使用synchronized、lock或者AtomicInteger
        System.out.println(p.num);
    }
}

7. Command rearrangement
method:

  • Compiler-optimized rearrangements
  • Instruction Parallel Reordering
  • memory system rearrangement

illustrate:

  • The result of single-threaded execution is determined, and the result may be unpredictable in the case of multi-threaded
  • Consider data dependencies

insert image description here

For example:

  • rearrangement 1
    insert image description here

Statement 4 will never be the first sentence because of data dependencies

  • rearrangement 2
    insert image description here
  • rearrangement 3
    • In a multi-threaded environment, threads are executed alternately. Due to the existence of compiler optimization rearrangement,
    • Whether the variables used in the two threads can guarantee consistency is uncertain, and the results are unpredictable

insert image description here

Summarize:
insert image description here

  • The synchronization delay between the working memory and the main memory 可见性问题can be solved by using the synchronized or volatile keywords, both of which can make the variables modified by one thread immediately visible to other threads.

  • For the visibility problem caused by instruction rearrangement 有序性问题, you can use the volatile keyword to solve it, because another function of volatile is to prohibit reordering optimization.


Singleton mode volatile

Singleton mode:

public class P11 {
    
    
    private static P11 instance;

    private P11() {
    
    
        System.out.println("构造方法...");
    }

    // DCL, double check lock,双端检验机制
    public static P11 getInstance() {
    
    
        if (instance != null) {
    
    
            synchronized (P11.class){
    
    
                if (instance != null) {
    
    
                    instance = new P11();
                }
            }
        }

        return  instance;
    }
}
  • There is instruction rearrangement in the above code, there is no problem under single thread, but there is a low probability of problems under multi-thread.
  • In order to prevent instruction rearrangement, consider adding volatile modification to the variable instance, so that instruction rearrangement can be avoided.

insert image description here


CAS

Compare And Swap, compare and exchange.

  • Compare the value in the current working memory with the value in the main memory, if they are the same, perform the specified operation;
    otherwise, continue to compare until the values ​​in the main memory and the working memory are consistent
  • CAS has 3 operands, the memory value V, the old expected value A, and the updated value B to be modified.
    Modify the memory value V to B if and only if the expected value A and the memory value V are the same, otherwise do nothing.

Unsafe:

  • It is the core class of CAS. Since the Java method cannot directly access the underlying system, it needs to be accessed through the local <native) method. Unsafe is equivalent to a backdoor, and based on this class, the data of a specific memory can be directly manipulated. The Unsafe class exists in the sun.misc package, and its internal method operations can directly manipulate memory like a pointer to c, because the execution of the CAS operation in Java depends on the methods of the Unsafe class.
  • All methods in the Unsafe class are natively modified, that is to say, the methods in the Unsafe class directly call the underlying resources of the operating system to perform corresponding tasks

shortcoming:

  • The cycle time is long and the overhead is high. If CAS fails, it will keep trying; if CAS has been unsuccessful for a long time, it may bring a lot of overhead to the CPU.

  • Atomic operations can only be guaranteed for one shared variable. If there are more than one, it can only be locked

  • ABA problems. What the current thread gets is A, and what the modification gets is also A, thinking that this A has not changed; in fact, it is possible that A is modified to B by other threads, but it is modified back to A before the current thread is ready to modify; so the current thread Thinking that there is no modification, ABA problem occurs

Custom type atomic class:

  • code:
    public class P17 {
          
          
        public static void main(String[] args) {
          
          
            // 自定义两个日期:
            LocalDate ld1 = LocalDate.now();
            LocalDate ld2 = LocalDate.now();
    
            // 日期原子类
            AtomicReference<LocalDate> localDateAtomicReference = new AtomicReference<>(ld1);
    
            //第一次修改:
            boolean cas1 = localDateAtomicReference.compareAndSet(ld1, ld2);
            System.out.println("cas1 = " + cas1);
            System.out.println("localDateAtomicReference.get() = " + localDateAtomicReference.get());
    
            // 第二次修改:
            boolean cas2 = localDateAtomicReference.compareAndSet(ld1, ld2);
            System.out.println("cas2 = " + cas2);
            System.out.println("localDateAtomicReference.get() = " + localDateAtomicReference.get());
        }
    }
    
  • result:
    insert image description here

insert image description here

Take a look at the getAndIncrement method:
insert image description here

Lower level:
insert image description here


ABA questions

For example

  • Both threads t1 and t2 need to modify the value of variable v
  • The value of v obtained by t1 before modification is A
  • t2 gets the value of v and modifies it to B, and then modifies it back to A
  • At this time, t1 is going to modify the value of v, and the cas is successful, because the value of v is A, which is the same as the expected value;
  • A thinks that v has not changed, but actually produces an ABA problem

Code: (Generation of ABA)

AtomicReference<String> stringAtomicReference = new AtomicReference<>("A");

//t1 改为B再改回A
new Thread(() -> {
    
    
    stringAtomicReference.compareAndSet("A","B");
    System.out.println("v1 = " + stringAtomicReference.get());
    stringAtomicReference.compareAndSet("B","A");
    System.out.println("v2 = " + stringAtomicReference.get());
},"t1").start();

//t2 将A改为B
new Thread(() -> {
    
    
	try {
    
    
     TimeUnit.SECONDS.sleep(1);
 } catch (InterruptedException e) {
    
    
     e.printStackTrace();
 }
 
    stringAtomicReference.compareAndSet("A","B");
    System.out.println("v3 = " + stringAtomicReference.get());
},"t2").start();

Code: (ABA solution, using an atomic class that includes a timestamp: AtomicStampedReference)

AtomicStampedReference<String> stampedReference = new AtomicStampedReference<>("A", 1);
//t3 改为B再改回A
new Thread(() -> {
    
    
    //保证t4先获取到版本号
    try {
    
    
        TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
    
    
        e.printStackTrace();
    }

    boolean compareAndSet1 = stampedReference.compareAndSet("A", "B", stampedReference.getStamp(), stampedReference.getStamp() + 1);
    System.out.println("compareAndSet1 = " + compareAndSet1);

    boolean compareAndSet2 = stampedReference.compareAndSet("B", "A", stampedReference.getStamp(), stampedReference.getStamp() + 1);
    System.out.println("compareAndSet2 = " + compareAndSet2);
}, "t3").start();

//t4 将A改为B
new Thread(() -> {
    
    
    int stamp = stampedReference.getStamp();

    //保证t3执行一次ABA操作了
    try {
    
    
        TimeUnit.SECONDS.sleep(2);
    } catch (InterruptedException e) {
    
    
        e.printStackTrace();
    }

    boolean compareAndSet3 = stampedReference.compareAndSet("A", "B", stamp, stamp + 1);
    System.out.println("compareAndSet3 = " + compareAndSet3);
}, "t4").start();

Collection class exception ConcurrentModificationException

produce:

  • code:
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
    
    
    int finalI = i;
    new Thread(() -> {
    
    
        for (int j = 0; j < 100; j++) {
    
    
            list.add(finalI * j);
        }

        System.out.println(list);
    }).start();
}
  • A java.util.ConcurrentModificationException occurs:
    insert image description here

reason:

  • Concurrent race modification results in

Solution:

  • Using vector, the bottom layer adds synchronized to the method, which is inefficient
    insert image description here

  • Use the Collections tool class to wrap the unsafe collection into a safe collection class, and
    insert image description here
    actually add synchronized in the method body:
    insert image description here


  • insert image description here
    insert image description here
    copy-on-write using classes in juc

    • The CopyOnWrite container is a copy-on-write container.
    • When adding elements to a container, instead of adding directly to the current container Object[], first copy the current container Object[] into icopy and copy out a new container object[] newELements
    • Then add elements to the new container object[] newELements
    • After adding elements, point the reference of the original container to the new container: setArray(newELements);
    • The advantage of this is that the Copyonwrite container can be read concurrently without locking, because the current container will not add any elements. So the Copyonwrite container is also an idea of ​​separation of reading and writing, reading and writing different containers

Unsafe classes and their corresponding juc classes:

  • ArrayList —> CopyOnWriteArrayList
  • HashSet —> CopyOnWriteArraySet
  • HashMap —> ConcurrentHashMap

Method parameter passing mechanism:

Title:
insert image description here
Result:
insert image description here


Lock

fair lock/unfair lock

Mainly depends on whether there are threads or not queued

  • Fair lock means that multiple threads acquire locks in the order in which they apply for locks
  • Unfair locks mean that the order in which multiple threads acquire locks is not in the order in which they apply for locks. It is possible that threads that apply later may acquire priority than threads that apply first. In the case of high concurrency, it may cause priority inversion or hunger phenomenon

fair lock/unfair lock

  • The creation of ReentrantLock in the concurrent package can specify the boolean type of the constructor to obtain a fair lock or an unfair lock, and the default is an unfair lock

About the difference between the two:

  • Fair lock: Threads acquire a fair lock in the order in which they requested it
    Fair lock is very fair. In a concurrent environment, each thread will first check the waiting queue maintained by this lock when acquiring a lock. If it is empty, or The current thread is the first one in the waiting queue, so it holds the lock, otherwise it will be added to the waiting queue, and will get itself from the queue in accordance with the rules of FIFO
  • Unfair lock: a nonfair lock permits barging: threads requesting a lock can jump ahead of the queue of waiting threads if the lockhappens to be available when it is requested. Unfair locks are rude, just try to occupy the lock directly,
    if the attempt fails , and then adopt a method similar to a fair lock.

ReentrantLock uses an unfair lock by default, which can increase throughput; synchronized is also an unfair lock.
insert image description here

Reentrant lock (recursive lock)

  • When the same thread acquires the lock in the outer method, it will automatically acquire the lock when entering the inner method
  • That is, a thread can enter any block of code that is synchronized by a lock it already owns.

A calls the B method, and A acquires the lock, then B does not need to acquire the lock again

code:

  • synchronized version:
@Slf4j
public class P27 {
    
    
    public static void main(String[] args) {
    
    
        P27 p = new P27();
        p.sendSms();
    }

    synchronized void sendSms() {
    
    
        log.info("发短信...");
        sendEmail();
    }

    synchronized void sendEmail() {
    
    
        log.info("发邮箱...");
    }
}

insert image description here

explain:

  • Synchronized modifies the ordinary method, the lock is the object, that is, the lock is the object of the call
  • sendSms has obtained the lock, and the internal call to sendEmail has to obtain the lock again, which is supposed to have been obtained and cannot be obtained
  • However, because it is a reentrant lock, sendEmail is inside sendSms, sendSms has already acquired the lock, and the internal method does not need to acquire the lock anymore, it has been acquired automatically
  • lock version:
@Slf4j
public class P27 {
    
    
    Lock lock = new ReentrantLock();

    public static void main(String[] args) {
    
    
        P27 p = new P27();
        p.sendSms();
    }

    void sendSms() {
    
    
        lock.lock();

        try {
    
    
            log.info("发短信...");
            sendEmail();
        } finally {
    
    
            lock.unlock();
        }
    }

    void sendEmail() {
    
    
        lock.lock();

        try {
    
    
            log.info("发邮箱...");
        } finally {
    
    
            lock.unlock();
        }
    }
}

insert image description here

spin lock

  • Loop through comparisons until successful.
  • No wait-like blocking

Write a spin lock by hand:

@Slf4j
public class P29 {
    
    
    AtomicReference<Thread> atomicReference = new AtomicReference<>();

	//加锁
    void myLock() {
    
    
        log.info("准备获取锁...");
        while (!atomicReference.compareAndSet(null, Thread.currentThread())) {
    
    

        }
        log.info("获取到锁");
    }

	//解锁
    void myUnlock() {
    
    
        log.info("准备释放锁...");
        atomicReference.compareAndSet(Thread.currentThread(), null);
        log.info("释放锁成功");
    }

    public static void main(String[] args) {
    
    
        P29 p = new P29();

        //线程1先获取到锁
        new Thread(() -> {
    
    
            p.myLock();

            try {
    
    
                log.info("do something");
                //保证线程2准备获取锁,但又没获取到
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            } finally {
    
    
                p.myUnlock();
            }
        }).start();

        //保证线程1获取到锁
        try {
    
    
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }

        //线程1释放锁之后,线程2才能获取到锁
        new Thread(() -> {
    
    
            p.myLock();

            try {
    
    
                log.info("do something");
            } finally {
    
    
                p.myUnlock();
            }
        }).start();
    }
}

insert image description here

Exclusive lock (write lock), shared lock (read lock), mutex lock

  • Read and read can coexist
  • Reading and writing, writing and writing cannot coexist

code:

@Slf4j
public class P31 {
    
    
    volatile Map<Integer, Integer> cache = new HashMap<>();

    ReadWriteLock rwLock = new ReentrantReadWriteLock();

    /**
     * 放入数据
     */
    void put(int key, int val) {
    
    
        Lock writeLock = rwLock.writeLock();
        writeLock.lock();
        try {
    
    
            log.info("准备写入:{}", key);
            cache.put(key, val);
            log.info("{}写入完成!", key);
        } finally {
    
    
            writeLock.unlock();
        }
    }

    /**
     * 读取数据
     */
    void get(int key) {
    
    
        Lock readLock = rwLock.readLock();
        readLock.lock();
        try {
    
    
            log.info("准备读取:{}", key);
            Integer val = cache.get(key);
            log.info("读取到的结果:{}", val);
        } finally {
    
    
            readLock.unlock();
        }
    }

    public static void main(String[] args) {
    
    
        P31 p = new P31();

        for (int i = 0; i < 3; i++) {
    
    
            int finalI = i;
            new Thread(() -> {
    
    
                p.put(finalI, finalI);
            }, String.valueOf(i)).start();
        }

        for (int i = 0; i < 3; i++) {
    
    
            int finalI = i;
            new Thread(() -> {
    
    
                p.get(finalI);
            }, String.valueOf(3 + i)).start();
        }
    }
}

insert image description here

  • There is no queue jumping phenomenon for writing. There is queue jumping in reading because it is a shared lock.

CounDownLatch

  • Let some threads block until other threads complete a series of operations before being woken up
  • CountDownLatch mainly has two methods. When one or more threads call the await method, the calling thread will be blocked. Calling the countDown method by other threads will decrement the counter by 1 (the thread calling the countDown method will not be blocked). When the value of the counter becomes zero, the thread blocked by calling the await method will be awakened and continue to execute.

For example, if there are 12 months in a year, if the 12 months have passed, the year will be considered as finished;

@Slf4j
public class P32 {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        CountDownLatch countDownLatch = new CountDownLatch(12);

        for (int i = 1; i <= 12; i++) {
    
    
            int finalI= i;
            new Thread(() -> {
    
    
                log.info("{}月过了",finalI);
                countDownLatch.countDown();
            }).start();
        }

        countDownLatch.await();
        System.out.println("一年过完了");
    }
}

insert image description here

CyclicBarrier

  • CyclicBarrier literally means a barrier (Barrier) that can be recycled (Cyclic). What it has to do is to block a group of threads when they reach a barrier (also called a synchronization point), and the barrier will not open until the last thread reaches the barrier. All threads enter the barrier through the await() method of 被屏障拦截的线程才会继续干活CyclicBarrier .
  • There is another difference between CounDownLatch and CyclicBarrier except that one is decreasing and the other is increasing.
    • CounDownLatch is A thread calls await, other threads do coutDown, and no other threads have anything to do after finishing. It's similar to locking a classroom, it can be locked only when people leave (no one else has anything to do when they leave).
    • CyclicBarrier is where each thread calls await and waits until the number reaches the requirement, and all await threads work together. Similar to group buying, everyone will buy when all the people are here (if all the people are there, they have to pay for the purchase).

For example, to simulate puzzles:

@Slf4j
public class P33 {
    
    
    public static void main(String[] args) {
    
    
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
    
    
            System.out.println("---拼团成功!!!---");
        });

        for (int i = 0; i < 7; i++) {
    
    
            final int finalI = i;
            new Thread(() -> {
    
    
                log.info("第{}个人参加拼团", finalI);
                try {
    
    
                    cyclicBarrier.await();
                    log.info("已经够7个人,第{}个人已付款,拼团成功", finalI);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
    
    
                    e.printStackTrace();
                }

            }).start();
        }
    }
}

insert image description here

Semaphore

  • The semaphore is mainly used for two purposes, one is for mutual exclusion of multiple shared resources, and the other is for controlling the number of concurrent threads.

For example, there are 6 people in the restaurant, but there are only three tables for dining:

@Slf4j
public class P34 {
    
    
    public static void main(String[] args) {
    
    
        Semaphore semaphore = new Semaphore(3);
        Random random = new Random();

        for (int i = 0; i < 6; i++) {
    
    
            int finalI = i;
            new Thread(() -> {
    
    
                try {
    
    
                    log.info("第{}个人准备就餐", finalI);
                    semaphore.acquire();

                    log.info("第{}个人成功就餐...", finalI);
                    int time = random.nextInt(3);
                    TimeUnit.SECONDS.sleep(time);
                    log.info("第{}个人就餐结束,用时{}秒!", finalI, time);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                } finally {
    
    
                    semaphore.release();
                }
            }).start();
        }
    }
}

insert image description here


enumerate

  • An enumeration is like an immutable table in a database
  • An enumeration is a special class in java that represents a set of constants

Common methods:

  • values(), get all constants
  • ordinal(), get the index
  • valueOf(), get the enumeration constant of the specified string value

For example, if there is a super user table with information such as id, name, password, etc., you can use enumeration to achieve:

import lombok.Getter;

public enum AdminEnum {
    
    
    SUPER_ADMIN(1L, "超级管理员", "1234"),
    COMMON_ADMIN(2L, "普通管理员", "5678");

    @Getter
    private Long id;

    @Getter
    private String username;

    @Getter
    private String password;

    AdminEnum(Long id, String username, String password) {
    
    
        this.id = id;
        this.username = username;
        this.password = password;
    }

    //自定义方法,通过id来找用户
    public static AdminEnum getById(Long id){
    
    
        for (AdminEnum value : values()) {
    
    
            if (id.equals(value.id)){
    
    
                return value;
            }
        }
        return null;
    }

    public static void main(String[] args) {
    
    
        //获取所有枚举元素
        AdminEnum[] values = AdminEnum.values();
        System.out.println("====================");
        for (AdminEnum value : values) {
    
    
            //ordinal -- 枚举的索引
            System.out.println("ordinal = " + value.ordinal() + ",value = " + value);
        }

        //输出每一个枚举元素的信息
        System.out.println("====================");
        AdminEnum superAdmin = AdminEnum.SUPER_ADMIN;
        System.out.println("superAdmin = " + superAdmin);
        System.out.println("superAdmin.id = " + superAdmin.id);
        System.out.println("superAdmin.username = " + superAdmin.username);
        System.out.println("superAdmin.password = " + superAdmin.password);

        //获取指定字符串值的枚举常量
        System.out.println("====================");
        AdminEnum valueOf = AdminEnum.valueOf("SUPER_ADMIN");
        System.out.println("valueOf super_admin = " + valueOf);

        //通过id获取用户的方法:
        System.out.println("====================");
        AdminEnum byId = AdminEnum.getById(1L);
        System.out.println("byId = " + byId);
    }
}

insert image description here

blocking queue

overview

  • When a blocking queue is active , operations on elements from the queue will be blocked.获取
  • When the blocking queue is active , the operation of adding elements to the queue 添加will be blocked.
  • In the field of multi-threading: the so-called blocking, in some cases, the thread will be suspended (that is, blocked), once the condition is met, the
    suspended thread will be automatically awakened
  • Why is BlockingQueue needed?
    • The advantage is that we don't need to care about when the thread needs to be blocked and when the thread needs to be woken up, because BlockingQueue has taken care of all this for you
    • Before the concurrent package was released, in a multi-threaded environment, each of our programmers had to control these details by themselves, especially taking into account efficiency and thread safety, which would bring a lot of complexity to our programs.

Blocking queue: (commonly marked in red)

  • ArrayBlockingQueue: A bounded blocking queue composed of array structures.
  • LinkedBlockingQueue: Bounded (but the size defaults to Integer.MAX_VALUE ) blocking queue composed of chainclothes structures
  • PriorityBlockingQueue : An unbounded blocking queue that supports priority sorting.
  • DelayQueue: A delayed unbounded blocking queue implemented using a priority queue.
  • SynchronousQueue: A blocking queue that does not store elements, that is, a queue of a single element
  • LinkedTransferQueue: An unbounded blocking queue composed of a linked list structure.
  • LinkedBlockingDeque: A two-way blocking queue composed of a linked list structure

Common methods:
insert image description here

ArrayBlockingQueue、LinkedBlockingQueue

Methods that throw exceptions: add, remove
public class P37 {
    
    
    public static void main(String[] args) {
    
    
        BlockingQueue<Integer> blockingQueue = new LinkedBlockingQueue<>(2);

        System.out.println("add 1:" + blockingQueue.add(1));
        System.out.println("add 2:" + blockingQueue.add(2));

        try {
    
    
            System.out.println("add 3:" + blockingQueue.add(3));
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }

        System.out.println("element:" + blockingQueue.element());

        System.out.println("remove 1:" + blockingQueue.remove());
        System.out.println("remove 2:" + blockingQueue.remove());

        try {
    
    
            System.out.println("element:" + blockingQueue.element());
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }

        try {
    
    
            System.out.println("remove 3:" + blockingQueue.remove());
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}

insert image description here

Methods that return special values: offer, poll
public class P38 {
    
    
    public static void main(String[] args) {
    
    
        BlockingQueue<Integer> blockingQueue = new LinkedBlockingQueue<>(2);

        System.out.println("offer 1:" + blockingQueue.offer(1));
        System.out.println("offer 2:" + blockingQueue.offer(2));

        try {
    
    
            System.out.println("offer 3:" + blockingQueue.offer(3));
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }

        System.out.println("peek:" + blockingQueue.peek());

        System.out.println("poll 1:" + blockingQueue.poll());
        System.out.println("poll 2:" + blockingQueue.poll());

        try {
    
    
            System.out.println("peek:" + blockingQueue.peek());
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }

        try {
    
    
            System.out.println("poll 3:" + blockingQueue.poll());
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}

insert image description here

Always blocked: put, take
@Slf4j
public class P39 {
    
    
    public static void main(String[] args) {
    
    
        BlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<>(1);

        new Thread(() -> {
    
    
            try {
    
    
                blockingQueue.put(1);
                log.info("放入1成功");

                blockingQueue.put(2);
                log.info("放入2成功");
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }).start();

        new Thread(() -> {
    
    
            try {
    
    
                TimeUnit.SECONDS.sleep(2);

                blockingQueue.take();
                log.info("取出1成功");

                blockingQueue.take();
                log.info("取出2成功");
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }).start();

    }
}

insert image description here

  • When the capacity is 1, after an element is put in, only when the element is taken out, a new element is allowed to be put in, otherwise it will be blocked all the time
Timeout mechanism: offer, poll
        BlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<>(1);

        System.out.println("blockingQueue.offer(1) = " + blockingQueue.offer(1));
        try {
    
    
            System.out.println("blockingQueue.offer(2,1,TimeUnit.SECONDS) = " + blockingQueue.offer(2,1,TimeUnit.SECONDS));
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }

insert image description here

SynchronousQueue (no capacity)

  • SynchronousQueue is a BlockingQueue that does not store elements.
  • Each put operation must wait for one take operation, otherwise elements cannot be added, and vice versa.
  • It is very similar to the case where ArrayBlockingQueue sets the capacity to 1
@Slf4j
public class P40 {
    
    
    public static void main(String[] args) {
    
    
        BlockingQueue<Integer> blockingQueue = new SynchronousQueue<>();

        new Thread(() -> {
    
    
            try {
    
    
                blockingQueue.put(1);
                log.info("放入1成功");

                blockingQueue.put(2);
                log.info("放入2成功");
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }).start();

        new Thread(() -> {
    
    
            try {
    
    
                TimeUnit.SECONDS.sleep(2);

                blockingQueue.take();
                log.info("取出1成功");

                TimeUnit.SECONDS.sleep(2);

                blockingQueue.take();
                log.info("取出2成功");
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }).start();
    }
}

insert image description here

Thread Communication – Producer Consumer Model

synchronized、lock

  • thread - operation - resource class
  • Judgment - Work - Notice

step

  • First, there must be one 资源类, similar to a commodity (but the reserve can only be 1)
  • 操作That is, the method, assuming that there are two methods: production and consumption;
  • 判断: Judge whether you should work by yourself? If not, wait (similar to already has goods, producer waits)
  • If it's up to you 干活, do the work (similar to, there is already a product, it's time for the consumer to do the work)
  • 通知Do not forget other threads to work after finishing the work (similar to producing a product, the consumer must be notified to consume; to consume a product, the producer must be notified to produce)
  • Note: To prevent false wakeup, use while instead of if, because there are only two operations now, if there is a third operation, then once awakened, the other two will be awakened, in fact we only want to wake up one of them

synchronized version and lock version:

  • sync version, use sync to lock, wait to wait, and notify to notify;
  • For the lock version, use lock to lock, the await of lock.newCondition() to wait, and the signal to notify;
  • There is also a blocking queue version later
    insert image description here
public class P41 {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        //=============使用synchronized实现=========
        ResourceSync resourceSync = new ResourceSync();

        for (int i = 0; i < 5; i++) {
    
    
            new Thread(() -> {
    
    
                try {
    
    
                    resourceSync.add();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }).start();

            new Thread(() -> {
    
    
                try {
    
    
                    resourceSync.del();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }).start();
        }

		TimeUnit.SECONDS.sleep(1);
        System.out.println("=====================");

        //===============使用lock实现===================
        ResourceLock resourceLock = new ResourceLock();

        for (int i = 0; i < 5; i++) {
    
    
            new Thread(() -> {
    
    
                try {
    
    
                    resourceLock.add();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }).start();

            new Thread(() -> {
    
    
                try {
    
    
                    resourceLock.del();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

// 使用synchronized实现
@Slf4j
class ResourceSync {
    
    
    int count;

    synchronized void add() throws InterruptedException {
    
    
        while (count != 0) {
    
    
            this.wait();
        }

        log.info("add 1");
        count++;
        this.notifyAll();
    }

    synchronized void del() throws InterruptedException {
    
    
        while (count == 0) {
    
    
            this.wait();
        }

        log.info("del 1");
        count--;
        this.notifyAll();
    }
}

// 使用lock实现
@Slf4j
class ResourceLock {
    
    
    int count;
    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();

    void add() throws InterruptedException {
    
    
        lock.lock();
        try {
    
    
            while (count != 0) {
    
    
                condition.await();
            }

            log.info("add 1");
            count++;
            condition.signalAll();
        } finally {
    
    
            lock.unlock();
        }

    }

    void del() throws InterruptedException {
    
    
        lock.lock();
        try {
    
    
            while (count == 0) {
    
    
                condition.await();
            }

            log.info("del 1");
            count--;
            condition.signalAll();
        } finally {
    
    
            lock.unlock();
        }

    }
}

insert image description here

blocking queue

Producer Consumer - Blocking Queue

public class P44 {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        Resource44 resource = new Resource44(new ArrayBlockingQueue<>(3));

        //生产线程启动
        new Thread(() -> {
    
    
            try {
    
    
                resource.product();
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }).start();

        //消费线程启动
        new Thread(() -> {
    
    
            try {
    
    
                resource.consumer();
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }).start();

        //五秒后强制叫停:
        TimeUnit.SECONDS.sleep(5);
        System.out.println("main方法叫停生产消费!!!");
        resource.stop();
    }
}

@Slf4j
class Resource44 {
    
    
    private volatile boolean flag = true;
    private BlockingQueue<Integer> blockingQueue;
    private AtomicInteger atomicInteger = new AtomicInteger();

    public Resource44(BlockingQueue<Integer> blockingQueue) {
    
    
        this.blockingQueue = blockingQueue;
        log.info("BlockingQueue:{}", blockingQueue.getClass().getName());
    }

    /**
     * 生产者生产
     *
     * @throws InterruptedException
     */
    public void product() throws InterruptedException {
    
    
        while (flag) {
    
    
            int val = atomicInteger.incrementAndGet();
            boolean offer = blockingQueue.offer(val, 2, TimeUnit.SECONDS);
            if (offer) {
    
    
                log.info("生产 资源-{} 成功", val);
            } else {
    
    
                log.info("生产 资源-{} 失败", val);
            }

            TimeUnit.SECONDS.sleep(1);
        }
        log.info("生产者停止生产!!!");
    }

    /**
     * 消费者消费
     *
     * @throws InterruptedException
     */
    public void consumer() throws InterruptedException {
    
    
        while (flag) {
    
    
            Integer poll = blockingQueue.poll(1, TimeUnit.SECONDS);
            if (StringUtils.isEmpty(poll)) {
    
    
                log.info("消费者1秒钟未收到资源,停止消费!!!");
            } else {
    
    
                log.info("消费 资源-{}", poll);
            }

            TimeUnit.SECONDS.sleep(1);
        }
        log.info("消费者停止消费!!!");
    }

    /**
     * 强制停止生产消费
     */
    public void stop() {
    
    
        this.flag = false;
    }
}

The difference between synchronized and lock

1. Original composition

  • Synchronized is a keyword that belongs to the JVM level
    • monitorenter (the bottom layer is completed through the monitor object. In fact, methods such as wait/notify also depend on the monitor object. Friends such as wait/notify can only be adjusted in the synchronization block or method.
    • monitorexit
  • Lock is a specific class (java.util.concurrent.locks.Lock) is a lock at the api level

2. How to use

  • Synchronized does not require the user to manually release the lock. When the synchronized code is executed, the system will automatically let the thread release the lock.
  • ReentrantLock requires the user to manually release the lock. If the lock is not actively released, it may lead to deadlock. Need Lock () and lunLock () method to cooperate with try/finally statement block to complete.

3. Whether the wait can be interrupted

  • synchronized cannot be interrupted unless an exception is thrown or normal operation completes
  • ReentrantLock can be interrupted
    • 1. Set the timeout method tryLock(Long timeout, TimeUnit unit)
    • 2. lockInterruptibly() is placed in the code block, and the interrupt() method can be called to interrupt

4. Is locking fair?

  • synchronized unfair lock
  • ReentrantLock can be both, the default is unfair lock, the constructor can pass boolean value, true is fair lock, false is unfair lock

5. The lock is bound to multiple conditions

  • synchronized no
  • ReentrantLock is used to wake up threads that need to be woken up in groups, and can be woken up precisely, instead of randomly waking up a thread or waking up all threads like synchronized.

Right now:

  • For creation, one is a keyword and the other is a class, which can specify whether the parameters are fair
  • For use, the lock needs to be unlocked manually, but can also be interrupted
  • Advantage: lock can wake up accurately

Require:

  • Print in the order of 5 AA, 10 BB, 15 CC
  • Repeat 10 times

Implementation: (mainly the precise wake-up of the lock)

public class P43 {
    
    
    public static void main(String[] args) {
    
    
        Print print = new Print();
        for (int i = 0; i < 10; i++) {
    
    
            new Thread(() -> {
    
    
                print.printA();
            }).start();

            new Thread(() -> {
    
    
                print.printB();
            }).start();

            new Thread(() -> {
    
    
                print.printC();
            }).start();
        }
    }
}

class Print {
    
    
    Lock lock = new ReentrantLock();
    Condition c1 = lock.newCondition();
    Condition c2 = lock.newCondition();
    Condition c3 = lock.newCondition();

    int note;

    void printA() {
    
    
        lock.lock();
        try {
    
    
            while (note != 0) {
    
    
                c1.await();
            }

            for (int i = 0; i < 5; i++) {
    
    
                System.out.println("AA");
            }

            note = 1;
            c2.signalAll();
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            lock.unlock();
        }
    }

    void printB() {
    
    
        lock.lock();
        try {
    
    
            while (note != 1) {
    
    
                c2.await();
            }

            for (int i = 0; i < 10; i++) {
    
    
                System.out.println("BB");
            }

            note = 2;
            c3.signalAll();
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            lock.unlock();
        }
    }

    void printC() {
    
    
        lock.lock();
        try {
    
    
            while (note != 2) {
    
    
                c3.await();
            }

            for (int i = 0; i < 15; i++) {
    
    
                System.out.println("CC");
            }

            note = 0;
            c1.signalAll();
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            lock.unlock();
        }
    }
}

Callable

public class P45 {
    
    
    public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    
        FutureTask<String> futureTask = new FutureTask<>(new CallableImpl());

        new Thread(futureTask).start();
        new Thread(futureTask).start();

        String res = futureTask.get();
        System.out.println(res);
    }
}

class CallableImpl implements Callable<String> {
    
    

    @Override
    public String call() throws Exception {
    
    
        System.out.println("CallableImpl.call...");
        return "ok";
    }
}

insert image description here


  • FutureTash implements Runnable: Callable can be passed
    insert image description here
    insert image description here
    insert image description here

  • Use new Thread to start the thread, and if you pass the same FutureTask object multiple times, it will only be calculated once, but if you pass different FutureTask objects, it will be calculated multiple times. For example, in the above example, two threads are clearly enabled, but only calculated once:
    insert image description here
    insert image description here

Thread Pool:

Summarized in another article: https://blog.csdn.net/m0_55155505/article/details/125191350

Deadlock:

  • Deadlock refers to a phenomenon in which two or more processes wait for each other due to competition for resources during the execution process. If there is no external force to dry the beach, they will not be able to advance.
  • If the system resources are sufficient and the resource requests of the process can be satisfied, the possibility of deadlock is very low; otherwise, it will fall into a deadlock due to competition for limited resources.

the code

  • The lock version simulates that people who become taller want to become thinner, and people who become thinner want to be taller, and each other wants to obtain each other's lock:
public class P55 {
    
    
    public static void main(String[] args) {
    
    
        Resource55 resource = new Resource55();

        new Thread(() -> {
    
    
            resource.toHigh();
        }).start();

        new Thread(() -> {
    
    
            resource.toThin();
        }).start();
    }
}

@Slf4j
class Resource55{
    
    
    Lock l1 = new ReentrantLock();
    Lock l2 = new ReentrantLock();

    void toHigh(){
    
    
        l1.lock();
        try{
    
    
            log.info("变高了...");
            TimeUnit.SECONDS.sleep(1);
            //变高的人想变瘦
            toThin();
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            l1.unlock();
        }
    }

    void toThin(){
    
    
        l2.lock();
        try{
    
    
            log.info("变瘦了...");
            TimeUnit.SECONDS.sleep(1);
            //变瘦的人想变高
            toHigh();
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            l2.unlock();
        }
    }
}

How do you know that this is a deadlock, not an error such as an infinite loop?

  • Command line input jps -lto view the corresponding pid
    insert image description here
  • enterjstack 对应pid
    insert image description here
  • At the end it says: Found 1 deadlock
    insert image description here
  • sync version:
    • Those who hold lockA are ready to acquire lockB, and those who hold lockB are ready to acquire lockA
@Slf4j
class RunnableImpl55 implements Runnable{
    
    
    private String lockA;
    private String lockB;

    public RunnableImpl55(String lockA, String lockB) {
    
    
        this.lockA = lockA;
        this.lockB = lockB;
    }

    private void lockA2lockB() throws InterruptedException {
    
    
        synchronized (lockA){
    
    
            log.info("已经持有锁:{},想要获取锁:{}",lockA,lockB);
            TimeUnit.SECONDS.sleep(1);
            lockB2lockA();
        }
    }
    private void lockB2lockA() throws InterruptedException {
    
    
        synchronized (lockB){
    
    
            log.info("已经持有锁:{},想要获取锁:{}",lockB,lockA);
            TimeUnit.SECONDS.sleep(1);
            lockA2lockB();
        }
    }
    @Override
    public void run() {
    
    
        try {
    
    
            lockA2lockB();
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
    
    
        String lockA = "lockA";
        String lockB = "lockB";

        new Thread(new RunnableImpl55(lockA,lockB)).start();
        new Thread(new RunnableImpl55(lockB,lockA)).start();
    }
}

insert image description here

JVM

Another article: https://blog.csdn.net/m0_55155505/article/details/125976760

class loader

Class loader classification (according to the java virtual machine specification)

  • Bootstrap ClassLoader
  • Custom class loader (User-Defined ClassLoader, except the bootstrap class loader)

The loader that comes with the virtual machine:
1. Bootstrap class loader: Bootstrap ClassLoader

  • c/c++ language implementation, nested inside jvm
  • Used to load the java core library: JAVA_HOME/jre/lib/rt.jar, resources.jar or the content under the sun.boot.class.path path, used to provide the classes needed by jvm itself
  • Does not inherit from java.lang.ClassLoader, no parent loader
  • Mount extension classes and application class loaders, and designate them as their parent class loaders
  • For security reasons, only classes whose package names start with java, javax, sun, etc. are loaded
    //通过Launcher.getBootstrapClassPath().getURLs()获取:
    file:/C:/Java/jdk1.8.0_271/jre/lib/resources.jar
    file:/C:/Java/jdk1.8.0_271/jre/lib/rt.jar
    file:/C:/Java/jdk1.8.0_271/jre/lib/sunrsasign.jar
    file:/C:/Java/jdk1.8.0_271/jre/lib/jsse.jar
    file:/C:/Java/jdk1.8.0_271/jre/lib/jce.jar
    file:/C:/Java/jdk1.8.0_271/jre/lib/charsets.jar
    file:/C:/Java/jdk1.8.0_271/jre/lib/jfr.jar
    file:/C:/Java/jdk1.8.0_271/jre/classes
    

2. Extended class loader

  • written in java language
  • Derived from ClassLoader
  • The parent class is the startup class loader
  • The class library is loaded from the directory specified by the java.ext.dirs system property, or the class library is loaded from the jre/lib/ext subdirectory (extension directory) of the jdk installation directory. If the jar created by the user is placed in this directory, it will be automatically loaded by the extended class loader
    //通过System.getProperty("java.ext.dirs")获取:
    C:\Java\jdk1.8.0_271\jre\lib\ext;C:\Windows\Sun\Java\lib\ext
    

3. Application class loader (system class loader, appClassLoader)

  • Written in java language, implemented by sun.misc.Launcher$AppclassLoader
  • Derived from the ClassLoader class
  • The parent class loader is the extended class class loader
  • It is responsible for the class library under the path specified by the loader environment variable classpath (custom class) or the system property java.class.path
  • This class loader is the default class loader in the program. Generally speaking, the classes of java applications are loaded by it
  • The class loader of this class can be obtained through the ClassLoaser#getSystemClassLoader() method

Parental Delegation Mechanism

The java virtual machine uses an on-demand loading method for class files, which means that when the class needs to be used, the class file of the class will be loaded into the memory to generate a class object, and when the class file of a certain class is loaded, The java virtual machine adopts the parent delegation mode , that is, the request is handed over to the parent class for processing. It is a task delegation mode


Principle: (After delegating up to the top level, try to load in turn: bootstrap class loader -> extended class loader -> system class loader)

  • If a class loader receives a class loading request, it will not load it by itself first, but delegate it to its parent class loader for loading
  • If the parent class loader still has a parent class, it will continue to delegate upwards and finally reach the bootstrap class loader
  • If the parent class can complete the class loading, it returns successfully, otherwise, the child class loader tries to load the class

example:

  • Customize a java.lang.String class
    insert image description here
  • try to create object
    insert image description here
  • It is found that the String class is not a custom one (the "custom String..." is not output)

advantage:

  • prevent double loading
  • Prevent the core API from being tampered with (for example: you cannot customize classes under java.lang, otherwise java.lang.SecurityException will be reported)

Sandbox Security Mechanism

The original text is more detailed: https://blog.csdn.net/qq_30336433/article/details/83268945
What is it?

  • The core of the Java security model is the Java sandbox (sandbox). What is a sandbox? A sandbox is an environment that restricts the execution of programs.
  • The sandbox mechanism is to limit the Java code to the specific operating scope of the virtual machine (JVM), and, 严格限制代码对本地系统资源访问through such measures 对代码的有效隔离, it is guaranteed to prevent damage to the local system.
  • The sandbox mainly restricts access to system resources, so what do system resources include? - CPU, memory, file system, network. Different levels of sandboxes may also have different restrictions on access to these resources.
  • Sandboxes can be specified for all Java programs to run, and security policies can be customized.

Core components:

  • Bytecode verifier (bytecode verifier): to ensure that Java class files follow the Java language specification. This can help Java programs achieve memory protection. But not all class files will undergo bytecode verification, such as core classes.
  • Class loader (class loader): The class loader works on the Java sandbox in three ways
    • It prevents malicious code from interfering with benign code;
    • It guards the boundaries of trusted libraries;
    • It puts the code into protection domains, which determines what the code can do.

    The virtual machine provides different namespaces for classes loaded by different class loaders. The namespace consists of a series of unique names. Each loaded class will have a name. This namespace is created by the Java virtual machine for each maintained by the class loader, they are not even visible to each other.


    The mechanism used by the class loader is the parent delegation model.

    • Starting from the class loader of the innermost JVM, the outer malicious class with the same name cannot be loaded and cannot be used;
    • Since the access domain is strictly distinguished by the package, the malicious class in the outer layer cannot gain access to the inner class through the built-in code, and the damaged code will naturally fail to take effect.
  • Access controller (access controller): The access controller can control the access authority of the core API to the operating system, and the policy setting of this control can be specified by the user.
  • Security manager (security manager): is the main interface between the core API and the operating system. To implement permission control, it has a higher priority than the access controller.
  • Security package (security package): classes under java.security and classes under the extension package, allowing users to add new security features to their applications

    Including: security provider, message digest, digital signature, encryption, authentication


As shown in the figure: customize the java.lang.String class, write the main method from it and try to run it.
insert image description here
The result of the operation:
insert image description here
it means that we are not using our custom String.

in conclusion:

  • 1. Customize the String class, and use the boot class loader to load it when loading

    2. The boot class loader will load the String class in the file that comes with jdk when loading

    3. There is no String in the built-in jdk main method, resulting in an error

  • This can ensure the protection of the java core source code, which is the sandbox security mechanism

Garbage collection (how to judge garbage, what is GC Root)

What is garbage: unused space in memory
How to judge:

  • Reference counting (understand)
    insert image description here

  • Enumerate root nodes for reachability analysis (root search path)

    • From GC Root, reachable is not garbage
    • What does GC Root include: (Familiar with the first four)
      • Objects referenced in the virtual machine stack

        For example: parameters, local variables, etc. used in the method called by each thread.

      • Objects referenced by JNI (commonly referred to as native methods) in the native method stack
      • Objects referenced by class static properties in the method area

        For example: reference type static variable of Java class

      • Objects referenced by constants in the method area

        For example: references in the string constant pool (stringTable), constants modified by static final

      • All objects held by synchronized locks
      • References inside the Java virtual machine.

        Class objects corresponding to basic data types, some resident exception objects (such as: NullPointerException, outofMemoryError), system class loader.

      • JMXBean that reflects the internal situation of the java virtual machine, callbacks registered in JVMTI, local code cache, etc.

How to Clear: Clear Phase Algorithm

  • copy algorithm
  • mark clear
  • markup

JVM parameters

Divided into the following three parameters:

  • Standard parameters
    insert image description here

  • X parameters (understand)insert image description here

  • XX parameters (including Xms, Xmx, similar to an alias)
    insert image description here


XX parameters:

  • Boolean type, -XX:+ (or -) parameters, for example: -XX:+PrintGCDetails
    check whether it is enabled:
    insert image description here
    add running parameters:
    insert image description here
    check again:
    insert image description here

  • KV setting value type: -XX: attribute key=attribute value, for example -XX:MetaspaceSize=11111111
    view:
    insert image description here
    setting:
    insert image description here
    check again: (inconsistency with the setting should be dynamically adjusted)
    insert image description here


jinfo

  • usage:
    insert image description here

Notice:

  • -Xms, -Xmx belong to XX parameters, similar to aliasing!
  • -Xms: Initial heap space memory (1/64 of physical memory by default)
  • -Xmx: Maximum heap space memory (1/4 of physical memory by default)
    insert image description here

View the initial default parameters:

  • java -XX:+PrintFlagsInitialInitialization parameters
    insert image description here

  • java -XX:+PrintFlagsFinalIt is used to view the modified parameters ( =means default, :=means modified, modified value; but jdk17 does not use =and :=, but adds a parameter (default, command line) at the end to indicate whether it is the default value or modified value)

    • jdk 8:
      insert image description here
    • jdk17:
      insert image description here
  • java -XX:+PrintCommandLineFlagsprint command line arguments
    insert image description here


Use the code to view:

public class P65 {
    
    
    public static void main(String[] args) {
    
    
        long totalMemory = Runtime.getRuntime().totalMemory();
        long maxMemory = Runtime.getRuntime().maxMemory();

        System.out.println("totalMemory = " + totalMemory / 1024 / 1024 + " MB");
        System.out.println("64 * totalMemory = " + 64 * totalMemory / 1024 / 1024 + " MB");
        System.out.println("maxMemory = " + maxMemory / 1024 / 1024 + " MB");
        System.out.println("4 * maxMemory = " + 4 * maxMemory / 1024 / 1024 + " MB");
    }
}

insert image description here

insert image description here


What parameters have you configured?
-XX:+PrintFlagsInitial: View the default initial values ​​of all parameters (examples are at the end of this section)
-XX:+PrintFlagsFinal: View the final values ​​of all parameters (there may be modifications, which are no longer the initial values; =indicate initial values, :=indicate modified, modified values)
-Xms: Initial heap space memory (1/64 of physical memory by default), equivalent to -XX:InitialHeapSize
-Xmx: maximum heap space memory (1/4 of physical memory by default), equivalent to -XX:MaxHeapSize
-Xss: setting the size of a single thread stack, generally 512k~ 1024k; equivalent to -XX:ThreadStackSize
-Xmn: set the size of the new generation. (Initial value and maximum value)
-XX:MetaspaceSize: Set the size of the metaspace
-XX:NewRatio: Configure the proportion of the new generation and the old generation in the heap structure
-XX:SurvivorRatio: Set the ratio of Eden and so/s1 space in the new generation
-XX:MaxTenuringThreshold: Set the maximum age of the new generation garbage
-XX:+PrintGCDetails: Output detailed Gc processing log
Print gc brief information: 1, -XX:+PrintGC2, -verbose:gc
-XX:HandlePromotionFailure: whether to set space allocation guarantee

For GC parameters, please refer to: PrintGCDetails output parameters and explanations

Strong references, soft references, weak references, phantom references

insert image description here

For details, please refer to: https://blog.csdn.net/m0_55155505/article/details/125976760#_1355

Strong reference: the default is strong reference, I would rather OOM than recycle

public class P72 {
    
    
    public static void main(String[] args) {
    
    
        Object obj = new Object();

        try {
    
    
            toOOM();
        } catch (OutOfMemoryError e) {
    
    
        	e.printStackTrace();
            System.out.println(obj);
        }
    }

    public static void toOOM() throws OutOfMemoryError {
    
    
        // 设置参数 -Xms10m -Xmx10m
        byte[] bytes = new byte[10 * 1024 * 1024];
    }
}

insert image description here

Soft reference: if the memory is not enough to recycle, if the memory is insufficient, it will be reclaimed

public class P73 {
    
    
    public static void main(String[] args) {
    
    
        Object obj = new Object();
        SoftReference softReference = new SoftReference(obj);
        obj = null;

        System.gc();

        System.out.println("第一次gc后:");
        System.out.println("obj = " + obj);
        System.out.println("softReference.get() = " + softReference.get());


        try {
    
    
            toOOM();
        } catch (OutOfMemoryError e) {
    
    
            e.printStackTrace();
            System.out.println("OOM之后:");
            System.out.println("obj = " + obj);
            System.out.println("softReference.get() = " + softReference.get());
        }
    }

    public static void toOOM() throws OutOfMemoryError {
    
    
        // -Xms10m -Xmx10m
        byte[] bytes = new byte[10 * 1024 * 1024];
    }
}

insert image description here

Weak reference: as long as gc occurs, it will be recycled

  • example
public class P74 {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        Object obj = new Object();
        WeakReference weakReference = new WeakReference(obj);
        obj = null;

        System.gc();

        TimeUnit.SECONDS.sleep(1);
        
        System.out.println("obj = " + obj);
        System.out.println("weakReference.get() = " + weakReference.get());
    }
}

insert image description here

  • ReferenceQueue: After being cleaned by gc, put it into the queue:
public class P78 {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        Object obj = new Object();
        ReferenceQueue referenceQueue = new ReferenceQueue();

        WeakReference weakReference = new WeakReference(obj, referenceQueue);

        System.out.println("=====gc前:=====");
        System.out.println("obj = " + obj);
        System.out.println("weakReference.get() = " + weakReference.get());
        System.out.println("referenceQueue.poll() = " + referenceQueue.poll());

        obj = null;

        System.gc();
        TimeUnit.SECONDS.sleep(1);

        System.out.println("=====gc后:=====");
        System.out.println("obj = " + obj);
        System.out.println("weakReference.get() = " + weakReference.get());
        System.out.println("referenceQueue.poll() = " + referenceQueue.poll());
    }
}

insert image description here

phantom reference:

  • Saved by the reference queue before being recycled
    insert image description here
  • code:
public class P79 {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        Object obj = new Object();
        ReferenceQueue referenceQueue = new ReferenceQueue();

        PhantomReference phantomReference = new PhantomReference(obj, referenceQueue);

        System.out.println("=====gc前:=====");
        System.out.println("obj = " + obj);
        System.out.println("phantomReference.get() = " + phantomReference.get());
        System.out.println("referenceQueue.poll() = " + referenceQueue.poll());

        obj = null;

        System.gc();
        TimeUnit.SECONDS.sleep(1);

        System.out.println("=====gc后:=====");
        System.out.println("obj = " + obj);
        System.out.println("phantomReference.get() = " + phantomReference.get());
        System.out.println("referenceQueue.poll() = " + referenceQueue.poll());
    }
}

insert image description here

Applications of soft references and weak references:
insert image description here


WeakHashMap:
insert image description here

public class P76 {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        WeakHashMap<String, Object> weakHashMap = new WeakHashMap<>();
        String key = new String("a");
        Object val = new Object();

        weakHashMap.put(key, val);

        key = null;

        System.gc();

        TimeUnit.SECONDS.sleep(1);

        System.out.println(weakHashMap); //{}
    }
}

SOFE (StackOverflowError)

java.lang.StackOverflowError

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

insert image description here


OOM

Java heap space

The heap of object creation can't fit! ! !

public class P82 {
    
    
    public static void main(String[] args) {
    
    
        // -Xms10m -Xmx10m
        byte[] bytes = new byte[10 * 1024 * 1024];
    }
}

insert image description here


GC overhead limit exceeded

insert image description here

  • OutOfMemroyError will be thrown during GC 回收时间过长.
  • The definition of too long is that more than 98% of the time is spent on GC and less than 2% of the heap memory is reclaimed. It will only be thrown in extreme cases where less than 2% of the GC has been recovered for many times in a row.
  • What happens if the GC overhead limit error is not thrown? That is, the memory cleared by the GC will soon fill up again, forcing the GC to execute again, thus forming a vicious circle. The CPU usage is always 100%, and the GC but no results
public class P83 {
    
    
    public static void main(String[] args) {
    
    
        // -Xms10m -Xmx10m -XX:+PrintGCDetails

        List<String> list = new ArrayList<>();

        int i = 0;
        try {
    
    
            while (true) {
    
    
                list.add(String.valueOf(i++).intern());
            }
        } catch (Throwable e) {
    
    
            e.printStackTrace();
        }
    }
}

insert image description here
Look at the garbage collection situation: basically no garbage will be received! !
insert image description here

Direct buffer memory

Caused by:

  • Writing NIO programs often uses ByteBuffer to read or write data, which is an I/O method based on channels (Channel) and buffers (Buffer).

  • It can use the Native function library to directly allocate off-heap memory, and then use a DirectByteBuffer object stored in the Java heap as a reference to this memory. This can significantly improve performance in some scenarios, because it avoids copying data back and forth between the ava heap and the Native heap.

    • ByteBuffer.allocate(capability) The first method is to allocate JVM heap memory, which belongs to the jurisdiction of GC, and the speed is relatively slow due to the need to copy

    • ByteBuffer.allocteDirect(capability) The second way is to allocate OS local memory, which is not under the jurisdiction of GC, and the speed is relatively fast because no memory copy is required.

  • But if the local memory is continuously allocated and the heap memory is rarely used, then the JVM does not need to perform GC, and the DirectByteBuffer objects will not be recycled.

  • At this time, the heap memory is sufficient, but the local memory may have been used up. If you try to allocate local memory again, an OutOfMemoryError will appear, and the program will crash directly.

example:

public class P84 {
    
    
    public static void main(String[] args) {
    
    
        // -Xms10m -Xmx10m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m
        // 设置本地内存5M,但申请6M的空间
        
        long maxDirectMemory = VM.maxDirectMemory();
        System.out.println("maxDirectMemory = " + maxDirectMemory / (double) 1024 / 1024 + " MB");

        ByteBuffer allocate = ByteBuffer.allocateDirect(6 * 1024 * 1024);
    }
}

insert image description here


unable to create new thread

Too many threads created
insert image description here


  • code:
    insert image description here
  • result:
    insert image description here

How does Linux adjust the maximum number of threads?

  • Check:ulimit -u
    insert image description here

  • Modification: vim /etc/security/limits.d/20-nproc.conf;* indicates users other than root, as you can see here, the root user has no restrictions, and other users are 4096 (different server configurations are different, and it may also be 1024)
    insert image description here

Metaspace

public class P87 {
    
    
    public static void main(String[] args) {
    
    
        // -XX:MaxMetaspaceSize=10m
        int i = 0;
        try {
    
    
            for (; ; i++) {
    
    
                Enhancer enhancer = new Enhancer();
                enhancer.setSuperclass(P87.class);
                // 默认是true,表示是同一个class;设为false,每次在方法区产生新的class
                enhancer.setUseCache(false);
                enhancer.setCallback(new MethodInterceptor() {
    
    
                    @Override
                    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
    
    
                        return methodProxy.invoke(o, objects);
                    }
                });
                enhancer.create();
            }
        } catch (Throwable e) {
    
    
            e.printStackTrace();
            System.out.println("次数:" + i);
        }
    }
}

insert image description here

garbage collector

insert image description here

Four types of garbage collectors:

  • Serial: serial; designed for a single-threaded environment, only one thread is used for garbage collection, and all user threads will be stopped. Not suitable for server environment (such as Double Eleven, stop and try).
  • Parallel: Parallel; multiple threads perform garbage collection at the same time, and user threads stop. It is suitable for weak interaction scenarios such as scientific computing.
  • CMS: concurrent mark sweep, concurrent mark sweep; user threads and garbage collection threads can be executed at the same time (not necessarily in parallel, but may also be concurrent). Internet companies often use it, and it is suitable for scenarios that require response time. STW (stop the world) exists, but for a short time
  • G1: Partition garbage collection, each district can be Eden, Survivor, Old, the larger object can be placed in H, and the partition is cleaned
    insert image description here

For details, please go to: https://blog.csdn.net/m0_55155505/article/details/125976760#_1511


Check out the default garbage collector:java -XX:+PrintCommandLineFlags -version

insert image description here


The garbage collector provided by the JVM:

insert image description here
In the case of jdk 8, check the usage of each garbage collector:

C:\Users\AikeTech>jps
7092 P91
8648
6828 Launcher
8380 Jps


C:\Users\AikeTech>jinfo -flag UseSerialGC  7092
-XX:-UseSerialGC

C:\Users\AikeTech>jinfo -flag UseConcMarkSweepGC  7092
-XX:-UseConcMarkSweepGC

C:\Users\AikeTech>jinfo -flag UseParNewGC  7092
-XX:-UseParNewGC

C:\Users\AikeTech>jinfo -flag UseParallelGC  7092
-XX:+UseParallelGC

C:\Users\AikeTech>jinfo -flag UseParallelOldGC  7092
-XX:+UseParallelOldGC

C:\Users\AikeTech>jinfo -flag UseG1GC 7092
-XX:-UseG1GC

Seven garbage collectors:

  • Serial Old is no longer there, but it existed!
    insert image description here
    insert image description here

DerNew(Serial)、Tenured(Serial old)、ParNew、PSYoungGen、ParOldGen

insert image description here

  • Such as: DefNew, default new generation, Tenured in the figure below
    insert image description here

Server and Client mode:

insert image description here

  • Scope of application: You only need to master the Server mode, and the Client mode is basically useless
  • operating system:
    • 32-bit Windows operating system, regardless of the hardware, uses the Client's JVM mode by default
    • 32-bit other operating systems, 2G memory and more than 2 CPUs at the same time use the Server mode, and the configuration is lower than the Client mode
    • 64-bit only server mode

switch between different garbage collectors

Serial

insert image description here

  • XX:+UseSerialGC, used in conjunction with Serial Old by default
  • code:
public class P94 {
    
    
    public static void main(String[] args) {
    
    
        // -Xms10m -Xmx10m -XX:+UseSerialGC -XX:+PrintCommandLineFlags -XX:+PrintGCDetails
        byte[] bytes = new byte[10 * 1024 * 1024];
    }
}

insert image description here

ParNew

insert image description here
insert image description here

  • -XX:+UseParNewGC, and Serial Old by default, but Serial Old will be discarded
  • code:
public class P95 {
    
    
    public static void main(String[] args) {
    
    
        // -Xms10m -Xmx10m -XX:+UseParNewGC -XX:+PrintCommandLineFlags -XX:+PrintGCDetails
        byte[] bytes = new byte[10 * 1024 * 1024];
    }
}

insert image description here


Parallel

insert image description hereinsert image description here
insert image description here

  • -XX:+UseParallelGC, the old generation defaults to ParallelOldGC, and can activate each other
  • code:
public class P96 {
    
    
    public static void main(String[] args) {
    
    
        // -Xms10m -Xmx10m -XX:+UseParallelGC -XX:+PrintCommandLineFlags -XX:+PrintGCDetails
        byte[] bytes = new byte[10 * 1024 * 1024];
    }
}

insert image description here

CMS

insert image description here
insert image description here
insert image description here

  • -XX:+UseConcMarkSweepGC, the young generation ParNew, there will be Serial Old for the bottom line

  • process:

    • Initial marking: STW, marking objects directly associated with GC root, short time
    • Concurrent marking: together with user threads, mark all objects associated with the GC root
    • Remark: STW, fix objects changed during just concurrent mark
    • Concurrent cleanup: together with user threads, clean up unmarked objects
  • Advantages: Time-consuming marking and cleaning are executed together with user threads, and the overall pause time is less.

  • shortcoming:

    • High pressure on the CPUinsert image description here
    • Mark-and-sweep algorithm, massive memory fragmentation
      insert image description here
  • Code: (The effect is not very good, there is a good picture at the beginning of this section)

public class P98 {
    
    
    public static void main(String[] args) {
    
    
        // -Xms10m -Xmx10m -XX:+UseConcMarkSweepGC -XX:+PrintCommandLineFlags -XX:+PrintGCDetails
        byte[] bytes = new byte[10 * 1024 * 1024];
    }
}

insert image description here

Serial Old

insert image description here

  • It is optimized in jdk8, such as configuration: -XX:+UseSerialOldGC:insert image description here
    insert image description here
G1

insert image description here
insert image description here
insert image description here

  • -XX:+UseG1GC
  • High throughput, low pause time, each block can act as a different area (Eden, Survivor, Old) at different times
  • code:
public class P101 {
    
    
    public static void main(String[] args) {
    
    
        // -Xms10m -Xmx10m -XX:+UseG1GC -XX:+PrintCommandLineFlags -XX:+PrintGCDetails
        byte[] bytes = new byte[10 * 1024 * 1024];
    }
}
  • result:

result -1

result -2

Features:

  • 1: G1 can make full use of the hardware advantages of multi-CPU and multi-core environment, and shorten the STW as much as possible.
  • 2: G1 adopts mark-organization algorithm as a whole, and partly uses copy algorithm, which will not generate memory fragmentation.
  • 3: From a macro perspective, there is no longer a distinction between the young generation and the old generation in G1. Divide the memory into multiple independent sub-regions (Regions), which can be roughly understood as a Go board.
  • 4: The G1 collector says that the entire memory area is mixed together, but it still needs to distinguish between the young generation and the old generation in a small area, retaining the new generation and the old generation, but they are no longer physically isolated Yes, but a collection of some Regions and does not require the Regions to be continuous, that is to say, different GC methods will still be used to process different regions.
  • 5: Although G1 is also a generational collector, there is no physical difference between the young generation and the old generation in the entire memory partition, and there is no need for a completely independent survivor (to space) heap for copy preparation. G1 only has a logical concept of generation, or each partition may switch back and forth between different generations with the operation of G1;

Regionalized memory slicing Region

  • Regionalized memory slicing Region, as a whole, becomes a series of discontinuous memory areas, avoiding GC operations of the entire memory area.
  • The core idea is to divide the entire heap memory area into sub-regions of the same size (Region), and the size of these sub-regions will be automatically set when the JVM starts.
  • In terms of the use of the heap, G1 does not require that the storage of objects must be physically continuous as long as it is logically continuous. switch between. The partition size (1MB~32MB, must be a power of 2) can be specified by parameter -XX:G1HeapRegionSize=n at startup, and the whole heap is divided into 2048 partitions by default.
  • The size ranges from 1MB to 32MB, and a maximum of 2048 areas can be set, that is, the maximum memory that can be supported is: 32MB*2048=65536MB=64G memory
  • Some of these regions include the new generation, and the garbage collection of the new generation still uses the method of suspending all application threads, and copies the surviving objects to the old generation or Survivor space.
  • Some of these regions contain the old generation, and the G1 collector completes the cleanup by copying objects from one region to another. This means that during normal processing, G1 completes the heap compaction (at least part of the heap compaction), so that there will be no CMS memory fragmentation problems.
  • In G1, there is also a special area called Humongous (huge) area.
    If an object occupies more than 50% of the partition capacity, the G1 collector considers it a huge object. These huge objects are directly allocated in the old generation by default, but if it is a short-lived huge object, it will have a negative impact on the garbage collector. To solve this problem, G1 divides a Humongous area, which is used to store huge objects. If a huge object cannot fit in one H area, then Gi will look for continuous H partitions to store it. In order to find continuous H areas, sometimes Full GC has to be started.

Recycling step: small area collection + formation of contiguous memory blocks

insert image description here
insert image description here
insert image description here

Parameter configuration: You can configure the maximum heap memory and maximum pause time
insert image description here
insert image description here

Compared with CMS:

  • G1 does not generate memory fragmentation
  • GC pause times can be precisely controlled. Divide the entire heap into fixed-size areas, and each time the area with the most garbage is collected according to the allowed pause time

how to choose:

insert image description here

Summarize:

insert image description here


Linux commands

insert image description here

top、(uptime)

  • topOrder:
    insert image description here

  • You can view memory (MEM), cpu, etc. usage

  • There are three values ​​behind the load average, indicating the load values ​​of the system in 1 minute, 5 minutes, and 15 minutes; if the average value of the three values ​​is greater than 0.6, it means that the system load is heavy. The load in the figure: (1.51+0.91+0.42)/3=0.95, indicating that the load is heavy

  • Press 1 all the time, you can see the situation of each cpu:
    insert image description here

  • uptimeThe command is a simplified version of top: it can be used to see the load
    insert image description here

vmstat、(mpstat、pidstat)

  • vmstat:
    insert image description here

  • Mainly used to check cpu

  • vmstat -n 2 3Indicates sampling every two seconds, a total of three sampling

  • processes

    • r: the number of processes running and waiting for the CPU time slice, in principle: the running queue of a CPU with 1 core should not exceed 2, and the running queue of the entire system should not exceed twice the total number of cores, otherwise it means that the system is under too much pressure│
    • b: The number of processes waiting for resources, such as waiting for disk I/0, network I/0, etc.
  • cpu (the first three are important)

    • us: The percentage of CPU time consumed by the user process. If the us value is high, the user process consumes more CPUI time. If it is greater than 50% for a long time, optimize the program;
    • sy: the percentage of CPU time consumed by the kernel process;
    • The reference value of us + sy is 80%. If us + sy is greater than 80%, it indicates that there may be insufficient CPU.
    • id (idle): The percentage of the CPU that is idle.
    • wa: The percentage of CPU time the system is waiting for IO.
    • st: Percentage of CPU time stolen from a virtual machine
  • View all cpu core information: mpstat -P ALL 2, where 2 means sampling every 2 seconds
    insert image description here

  • The decomposition information of the cpu usage used by each process: pidstat -u 2 -p 进程id, where 2 means sampling every two seconds

    [root@yy ljy]# ps -ef | grep 'java -jar'
    root     23534     1  0 Jun17 ?        01:12:01 java -jar wechat-0.0.1-SNAPSHOT.jar
    root     30815 30045  0 20:46 pts/1    00:00:00 grep --color=auto java -jar
    [root@yy ljy]# pidstat -u 2 -p 23534
    Linux 3.10.0-1160.49.1.el7.x86_64 (yy)  08/17/2022      _x86_64_        (1 CPU)
    
    08:47:01 PM   UID       PID    %usr %system  %guest    %CPU   CPU  Command
    08:47:03 PM     0     23534    0.51    0.00    0.00    0.51     0  java
    08:47:05 PM     0     23534    0.00    0.00    0.00    0.00     0  java
    08:47:07 PM     0     23534    0.00    0.00    0.00    0.00     0  java
    08:47:09 PM     0     23534    0.00    0.00    0.00    0.00     0  java
    08:47:11 PM     0     23534    0.00    0.00    0.00    0.00     0  java
    

free、(pidstat )

insert image description here
Experience:

  • Application available memory/system physical memory > 70% sufficient memory

  • Application available memory/system physical memory < 20% Insufficient memory, need to increase memory

  • 20% < Application available memory/system physical memory < 70% The memory is basically enough

  • pidstat -p 进程号 -r 采样间隔秒数
    insert image description here


df

  • View remaining disk space
    insert image description here

iostat

insert image description here

Disk block device distribution

  • rkB/s: kB of read data per second;
  • wkB/s: the amount of data written in kB per second;
  • svctm: the average service time of I/O requests, in milliseconds;
  • await: The average waiting time of l/O requests, in milliseconds; the smaller the value, the better the performance
  • util: What percentage of a second is used for I/O operations. When it is close to 100%, it means that the disk bandwidth is full, and it is necessary to optimize the program or increase the disk;
  • rkB/s and wkB/s will have different values ​​according to different system applications, but there are rules to follow: long-term, large-scale data reading and writing are definitely abnormal, and program reading needs to be optimized.
  • The value of svctm is very close to the value of await, which means that there is almost no IO waiting and the disk performance is good.
    If the value of await is much higher than the value of svctm, it means that the IO queue waits too long, and the program needs to be optimized or a faster disk should be replaced.

Other commands:

  • pidstat -d 采样间隔秒数 -p 进程号
    insert image description here

ifstat

  • If there is no such command, install the command:

    wget http://gael.roualland.free.fr/ifstat/ifstat-1.1.tar.gz
    tar xzvf ifstat-1.1.tar.gz
    cd ifstat-1.1
    ./configure
    make
    make install
    
  • use
    insert image description here


CPU usage is too high location analysis

insert image description here

  • top
    insert image description here

  • ps -ef or jps
    insert image description here

    grep -v grep: Find lines that do not contain grep

  • Check the specific thread:ps -mp 进程id -o THREAD,tid,time
    insert image description here

    Parameter explanation:
    -m: display all threads
    -p pid: the time the process uses the cpu
    -o: user-defined format after this parameter

  • The thread id is converted to hexadecimal:printf "%x\n” 有问题的线程ID
    insert image description here

    Or use a calculator: but pay attention to converting the letters to lowercase at the end:
    insert image description here

  • jstack 进程ID | grep tid(16进制线程ID小写英文) -A60, where A60 means to print the first 60 lines
    insert image description here

  • Go to the tenth line:

Guess you like

Origin blog.csdn.net/m0_55155505/article/details/126134031
Recommended