During an interview, encounter this problem, do not take lightly a answer in the heap, generally in a Java program, the new object is allocated on the heap in space, but the truth is, most of the new objects into the heap space, is not the whole object, there are two other places to store the new object, We call it on-stack allocation and TLAB
There are a few things you need to know before you start this chapter. Here are some of them:
1. JVM class loading process
2. JVM memory structure/heap generation structure
Let’s get down to business:
[toc]
Understand the Java compilation process
Low-level languages are the languages that computers know, and high-level languages are the languages that programmers know. How do you convert from a high-level language to a low-level language? This process is essentially compilation.
Different languages have their own compilers, and the compiler responsible for compiling in the Java language is a command: javac
The source code of a Java program is compiled into Java bytecodes, commonly known as.class files, through the Javac command. This is what we understand by compilation.
But.class is not a language that computers can recognize. In order for the machine to execute, the bytecode needs to be translated into machine instructions, which is done by the JVM. This process is also called compilation. Just deeper..
So we know that the compiler can be divided into a Front End and a Back End.
We can put the.java
File compiled into.class
The compilation process is called front-end compilation. The will.class
The process of translating files into machine instructions is called back-end compilation.
Front End compilation
Front-end compilation mainly refers to the part related to the source language but independent of the target machine, including lexical analysis, syntax analysis, semantic analysis and intermediate code generation.
For example, many ides we use, such as Eclipse, IDEA, etc., have a built-in front-end compiler. The main function is to convert. Java code to. Class bytecode
Back End
Back-end compilation mainly refers to the part related to the target machine, including code optimization and object code generation, etc.
The JVM interprets the bytecode and reads it one by one and translates it into the corresponding machine instructions. This is a problem of efficiency, so JIT(Just in Time) was introduced.
What is JIT (Just in Time)
The JVM considers a method or block of Code to be “Hot Spot Code” when it detects that it is running particularly frequently. The JIT translates some of the “hot code” into local machine-specific machine code, optimizes it, and then caches it for future use
There are two JIT compilers built into the HotSpot virtual machine:
-client complier [Client] -server complier [Server]Copy the code
The current JVM default is a combination of an interpreter and a JIT compiler that works in hybrid mode
The following figure shows the JDK installed on my machine. It can be seen that the JIT used is Server Complier, and the interpreter and JIT work in mixed mode
Interview question: Why does the HotSpot VIRTUAL machine implement two different just-in-time compilers?
Two just-in-time compilers are built into the HotSpot VIRTUAL machine: Client Complier and Server Complier (C1 and C2 compilers for short) for the Client and Server side respectively. The current HotSpot virtual machine defaults to an interpreter working directly with one of the compilers. Which compiler the program uses depends on the mode in which the virtual machine is running. The HotSpot vm automatically selects a running mode based on its version and the hardware performance of the host machine. You can also use the -client or -server parameters to force the VM to run in client or Server mode.
Use Client Complier for higher compilation speed and Server Complier for better compilation quality. Similar to why multiple garbage collectors are provided, it is to accommodate different application scenarios.
Advantages and disadvantages of compilers and interpreters and practical scenarios
The JVM executing code, it is not immediately began to compile the code, as a frequently executed code is compiled, the next operation need not repeat the compilation, this time with a JIT is cost-effective, but it also is not everything, for example some rarely execute code at compile time the amount of time longer than the interpreter, at this time is not worth the cost
So, the interpreter and the JIT have their own advantages:
The interpreter and the compiler each have their own advantages: when a program needs to be started and executed quickly, the interpreter can be used first, saving compilation time and executing immediately. After the program runs, as time goes by, the compiler gradually comes into play, and more and more code is compiled into local code, which can achieve higher execution efficiency.
- The interpreter is best used when JAVA code is executed infrequently or infrequently.
- JIT is more cost-effective when JAVA code is executed repeatedly or more frequently.
Hotspot detection algorithm
To trigger the JIT, you first need to identify the hot code. At present, the main hotspot code identification method is Hot Spot Detection, which includes the following two methods:
1) Hot spot detection based on sampling
A virtual machine that uses this approach periodically checks the top of the stack for each thread, and if it finds a method that is frequently at the top of the stack, that method is a “hot method.” The advantage of this method is that it is simple and efficient to implement, and it is easy to obtain the method call relationship (just expand the call stack), but the disadvantage is that it is difficult to accurately confirm the popularity of a method, and it is easy to disrupt the hot spot detection due to thread blocking or other external factors.
2) Hotspot detection based on counters
A virtual machine that takes this approach sets up counters for each method (even a block of code), counts the number of times a method is executed, and considers it a “hot method” if it is executed more than a certain threshold. The implementation of this statistical method is more complicated. Counters need to be established and maintained for each method, and the call relationship of the method cannot be directly obtained, but its statistical results are relatively more precise and rigorous.
So which HotSpot detection method is used in the HotSpot VIRTUAL machine?
The second, counter based HotSpot detection method is used in the HotSpot VIRTUAL machine, so it has two counters for each method:
> 1Method call counter
As the name implies, a counter that counts the number of times a method is called.
> 2Back edge counter
Is a counter that counts the number of times a for or while has been run in a method.
Both counters have a certain threshold, provided that the virtual machine’s operating parameters are determined. When the counter overflows, JIT compilation is triggered.
Optimization of allocation on the object stack
Escape analysis
Escape analysis is an analysis algorithm that effectively reduces synchronization load and heap memory allocation stress in JAVA programs. The Hotspot compiler can analyze the scope of a new object reference and decide whether to assign the object to the stack.
public static StringBuffer method(String s1, String s2) {
StringBuffer sb = new StringBuffer();
sb.append("Attention");
sb.append("Java treasure dian");
return sb;
// The sb object escapes from the method.
}
Copy the code
public static String method(String s1, String s2) {
StringBuffer sb = new StringBuffer();
sb.append("Attention");
sb.append("Java treasure dian");
return sb.toString();
// the sb object is not out of scope
}
Copy the code
public void globalVariableEscape(a){
globalVariableObject = new Object(); // Static variable, visible to external threads, escape occurs
}
public void instanceObjectEscape(a){
instanceObject = new Object(); // Assign to the instance field in the heap, visible to external threads, escape occurs
}
Copy the code
After determining that the object will not escape, the JIT will be able to optimize as follows: scalar replacement synchronously eliminates allocation on the stack
The SB in the first code escapes, while the SB in the second code does not.
When Java code runs, you can specify whether to turn on escape analysis via JVM parameters,
-xx :+DoEscapeAnalysis: Enables escape analysis
-xx: -doescapeanalysis: closes escape analysis
-xx :+PrintEscapeAnalysis Enables printing escape analysis filtering results
Escape analysis has been implemented by default since JDK 1.7
Scalar replacement
Allows objects to be split and allocated on the stack, for example if an object has two fields, these fields are allocated as local variables.
Escape analysis is only a prerequisite for memory allocation on the stack, and scalar replacement is needed to truly implement it. Ex. :
public static void main(String[] args) throws Exception {
long start = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
allocate();
}
System.out.println((System.currentTimeMillis() - start) + " ms");
Thread.sleep(10000);
}
public static void allocate(a) {
MyObject myObject = new MyObject(2019.2019.0);
}
public static class MyObject {
int a;
double b;
MyObject(int a, double b) {
this.a = a;
this.b = b; }}Copy the code
Scalars are data in the JVM that cannot be subdivided, such as int, long, reference, etc. Conversely, data that can be subdivided is called aggregate
Primitive data types in the Java virtual machine (numeric types such as int, long, and reference types) cannot be further decomposed and can be called scalars. In contrast, if a piece of data can be further decomposed, it is called an aggregate quantity, and the most typical aggregate quantity in Java is an object
If escape analysis proves that an object cannot be accessed externally and that the object is decomposable, the program may not actually create the object, but instead create its member variables that are used by the method. The disassembled variables can then be analyzed and optimized separately, and space can be allocated on each stack frame or register, eliminating the need to allocate space to the original object as a whole
Still considering the above example, MyObject is an aggregate because it consists of two scalars a and b. Through escape analysis, the JVM finds that myObject does not escape the allocate() method, and the scalar replacement process disassembles myObject directly into a and B, i.e.
static void allocate(a) {
int a = 2019;
double b = 2019.0;
}
Copy the code
As you can see, allocation of objects is completely eliminated, and ints and doubles are basic data types that can be allocated on the stack. So objects can be allocated on the stack as long as they do not escape scope and can be decomposed into pure scalar representations
- Turn on scalar substitutions (-xx :+EliminateAllocations)
Scalar substitution allows objects to be scattered on the stack based on attributes. This is enabled by default
Sync elimination (lock elimination)
If the lock object used by the synchronized block is verified by escape analysis to be accessible by only one thread, then the JIT compiler unsynchronizes that part of the code when the synchronized block is compiled. This unsynchronization process is called synchronization elision, also known as lock elimination
Example:
public void f(a) {
Object java_bible = new Object();
synchronized(java_bible) { System.out.println(java_bible); }}Copy the code
After escape analysis, the JIT compilation phase is optimized to:
public void f(a) {
Object java_bible = new Object();
System.out.println(java_bible); // The lock is removed.
}
Copy the code
If the JIT does escape analysis and finds no thread-safety issues, lock elimination is performed.
On the stack
Through escape analysis, we find that the life cycle of many objects begins and ends when a method is called, and many objects do not escape the scope of the method. For such objects, we can consider using stack allocation rather than heap allocation.
Because once allocated in heap space, when the method call ends and there are no references to the object, the object needs to be reclaimed by the GC, which can be a burden to the GC if there are a lot of such cases.
The JVM provides a concept called allocation on the stack, which allocates memory not in the heap for objects whose scope does not escape the method, but on the stack (thread private, belonging to the stack memory, scalar substitution) after the method is called. The collection of stack space increases the overall performance of the application by reclaiming the scattered objects allocated on the stack without placing additional unwanted burden on the GC
So the question is, what if an assignment on the stack fails?
Object memory allocation
There are several ways to create an object: use new, Reflect, and clone. Either way, we allocate memory first
Let’s take new for example:
T t = new T()
class T{
int m = 8;
}
//javap
0 new #2<T> // the value of m is 0 after new
3 dup
4 invokespecial #3 <T.<init>>
7 astore_1
8 return
Copy the code
So how is it distributed?
When we use the new code starts running after creating an object, a virtual machine to execute commands to the new, will first check whether the corresponding class to the new object has been loaded, if not be loaded first class loading, check through the later, you need to object to carry on the memory allocation, the memory allocated is mainly used to store the object’s instance variables
The task of allocating space for an object is equivalent to dividing a certain size of memory from the Java heap
The JVM uses different allocation methods depending on whether memory is contiguous or discontiguous.
- Continuous: pointer collision
- Discontinuous: Free list
Pointer collisions (collectors such as Serial, ParNew, and others with Compact procedures) assume that memory in the Java heap is perfectly neat, with all used memory on one side and free memory on the other, with a pointer in the middle as an indicator of the dividing point. The allocated memory simply moves the Pointer an equal distance to the size of the object into free space. This allocation is called a “Bump the Pointer.”
Free list (CMS) If the memory in the Java heap is not tidy and used memory and free memory interleave each other, there is no way to simply do pointer collisions and the virtual machine must maintain a list of which memory blocks are available. During allocation, a large enough space is found in the List to allocate to the object instance, and the records on the List are updated. This allocation method is called “Free List”.
Either way, you eventually need to decide on an area of memory to allocate to newly created objects. In the process of object memory allocation, it is mainly the reference of the object pointing to the memory region, and then the initialization operation, so in the concurrent scenario, if multiple threads concurrently acquire the memory region in the heap, how to ensure the thread safety of memory allocation?
Solve concurrent heap memory allocation problems
There are two ways to ensure thread-safe allocation:
- CAS
- TLAB
CAS
CAS: Uses the CAS mechanism and retry mode to ensure thread security
CAS uses retry mechanism to control memory, so it is inefficient. Currently, THE JVM uses TLAB, and we will focus on TLAB.
TLAB
TLAB: each thread in Java heap pre-allocated a small piece of memory, and then to allocate memory objects, directly in the memory is allocated in “private”, when the parts finished, after the redistribution of new “private” memory, pay attention to the private for creating objects are private, but to read is Shared.
TLAB (Thread local Allcation Buffer) is a thread-exclusive activity for allocation, and a thread-shared activity for read and garbage collection. If the allocation fails, TLAB will be used to try the allocation. If the allocation fails, it will check whether it is a large object. If it is a large object, it will enter the old age directly; otherwise, it will enter the new generation (Eden). Here I summarize a flow chart as follows:
We can conclude that creating large objects is more efficient than creating many small objects
Don’t know if you have noticed, TLAB allocate space, each thread in advance a small piece of allocated memory in the Java heap, they in the heap to rob the territory, also can appear concurrency issues, but for the synchronous control TLAB compared to us directly in the heap allocation efficiency a lot (don’t want to assign an object and lock the entire heap).
conclusion
In order to ensure the security of memory Allocation for Java objects and improve efficiency, each Thread can allocate a small portion of memory in the Java heap in advance. This portion of memory is called TLAB (Thread Local Allocation Buffer). This portion of memory can be allocated exclusively by the Thread. Read, use, and recycle are shared by threads.
Whether the VM uses TLAB can be specified using the -xx :+/ -usetlab parameter
Welcome to pay attention to the public number: Java treasure to receive more tutorial welfare