It’s been a long time, and finally it’s over. This study guide series will be divided into three modules
- First Java knowledge summary
- Second Android basic knowledge summary
- Third Android advanced knowledge summary
The subsequent NDK, cross-platform, underlying source code and other technologies will also take time to sort out the knowledge points for you. If you like it, I hope you will pay attention to it and give me a thumbs up. If you have contact information about your home page, please find me to get the PDF version of the study guide.
Without further ado, let the body begin.
JVM
JVM Workflow
Runtime Data Area
Program counter
The Program Counter Register is a small memory space that can be thought of as a line number indicator of the bytecode being executed by the current thread.
The bytecode interpreter works by changing the value of this counter to select the next bytecode instruction to be executed. Branch, loop, jump, exception handling, thread recovery and other basic functions need to be completed by this counter.
Because multithreading in the Java VIRTUAL machine is implemented by the way threads alternate and allocate processor execution time, at any given moment, one processor (or kernel for multi-core processors) will execute instructions in only one thread.
Therefore, in order to recover to the correct execution position after thread switching, each thread needs to have an independent program counter, which is not affected by each other and stored independently. We call this kind of memory area “thread private” memory.
- If the thread is executing a Java method, this counter records the address of the virtual machine bytecode instruction being executed.
- If the thread is executing a Native method, this counter is null (Undefined).
This memory region is the only one where the Java Virtual Machine specification does not specify any OutOfMemoryError cases.
Java virtual machine stack
The Java Virtual Machine Stack is also thread-private and has the same lifetime as a thread. Virtual machine Stack describes the memory model of Java method execution, and each method execution creates a Stack Frame for storing messages such as local variable table, operand Stack, dynamic link, method exit, etc. The process of each method from invocation to completion corresponds to the process of a stack frame being pushed into and out of the virtual machine stack.
The local variable table contains the basic data types known by the compiler (Boolean, byte, CHAR, short, int, float, long, double), object references (reference type, which is not the same as the object itself, may be a reference pointer to the object’s starting address. They can also point to a handle representing an object or other location associated with that object) and the returnAddress type (which points to the address of a bytecode instruction).
64-bit long and double data occupy two local variable Spaces (slots), and the rest occupy only one. The memory space required for the local variable table is allocated at compile time. When entering a method, how much local variable space the method needs to allocate in the frame is completely determined, and the size of the local variable table does not change during the method run.
In the Java Virtual Machine specification, two exception states are specified for this area:
- StackOverflowError is thrown if the stack depth requested by the thread is greater than the depth allowed by the virtual machine.
- If the virtual stack can be dynamically extended (as most Java virtual machines currently do, although the Java Virtual Machine specification also allows fixed-length virtual stacks), an OutOfMemoryError will be thrown if sufficient memory cannot be allocated during the extension.
Local method stack
The Native Method Stack is very similar to the virtual machine Stack. The difference is that the virtual machine Stack performs Java methods (that is, bytecode) services for the virtual machine, while the Native Method Stack serves the Native methods used by the virtual machine.
The virtual machine specification does not mandate the language, usage, or data structure of methods in the local method stack, so specific virtual machines are free to implement it. There are even virtual machines (for example, the Sun HotSpot VIRTUAL machine) that simply combine the virtual machine stack with the local method stack. Like the virtual stack, the local method stack area throws StackOverflowError and OutOfMemoryError exceptions.
The Java heap
For most applications, the Java Heap is the largest chunk of memory managed by the Java virtual machine. The Java heap is an area of memory that is shared by all threads and is created when the virtual machine is started. The sole purpose of this memory area is to hold object instances, and almost all object instances are allocated memory here.
The Java heap is the main area of garbage collector management. From the point of view of memory collection, because the collector now basically adopts generational collection algorithm, the Java heap can also be subdivided into: new generation and old generation; More detailed are Eden space, From Survivor space, To Survivor space, etc.
From the perspective of memory Allocation, the Java heap shared by threads may have multiple Thread private Allocation buffers (TLabs). However, no matter how to partition, it has nothing to do with the storage content, no matter which area, the storage is still the object instance, the purpose of further partition is to better reclaim memory, or faster allocation of memory.
Methods area
The Method Area, like the Java heap, is an Area of memory shared by threads that stores information about classes loaded by the virtual machine, constants, static variables, code compiled by the just-in-time compiler, and so on.
The Runtime Constant Pool is part of the method area. The Constant Pool Table is used to store various literals and symbolic references generated by the compiler. This part of the Constant Table is stored in the runtime Constant Pool when the Class is loaded into the method area.
Since the runtime constant pool is part of the method area and is naturally limited by the method area memory, OutOfMemoryError is thrown when the constant pool can no longer claim memory.
Method of instruction
instruction | instructions |
---|---|
invokeinterface | To invoke interface methods |
invokevirtual | Directives are used to invoke instance methods of objects |
invokestatic | To call class/static methods |
invokespecial | Use to call instance methods that require special handling, including instance initializer methods, private methods, and superclass methods |
Class loader
Class loader | instructions |
---|---|
BootstrapClassLoader | The Bootstrap class loader is responsible for loading JDK class files in rt.jar and is the parent of all class loaders. The Bootstrap class loader no parent class loader,. If you call the String class. GetClassLoader (), returns null, any code based on this will throw a NUllPointerException. The Bootstrap loader is called the initial class loader |
ExtClassLoader | Extension will delegate the request to load the class to its parent loader, Bootstrap, and then load the class from the jre/lib/ext directory or the directory defined by the java.ext.dirs system property if the load fails. The Extension loader is implemented by sun.misc.Launcher$ExtClassLoader |
AppClassLoader | The third default loader is the System class loader (also known as the Application class loader). It is responsible for loading some application-specific classes from the CLASspath environment variable, which is usually defined by the -classpath or -cp command-line options, or the CLASspath attribute of the Manifest in the JAR. The Application class loader is a child of the Extension class loader |
The working principle of | instructions |
---|---|
Trust mechanism | The loading task is delegated to the parent class loader. If it fails, the delegate task is passed down and loaded by its subclass loader to ensure the security of the Java core library |
Visibility mechanism | The subclass loader can see the classes loaded by the parent class loader, but not vice versa |
Single mechanism | A class that has been loaded by the parent loader cannot be loaded a second time by the quilt loader |
Garbage collection GC
Object survival judgment
- Reference counting
Each object has a reference count property. When a new reference is added, the count is increased by 1. When a reference is released, the count is decreased by 1. This method is simple and does not solve the problem of objects referring to each other circularly.
- Accessibility analysis
The path followed by a downward search from GC Roots is called the reference chain. When an object is not connected to GC Roots by any reference chain, the object is proved to be unavailable. Unreachable objects.
In the Java language, GC Roots includes:
- Object referenced in the virtual machine stack.
- The object referenced by the class static attribute entity in the method area.
- The object referenced by the constant in the method area.
- Objects referenced by JNI in the local method stack.
Garbage collection algorithm
- Mark-clear algorithm
The Mark-sweep algorithm, as its name suggests, is divided into two phases: “Mark” and “Sweep.” It flags all objects that need to be reclaimed, and when it’s done, it dumps all of the flagged objects. The reason why it is the most basic collection algorithm is that subsequent collection algorithms are based on this idea and improve its shortcomings.
Its main disadvantages are two: one is the efficiency problem, the efficiency of the marking and cleaning process is not high; Another problem is the space problem. After the mark is cleared, a large number of discrete memory fragments will be generated. Too many space fragments may cause the program to be unable to find enough contiguous memory when it needs to allocate large objects in the future and have to trigger another garbage collection action in advance.
- Replication algorithm
A collection algorithm for “Copying” that divides available memory into two equivalent pieces by capacity and uses only one piece at a time. When this area of memory is used up, the surviving objects are copied to the other area, and the used memory space is cleaned up again.
In this way, each time a piece of memory is reclaimed, memory allocation does not have to consider the complexity of memory fragmentation, as long as the heap top pointer is moved, in order to allocate memory, simple implementation, efficient operation. However, the cost of this algorithm is to reduce memory by half, and the efficiency of continuously copying long-lived objects is reduced.
- Mark-collation algorithm
The copy-collection algorithm will perform more replication operations when the object survival rate is high, and the efficiency will be low. More importantly, if you do not want to waste 50% of the space, you need to have extra space for allocation guarantee, in the extreme case that all objects in the memory used are 100% alive, so in the old days, this algorithm generally cannot be used directly.
According to the characteristics of the old s, someone put forward another “tag – finishing” (Mark – Compact) algorithm, the labeling still like “tag – clear” algorithm, but the subsequent steps are not directly to recyclable objects to clean up, but let all live objects move to the end, and then clear directly outside the boundary of the memory.
- Generational collection algorithm
The basic assumption of GC generation is that most objects have very short lifetimes.
A “Generational Collection” algorithm divides the Java heap into Generational and older generations so that the most appropriate Collection algorithm can be used for each generation. In the new generation, a large number of objects are found dead and only a few survive in garbage collection, so the replication algorithm is selected, and only a small amount of the replication cost of the surviving objects can be collected. In the old days, because the object has a high survival rate and there is no extra space to allocate it, it has to use the “mark-clean” or “mark-tidy” algorithm for recycling.
Garbage collector
- CMS collector
The CMS (Concurrent Mark Sweep) collector is a collector whose goal is to obtain the shortest collection pause time. At present, a large part of Java applications are concentrated on the server side of Internet sites or B/S systems. These applications pay special attention to the response speed of services and hope that the system pause time is the shortest, so as to bring users a better experience.
As the name (which includes “Mark Sweep”) suggests, the CMS collector is based on a “mark-sweep” algorithm, which is more complex than the previous collectors. The process is divided into four steps, including:
- CMS Initial Mark
- CMS Concurrent Mark
- Re-marking (CMS Remark)
- CMS Concurrent sweep
The initial marking and re-marking steps still need to “Stop The World”. Initial marking only marks the objects directly associated with GC Roots, which is very fast. Concurrent marking is the process of GC Roots Tracing, while re-marking is to revise the marking records of those objects whose marks are changed due to the continuous operation of user programs during concurrent marking. The pause time in this phase is generally slightly longer than in the initial tagging phase, but much shorter than in concurrent tagging.
Since the collector thread can work with the user thread during the longest concurrent markup and concurrent cleanup, the CMS collector’s memory reclamation process is generally executed concurrently with the user thread. Old collector (New generation using ParNew)
- G1 collector
Compared to the CMS collector, the G1 collector has the following features:
1. Space integration: G1 collector adopts the mark collation algorithm and will not generate memory space fragmentation. Allocating large objects does not trigger the next GC prematurely because contiguous space cannot be found.
2, predictable pauses, this is another big advantage of G1, reduce the pause time is the common concern of G1 and CMS, but G1 in addition to the pursuit of low pause, also can establish predictable pauses model, can let the user specify in a length of N segment within milliseconds, time on garbage collection may not consume more than N milliseconds, This is almost already a feature of the real-time Java (RTSJ) garbage collector.
When using the G1 collector, the memory layout of the Java heap is very different from that of the other collectors. It divides the entire Java heap into independent regions of equal size. While the concept of new generation and old generation is retained, the new generation and old generation are no longer physically separated. They are all collections of partial (possibly discontinuous) regions.
G1’s Cenozoic collection is similar to ParNew’s. When the Cenozoic occupation reaches a certain proportion, the collection starts. Like CMS, the G1 collector pauses briefly to collect old objects.
Memory model and reclamation strategy
The Java Heap is the largest chunk of memory managed by the JVM, and the Heap is the main area managed by the garbage collector. The Java Heap is mainly divided into two areas – young generation and old generation, among which the young generation is divided into Eden area and Survivor area. Among them, Survivor zone is divided into From and To zones.
- Eden area
In most cases, objects will be allocated in Eden of the new generation. When Eden does not have enough space for allocation, the virtual machine will initiate a Minor GC. Minor GC is more frequent and faster than Major GC. After Minor GC, Eden is cleared, most of the objects in Eden are reclaimed, and the surviving objects that do not need to be reclaimed are placed in Survivor’s From zone (or Old zone if the From zone is insufficient).
- Survivor area
The Survivor zone serves as a buffer between the Eden zone and the Old zone, similar to the yellow light in our traffic lights. Survivor is divided into two zones, one is From zone and one is To zone. Each time a Minor GC is performed, the Eden region and the SURVIVING objects From are placed in the Survivor’s To region (if the To region is insufficient, it goes directly To the Old region). Survivor exists to reduce the number of objects sent to the old age, thus reducing the incidence of Major GC. Survivor pre-screening guarantees that only objects that survive 16 Minor GC’s will be sent to the old age.
- Old district
Older generations occupy two-thirds of The heap and are only cleaned up during Major GC, which triggers “stop-the-world”. The larger the memory, the longer the STW, so it’s not just that bigger memory is better. Because the replication algorithm in the old age when the survival rate of the object is high will carry out many times of replication operation, the efficiency is very low, so the old age here adopts the mark-collation algorithm.
Object
The equals method
A comparison of the address values of two objects (that is, whether references are the same)
public boolean equals(Object obj) {
return (this == obj);
}
Copy the code
HashCode methods
The hashCode() method returns a hash code value to the object. This method is used for hash tables, such as HashMap.
Its properties are:
- During the execution of a Java application, an object calls the hashCode() method multiple times if the information it provides to equals to compare has not been modified. The method must always return the same INTEGER.
- If two objects are equal according to equals(Object), then calling their respective hashCode() methods must produce the same INTEGER result.
- It is not required that two objects are not equal according to the equals(Object) method; calling their respective hashCode() methods must produce different INTEGER results. However, programmers should be aware of the potential to improve hash table performance by producing different INTEGER results for different objects.
In the JDK, an Object’s hashcode method is a native method, implemented in C or C ++, that returns the Object’s memory address directly. In the String class, we override the hashCode method
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
Copy the code
static
- Methods or variables modified by the static keyword do not need to rely on the object to access, as long as the class is loaded, can be accessed by the class name.
- A static variable is shared by all objects and has only one copy in memory, which is initialized if and only if the class is first loaded.
- Can static member variables be accessed through this? All static methods and variables can be accessed through objects (as long as access is sufficient).
- Static is not allowed to modify local variables
final
- You can declare member variables, methods, classes, and local variables
- Final member variables must be initialized at declaration time or in the constructor, otherwise a compilation error will be reported
- The final variable is read-only
- Methods declared by final cannot be overridden by methods of subclasses
- Final classes are usually fully functional and cannot be inherited
- Final variables can be safely shared in multi-threaded environments without additional synchronization overhead
- The final keyword improves performance. Both JVM and Java applications cache final variables and optimize methods, variables, and classes
- The inner class of a method accesses local variables in the method, but must be decorated with final
String, StringBuffer, StringBuilder
- String is final and cannot be inherited. To change the value of an existing Stirng object is to create a new object
- A StringBuffer is a Stringbuffer similar to a String, whose value is modified using the append() method and converted to a String using the toString() method, which is thread-safe
- StringBuilder is used as an alternative to StringBuffer, StringBuilder is thread-safe and faster
Exception handling
- Exception and Error are subclasses of the Throwable class
- Error objects are generated and thrown by the Java VIRTUAL machine and cannot be captured
- The code in finally executes with or without exceptions
- When there is a return ina try or catch, the code in finally continues to execute
Common Error | ||
---|---|---|
OutOfMemoryError | StackOverflowError | NoClassDeffoundError |
Common Exception | ||
---|---|---|
Common non-checking abnormalities | ||
ArithmeticException | ArrayIndexOutOfBoundsException | ClassCastException |
IllegalArgumentException | IndexOutOfBoundsException | NullPointerException |
NumberFormatException | SecurityException | UnsupportedOperationException |
Common diagnostic abnormalities | ||
IOException | CloneNotSupportedException | IllegalAccessException |
NoSuchFieldException | NoSuchMethodException | FileNotFoundException |
The inner class
- A non-static inner class cannot be instantiated in a static method of an outer class.
- Methods of a non-static inner class can directly access all data of the outer class, including private data.
- Call an external class member from a static inner class, which also requires static decoration.
- An object that creates a static inner class can call the constructor of the static inner class directly from the outer class; To create an object of a non-static inner class, you must first create an object of the outer class, through which the constructor of the inner class is called.
Anonymous inner class
- Anonymous inner classes cannot define any static members or methods
- Methods in anonymous inner classes cannot be abstract
- The anonymous inner class must implement all the abstract methods of the interface or abstract parent class
- An anonymous inner class cannot define a constructor
- External class member variables or member methods accessed by anonymous inner classes must be modified with final
polymorphism
- A reference to a parent class can point to an object of a subclass
- When a subclass object is created, the method called is the method overridden or inherited by the subclass
- If we write a unique method in a subclass, that method cannot be called from a subclass object created by a reference to the parent class
Abstractions and interfaces
- Abstract classes cannot have objects (you cannot use the new keyword to create an object of an abstract class)
- Abstract methods in abstract classes must be overridden in subclasses
- All attributes in the interface default to: public static final;
- All methods in the interface default to: public abstract;
Collections framework
- The List interface stores a Set of non-unique, ordered (insert order) objects, while the Set interface stores a Set of unique, unordered objects.
HashMap
chart
- JDK 1.7 HashMap architecture
- JDK 1.8 HashMap structure
How HashMap works
HashMap is based on hashing, where we store and retrieve objects using the put() and get() methods. When we pass the key-value pair to the put() method, it calls the key object’s hashCode() method to compute the hashCode, and then finds the bucket location to store the Entry object. A ‘collision’ occurs when two objects have the same Hashcode and their bucket positions are the same. Because HashMap uses a linked list to store objects, this Entry is stored in the linked list, and when the object is fetched, the correct key-value pair is found through the equals() method of the key object, and the value object is returned.
What if the size of the HashMap exceeds the capacity defined by the Load factor? The default load factor is 0.75, which means that when a map is 75% full of buckets, as with other collection classes such as ArrayList, an array of buckets twice the size of the original HashMap will be created to resize the map. And put the original object into the new bucket array. This process is called rehashing because it calls the hash method to find the new bucket location.
Why are wrapper classes like String and Interger good for keys? Because strings are immutable and final, and the equals() and hashCode() methods have been overridden. Other Wrapper classes have this feature as well. Immutability is necessary because in order to evaluate hashCode(), the key is prevented from changing, and if the key returns a different hashCode when it is put in and when it is retrieved, then the object you want cannot be found in the HashMap. Immutability has other advantages such as thread safety. If you can guarantee that hashCode is immutable simply by declaring a field final, do so. Because the equals() and hashCode() methods are used to get objects, it is important that the key object overrides them properly. If two objects that are not equal return different Hashcodes, there is less chance of collisions, thus improving the performance of the HashMap.
Compare HashMap with HashTable
A HashMap is non-synchronized and performs better. A HashMap can accept a null key-value, whereas a Hashtable is thread-safe and slower than a HashMap and does not accept null key-values.
HashMap.java
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {··· public V put(K key, V value) {return putVal(Hash (key), key, value, false, true); } ··· · public V get(Object key) {Node<K,V> e; return (e = getNode(hash(key), key)) == null ? null : e.value; }...}Copy the code
HashTable.java
public class Hashtable<K,V> extends Dictionary<K,V> implements Map<K,V>, Cloneable, Java.io.Serializable {··· public synchronized V put(K key, V value) { // Make sure the value is not null if (value == null) { throw new NullPointerException(); } ··· addEntry(hash, key, value, index); return null; } ··· public synchronized V get(Object key) {HashtableEntry<? ,? > tab[] = table; int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; for (HashtableEntry<? ,? > e = tab[index] ; e ! = null ; e = e.next) { if ((e.hash == hash) && e.key.equals(key)) { return (V)e.value; } } return null; }...}Copy the code
ConcurrentHashMap
The Base 1.7
The outer layer of ConcurrentHashMap is not a large array, but an array of segments. Each Segment contains a linked list array similar to a HashMap data structure.
When reading or writing a Key, the hash value of the Key is first obtained. It modulates the number of segments by the high N bits of the hash value to find which Segment the Key should belong to, and then operates on the Segment as if it were a HashMap.
Segments inherit from ReentrantLock and can be easily locked on each Segmen.
For read operations, you need to ensure visibility when fetching the Segment where the Key resides. The implementation can use the volatile keyword or lock. But using locks was expensive, as was using volatile, which invalidated all CPU caches with each write. ConcurrentHashMap uses the following method to obtain the latest Segment:
Segment<K,V> s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)
Copy the code
A similar method is used to get a HashEntry from a Segment:
HashEntry<K,V> e = (HashEntry<K,V>) UNSAFE.getObjectVolatile
(tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE)
Copy the code
For write operations, it is not required to lock all segments at the same time, as that would lock the entire Map. It obtains the key-value lock on the Segment. After obtaining the lock, it can operate on the Segment just like a normal HashMap and guarantee the security of the Segment. Because locks on other segments are not acquired, concurrent read-write concurrencyLevel (equal to the number of segments) is theoretically supported.
When acquiring a lock, you do not use lock directly because it will hang if the method fails to acquire the lock. In fact, it uses a spin lock, and if tryLock fails to acquire the lock, the lock is being held by another thread, at which point it loops through again to tryLock the lock. Resets the retry times if the Key’s linked header is changed during the loop. If the retry times exceed a certain value, apply for a lock using the lock method.
Spinlocks are used here because they are more efficient, but they consume more CPU resources, so they are switched to mutex when the number of spins exceeds the threshold.
The Base 1.8
1.7 has solved the concurrency problem and supports N Segment concurrency, but the problem with HashMap in version 1.7 is that it is inefficient to query through the list. So 1.8 has made some changes to the data structure.
The original Segment Segment lock is abandoned, and CAS + synchronized is used to ensure concurrency security.
ConcurrentHashMap.java
final V putVal(K key, V value, boolean onlyIfAbsent) { if (key == null || value == null) throw new NullPointerException(); int hash = spread(key.hashCode()); int binCount = 0; for (Node<K,V>[] tab = table;;) { Node<K,V> f; int n, i, fh; if (tab == null || (n = tab.length) == 0) tab = initTable(); else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null))) break; // no lock when adding to empty bin } else if ((fh = f.hash) == MOVED) tab = helpTransfer(tab, f); else { V oldVal = null; synchronized (f) { if (tabAt(tab, i) == f) { if (fh >= 0) { binCount = 1; ···} else if (f instanceof TreeBin) {··· ·} else if (f instanceof ReservationNode) throw new IllegalStateException("Recursive update"); }} ··· ·} addCount(1L, binCount); return null; }Copy the code
ArrayList
An ArrayList is essentially a dynamic array. The first time you add an element, the array size changes to DEFAULT_CAPACITY 10. As you add more elements, you expand. When an element is deleted, the entire array element is moved (copied) by position. ArrayList.java
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, Java.io.Serializable ··· // Add element public Boolean add(E E) {ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; Public E remove(int index) {if (index >= size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); modCount++; E oldValue = (E) elementData[index]; int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // clear to let GC do its work return oldValue; }... / / find public E get (int index) {if (index) > = size throw new IndexOutOfBoundsException (outOfBoundsMsg (index)); return (E) elementData[index]; }...}Copy the code
LinkedList
LinkedList is essentially a storage structure for a two-way LinkedList.
LinkedList.java
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, Java.io.Serializable {···· · private Static Class Node<E> {E item; Node<E> next; Node<E> prev; Node(Node<E> prev, E element, Node<E> next) { this.item = element; this.next = next; this.prev = prev; Void linkLast(E E) {final Node<E> l = last; final Node<E> newNode = new Node<>(l, e, null); last = newNode; if (l == null) first = newNode; else l.next = newNode; size++; modCount++; E unlink(Node<E> x) {final E element = x.tem; final Node<E> next = x.next; final Node<E> prev = x.prev; if (prev == null) { first = next; } else { prev.next = next; x.prev = null; } if (next == null) { last = prev; } else { next.prev = prev; x.next = null; } x.item = null; size--; modCount++; return element; } assert isElementIndex(index);} assert isElementIndex(index); if (index < (size >> 1)) { Node<E> x = first; for (int i = 0; i < index; i++) x = x.next; return x; } else { Node<E> x = last; for (int i = size - 1; i > index; i--) x = x.prev; return x; ...}}}Copy the code
ArrayList is superior to LinkedList for element queries because LinkedList moves the pointer. For add and delete operations, LinedList has an advantage because ArrayList moves data.
CopyOnWriteArrayList
CopyOnWriteArrayList is a thread-safe container (as opposed to ArrayList) in which write operations such as delete are locked to ensure consistency of data and iterate by copying new collections.
CopyOnWriteArrayList.java
public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { final transient Object lock = new Object(); Public Boolean add(E E) {synchronized (lock) {Object[] elements = getArray(); public Boolean add(E E) {synchronized (lock) {Object[] elements = getArray(); int len = elements.length; Object[] newElements = Arrays.copyOf(elements, len + 1); newElements[len] = e; setArray(newElements); return true; }} ··· // Delete element public E remove(int index) {synchronized (lock) {Object[] elements = getArray(); int len = elements.length; E oldValue = get(elements, index); int numMoved = len - index - 1; if (numMoved == 0) setArray(Arrays.copyOf(elements, len - 1)); else { Object[] newElements = new Object[len - 1]; System.arraycopy(elements, 0, newElements, 0, index); System.arraycopy(elements, index + 1, newElements, index, numMoved); setArray(newElements); } return oldValue; Private E get(Object[] a, int index) {return (E) a[index]; }}Copy the code
reflection
try { Class cls = Class.forName("com.jasonwu.Test"); Constructor[] publicConstructors = cls.getconstructors (); / / get all constructors Constructor [] declaredConstructors = CLS. GetDeclaredConstructors (); [] methods = cls.getmethods (); [] declaredMethods = cls.getDeclaredmethods (); Field[] publicFields = cls.getFields(); Field[] declaredFields = cls.getDeclaredFields(); Object clsObject = cls.newInstance(); Method method = cls.getDeclaredMethod("getModule1Functionality"); Object object = method.invoke(null); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); }Copy the code
The singleton
The hungry type
public class CustomManager { private Context mContext; private static final Object mLock = new Object(); private static CustomManager mInstance; public static CustomManager getInstance(Context context) { synchronized (mLock) { if (mInstance == null) { mInstance = new CustomManager(context); } return mInstance; } } private CustomManager(Context context) { this.mContext = context.getApplicationContext(); }}Copy the code
Double check mode
public class CustomManager { private Context mContext; private volatile static CustomManager mInstance; Public static CustomManager getInstance(Context Context) {// Avoid unnecessary if (mInstance == null) {synchronized (CustomManger.class) { if (mInstance == null) { mInstacne = new CustomManager(context); } } } return mInstacne; } private CustomManager(Context context) { this.mContext = context.getApplicationContext(); }}Copy the code
Static inner class schema
public class CustomManager{ private CustomManager(){} private static class CustomManagerHolder { private static final CustomManager INSTANCE = new CustomManager(); } public static CustomManager getInstance() { return CustomManagerHolder.INSTANCE; }}Copy the code
Static inner classes work like this: When a SingleTon is first loaded, there is no need to load the SingleTonHoler. INSTANCE is initialized only when the getInstance() method is called for the first time. This method not only ensures thread-safety, but also ensures the uniqueness of singletons. It also delays instantiation of singletons. The getInstance method does not go to the new object multiple times; it always takes the same INSTANCE object.
The virtual machine ensures that a class’s < Clinit >() methods are locked and synchronized correctly in a multithreaded environment. If multiple threads initialize a class at the same time, only one thread will execute the class’s < Clinit >() methods, and all the other threads will block and wait. Until the active thread completes executing the
() method
The disadvantage is that you cannot pass parameters such as Context
thread
A thread is the smallest unit in a process that can execute independently and is the basic unit for allocating CPU resources (time slices). Threads in the same process can share resources in the process, such as memory space and file handles.
attribute
attribute | instructions |
---|---|
id | Thread ids are used to identify different threads. The number may be used by subsequent threads. The number is a read-only property and cannot be modified |
name | The default value of the name is Thread-(id) |
daemon | There are daemons and user threads, which can be set to daemons by setDaemon(true). Daemon threads are usually used to perform unimportant tasks, such as monitoring the health of other threads, and the GC thread is a daemon thread. SetDaemon () should be set before the thread starts, otherwise the JVM will throw an illegal thread state exception that can be inherited. |
priority | The thread scheduler uses this value to determine which thread to run first (not guaranteed). The value of priority ranges from 1 to 10. The default value is 5, which can be inherited. Thread defines the following priority constants: – Minimum priority: MIN_PRIORITY = 1 – Default priority: NORM_PRIORITY = 5 – Maximum priority: MAX_PRIORITY = 10 |
state
state | instructions |
---|---|
New | A new thread object is created, but the start() method has not yet been called. |
Runnable | After the Ready thread object is created, other threads (such as the main thread) call its start() method. Threads in this state are in the runnable thread pool, waiting to be selected by thread scheduler for CPU usage. A thread in the Running state becomes in the Running state after obtaining a CPU time slice. |
Blocked | The thread has relinquished CPU usage for some reason (waiting for a lock) and has temporarily stopped running |
Waiting | The thread enters the wait state because of the following methods: -object# wait() -thread #join() -locksupport #park() |
Timed Waiting | A wait state with a wait time. |
Terminated | Indicates that the thread has finished executing. |
State control
- wait() / notify() / notifyAll()
Wait (), notify(), and notifyAll() are instance methods defined on the Object class to control thread state. All three methods must be called in the scope defined by the synchronized keyword. Otherwise an error Java. Lang. IllegalMonitorStateException.
methods | instructions |
---|---|
wait() |
The use of thread state by. Running becomes Waiting and the current thread is queued |
notify() |
The notify() method moves a wait thread from the wait queue to the synchronization queue |
notifyAll() |
All threads in the wait queue are moved to the synchronous queue |
The status of the thread being moved changes from Running to Blocked. After notifyAll is called, the waiting thread still does not return from wait(). After the thread calling notify() or notifyAll() releases the lock, The waiting thread has a chance to return from wait().
- join() / sleep() / yield()
In many cases, the main thread creates and starts child threads, and if a lot of time-consuming computation is required in the child thread, the main thread often terminates before the child thread. If the main thread wants to wait for the child thread to finish, for example, processing a piece of data, the main thread uses join() to retrieve the data.
The sleep(long) method does not release the object lock while sleeping, while the Join () method releases the object lock while waiting.
The yield() method temporarily suspends the currently executing thread to give waiting threads with the same priority a chance to execute. If there are no waiting threads, or if all waiting threads are of low priority, the thread continues to run. The thread scheduler determines when a thread that has performed yield will continue to run.
volatile
When a variable is declared volatile, both the compiler and the runtime notice that the variable is shared and therefore do not reorder operations on it with other memory operations. Volatile variables are not cached in registers or hidden from other processors. The JVM ensures that each read is read from memory, skipping the CPU cache, so volatile variables always return the most recently written value.
When a variable is defined as volatile, it has the following properties:
- This variable is guaranteed to be visible to all threads. It is not guaranteed to be atomic.
- Disallow instruction reordering optimization
- Volatile has almost the same read cost as normal variables, but writes are slower because it requires inserting many memory-barrier instructions into the native code to keep the processor from executing out of order
AtomicInteger mainly implements atomic operations of integers to prevent abnormal results in concurrent situations. Its internal implementation relies on the Unsafe class in the JDK to manipulate data in memory. The volatile modifier ensures that the value is visible to other threads in memory that it is worth changing. Compare and Swap (CAS) ensures that AtomicInteger can safely modify value.
synchronized
When it is used to decorate a method or a code block, it ensures that at most one thread is executing the code at a time.
In Java, each object has a Monitor object, which is essentially a lock on the Java object, often referred to as a “built-in lock” or “object lock.” Class can have more than one object, so each object has its own object lock that does not interfere with each other. There is also a lock for each Class, which can be called a “Class lock.” Class locks are actually implemented through object locks, the Class object locks of classes. Each Class has only one Class object, so each Class has only one Class lock.
Monitor is a thread-private data structure, and each thread has a list of available Monitor Records, as well as a global list of available records. Each locked object is associated with a Monitor, and an Owner field in the monitor stores the unique identity of the thread that owns the lock, indicating that the lock is occupied by the thread. Monitor is a thread synchronization that relies on the underlying operating system’s Mutex Lock.
Sort by lock acquired
Obtaining the object lock
- synchronized(this|object) {}
- Decorates non-static methods
Get kind of lock
- Synchronized (class. The class) {}
- Decorated static methods
The principle of
Sync code block:
- Monitorenter and Monitorexit directives
Synchronized methods
- ACC_SYNCHRONIZED implementation on method modifiers
Lock
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
Copy the code
methods | instructions |
---|---|
lock() |
Used to acquire the lock, if acquired by another thread, in the wait state. If a Lock is used, the Lock must be actively released and will not be automatically released in the event of an exception. Therefore, in general, using a Lock must be done ina try{}catch{} block, and the Lock release must be done ina finally block to ensure that the Lock is released and prevent deadlocks. |
lockInterruptibly() |
If the thread is waiting to acquire the lock, the thread can respond to the interrupt, which is the wait state of the interrupted thread. |
tryLock() |
The tryLock method returns a value indicating that it was used to attempt to acquire the lock, returning true on success or false on failure (i.e. the lock has been acquired by another thread), meaning that the method will return immediately anyway. You don’t wait around until you get the lock. |
TryLock (long, TimeUnit) |
Similar to tryLock, but with a wait time, the lock is acquired within the wait time and false after timeout. |
The classification of the lock
Pessimistic lock, optimistic lock
Pessimistic locks assume that data must be modified by other threads when they are using data. Therefore, they lock data before acquiring data to ensure that data cannot be modified by other threads. In Java, the implementation classes for the synchronized keyword and Lock are pessimistic locks. Pessimistic locking applies to scenarios where many write operations are performed. The pessimistic locking ensures correct data during write operations.
Optimistic locks, on the other hand, assume that no other thread will modify the data when they use it, so they will not add the lock, but just check whether the data has been updated before by another thread. If the data is not updated, the current thread writes its modified data successfully. If the data has already been updated by another thread, different operations are performed (such as reporting an error or automatic retry) depending on the implementation. Optimistic locking is implemented in Java by using lock-free programming, most commonly CAS algorithm, where incremental operations in Java atomic classes are implemented by CAS spin. Optimistic lock is suitable for scenarios where many read operations are performed. The no-lock feature greatly improves the read operation performance.
Spin lock, adaptive spin lock
Blocking or waking up a Java thread requires the operating system to switch CPU state, which takes processor time. If synchronizing the contents of a code block is too simple, state transitions can take longer than user code execution.
In many scenarios, synchronization resources are locked for a short period of time, and the cost of thread suspension and site recovery may not be worth the cost to the system to switch threads for this short period of time. If the physical machine has multiple processors that allow two or more threads to execute in parallel at the same time, we can let the later thread that requests the lock not give up CPU execution time to see if the thread that holds the lock will release the lock soon.
In order for the current thread to “hold on”, we need to spin the current thread. If the thread that held the synchronized resource before spinning has released the lock, the current thread can directly acquire the synchronized resource without blocking, thus avoiding the overhead of switching threads. This is the spin lock.
Spinlocks have their own drawbacks; they are no substitute for blocking. Spin waiting avoids the overhead of thread switching, but it takes up processor time. If the lock is held for a short period of time, the spin wait works very well. Conversely, if the lock is held for a long time, the spinning thread is a waste of processor resources. Therefore, the spin wait time must be limited, and the thread should be suspended if the lock is not successfully acquired if the spin exceeds the limit (the default is 10 spins, which can be changed using -xx :PreBlockSpin).
The implementation principle of spin locking is also CAS. In AtomicInteger, the source code calling unsafe for the increment operation, the do-while loop is a spin operation. If changing the value fails, the spin operation is performed through the loop until the change succeeds.
A deadlock
The current thread has resources needed by other threads, and the current thread waits for resources already owned by other threads without giving up its own resources.
Reference types
Strong > Soft > Weak references
Reference types | instructions |
---|---|
StrongReferenc (strong reference) | When an object has a strong reference, the garbage collector never collects and destroys it.A non-static inner class holds strong references to its outer class throughout its life cycle |
WeakReference (WeakReference) | When the garbage collector runs, if all references to an object are weak references, the object is collected |
SoftReference (SoftReference) | If an object has only soft references, the garbage collector will not reclaim it if there is enough memory; If the memory space is insufficient, the memory of these objects is reclaimed |
PhantomReference | An object held only by a virtual reference may be reclaimed by the GC at any time. A virtual reference has no effect on the life cycle of an object, nor can an object instance be obtained through a virtual reference. It only gets a system notification when an object is reclaimed (it can only be determined by whether it is added to the ReferenceQueue, which is the only way to determine whether an object is GC or not). |
A dynamic proxy
Example:
Public interface BaseInterface {void doSomething(); } public class implements BaseInterface {@override public void doSomething() { System.out.println("doSomething"); } } public static void main(String args[]) { BaseImpl base = new BaseImpl(); BaseInterface proxyInstance = (BaseInterface) proxy.newProxyInstance (base.getClass().getClassLoader(), base.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (method.getName().equals("doSomething")) { method.invoke(base, args); System.out.println("do more"); } return null; }}); proxyInstance.doSomething(); }Copy the code
Proxy.java
Public class Proxy implements java.io.Serializable {// Public class Proxy implements java.io.Serializable {// Private static final WeakCache<ClassLoader, class <? >[], Class<? >> proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory()); Public static Object newProxyInstance(ClassLoader loader, Class<? >[] interfaces, InvocationHandler h) throws IllegalArgumentException { Objects.requireNonNull(h); final Class<? >[] intfs = interfaces.clone(); // Find and generate the associated proxy Class<? > cl = getProxyClass0(loader, intfs); // Call the Constructor of the proxy class to generate the proxy class instance try {final Constructor<? > cons = cl.getConstructor(constructorParams); final InvocationHandler ih = h; if (! Modifier.isPublic(cl.getModifiers())) { cons.setAccessible(true); } return cons.newInstance(new Object[]{h}); } ··· ··· ··· ··· ··· ··· ··· private static final Class ProxyClassFactory implements BiFunction<ClassLoader, class <? >[], Class<? >> {private static Final String proxyClassNamePrefix = "$Proxy"; Private static Final AtomicLong nextUniqueNumber = new AtomicLong(); @Override public Class<? > apply(ClassLoader loader, Class<? >[] interfaces) { Map<Class<? >, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length); ··· String proxyPkg = null; / / used to define the package name of the proxy class int accessFlags = Modifier. The PUBLIC | Modifier. The FINAL; // ensure that all non-public proxy interfaces are in the same package for (Class<? > intf : interfaces) { int flags = intf.getModifiers(); if (! Modifier.isPublic(flags)) { accessFlags = Modifier.FINAL; String name = intf.getName(); int n = name.lastIndexOf('.'); String pkg = ((n == -1) ? "" : name.substring(0, n + 1)); if (proxyPkg == null) { proxyPkg = pkg; } else if (! pkg.equals(proxyPkg)) { throw new IllegalArgumentException( "non-public interfaces from different packages"); }} if (proxyPkg == null) {// If there is no non-public proxy interface, use the default package name proxyPkg = ""; } { List<Method> methods = getMethods(interfaces); Collections.sort(methods, ORDER_BY_SIGNATURE_AND_SUBTYPE); validateReturnTypes(methods); List<Class<? >[]> exceptions = deduplicateAndGetExceptions(methods); Method[] methodsArray = methods.toArray(new Method[methods.size()]); Class<? >[][] exceptionsArray = exceptions.toArray(new Class<? >[exceptions.size()][]); / / the name of the generated proxy class long num = nextUniqueNumber. GetAndIncrement (); String proxyName = proxyPkg + proxyClassNamePrefix + num; Return generateProxy(proxyName, Interfaces, Loader, methodsArray, exceptionsArray); / / JDK use ProxyGenerator generateProxyClas method to create the proxy class byte [] proxyClassFile = ProxyGenerator. GenerateProxyClass ( proxyName, interfaces, accessFlags); try { return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length); @fastNative Private static native Class<? > generateProxy(String name, Class<? >[] interfaces, ClassLoader loader, Method[] methods, Class<? >[][] exceptions); }Copy the code
ProxyGenerator.java
public static byte[] generateProxyClass(final String name, Class[] interfaces) { ProxyGenerator gen = new ProxyGenerator(name, interfaces); final byte[] classFile = gen.generateClassFile(); if (saveGeneratedFiles) { java.security.AccessController.doPrivileged( new java.security.PrivilegedAction<Void>() { public Void run() { try { FileOutputStream file = new FileOutputStream(dotToSlash(name) + ".class"); file.write(classFile); file.close(); return null; } catch (IOException e) { throw new InternalError( "I/O exception saving generated file: " + e); }}}); } return classFile; }Copy the code
Yuan notes
@Retention: The range of Retention. There are three possible values.
RetentionPolicy | instructions |
---|---|
SOURCE | Annotations are discarded by the compiler (annotations of this type are discarded only in the source code after the source code is compiled, not in the compiled class file), such as @override |
CLASS | Annotations are available in the class file, but are discarded by the VM (annotation information of this type is retained in the source code and in the class file, and is not loaded into the VM at execution time). Note that the default value is class when annotations do not define Retention. |
RUNTIME | Annotation information is also retained at runtime (JVM), so annotation information can be read by reflection (annotated information is available at source, class file, and execution time), such as @deprecated |
@target: Which program elements can be modified (TYPE, METHOD, CONSTRUCTOR, FIELD, PARAMETER, etc.)
@Inherited: Whether Inherited is used. The default value is false
Documented: Whether to save to a Javadoc document