Detailed explanation of Java virtual machine

Source: Shang Silicon Valley Song Hongkang JVM full set of tutorials (detailed java virtual machine)_哔哩哔哩_bilibili

1. JVM and Java Architecture

1.1. Preface

image-20200704111417472

If we compare the API of the core class library to a mathematical formula, then the knowledge of the Java virtual machine is like the derivation process of the formula.

image-20200704112119729

The computer system system is getting farther and farther away from us. It is easy to write program code in a high-level language without knowing the underlying implementation method. But in fact, computers don't know high-level languages.

Java vs C++

image-20200704112700211

The garbage collection mechanism has taken care of a lot of tedious work for us and greatly improved the efficiency of development. However, garbage collection is not a panacea. Understanding the internal memory structure and working mechanism of the JVM is the key to designing highly scalable applications and diagnosing runtime problems. The foundation of Java is also a necessary ability for Java engineers to advance.

1.2. Target audience and bibliography

image-20210507095948516

image-20200704145340513

1.3. Introduction to Java and JVM

There is no best programming language in the world, only the most suitable programming language for specific application scenarios

JVM: A cross-language platform

Java is currently one of the most widely used software development platforms. With the continuous growth of Java and the Java community, Java is no longer a simple computer language, but a platform, a culture, and a community.

image-20200704151731216

Each language needs to be converted into a bytecode file, and the final converted bytecode file can be run and processed by the Java virtual machine

image-20200704152052489

bytecode

  • The java bytecode we usually talk about refers to the bytecode compiled in the java language. To be precise, any bytecode format that can be executed on the jvm platform is the same. So it should be collectively referred to as: jvm bytecode.
  • Different compilers can compile the same bytecode file, and the bytecode file can also run on different JVMs.
  • The Java virtual machine is not necessarily related to the Java language. It is only associated with a specific binary file format—the Class file format. The Class file contains the Java virtual machine instruction set (or called bytecode, Bytecodes) and symbol table , and some other auxiliary information.

1.4. Major events in the development of Java

  • In 1990, in Sun Computer Corporation, the Green Team, a group led by Patrick Naughton, Mike Sheridan and James Gosling, developed a new programming language named oak and later named Java.
  • In 1995, Sun officially released Java and HotJava products, and Java made its public appearance for the first time.
  • On January 23, 1996, Sun Microsystems released JDK 1.0.
  • In 1998, the JDK1.2 version was released. At the same time, sun released the JSP/Servlet and EJB specifications, and divided Java into J2EE, J2SE and J2ME. This shows that Java has begun to advance to the three major areas of enterprise, desktop applications and mobile device applications.
  • In 2000, JDK1.3 was released, and the Java HotSpot Virtual Machine was officially released, becoming the default virtual machine for Java.
  • In 2002, JDK1.4 was released, and the ancient Classic virtual machine withdrew from the stage of history.
  • In 2004, JDK1.5 was released. At the same time, JDK1.5 was renamed JavaSE5.0.
  • In 2006, JDK6 was released. In the same year, Java was open-sourced and OpenJDK was established. Logically, the Hotspot virtual machine has also become the default virtual machine in openJDK.
  • In 2008, Oracle acquired BEA and got the JRockit virtual machine.
  • In 2010, Oracle acquired Sun, acquiring the Java trademark and the most valuable HotSpot virtual machine. At this time, Oracle has the two virtual machines HotSpot and JRockit with the highest market occupancy, and plans to integrate them in the future: HotRockit
  • In 2011, JDK7 was released. In JDK1.7u4, the new garbage collector G1 is officially enabled.
  • In 2017, JDK9 was released. Set G1 as default Gc, instead of CMS
  • In 2018, Android's Java infringement case was judged, and Google compensated Oracle with a total of 8.8 billion US dollars
  • In 2019, JDK12 was released and joined the shenandoah GC led by RedHat


1.5. Virtual Machine and Java Virtual Machine

virtual machine

The so-called virtual machine (Virtual Machine), is a virtual computer. It is a piece of software that executes a series of virtual computer instructions. Generally, virtual machines can be divided into system virtual machines and program virtual machines.

  • The well-known Visual Box and Mware belong to system virtual machines, which are completely simulations of physical computers and provide a software platform that can run a complete operating system.
  • A typical representative of a program virtual machine is the Java virtual machine, which is specially designed to execute a single computer program. The instructions executed in the Java virtual machine are called Java bytecode instructions.

Whether it is a system virtual machine or a program virtual machine, the software running on it is limited to the resources provided by the virtual machine.

Java virtual machine

  • The Java virtual machine is a virtual computer that executes Java bytecode. It has an independent operating mechanism, and the Java bytecode it runs may not be compiled from the Java language.
  • Various languages ​​​​of the JVM platform can share the cross-platform, excellent garbage collector, and reliable just-in-time compiler brought by the Java virtual machine.
  • The core of Java technology is the Java Virtual Machine (JVM, Java Virtual Machine), because all Java programs run inside the Java Virtual Machine.

effect

  • The Java virtual machine is the operating environment of the binary bytecode, which is responsible for loading the bytecode into it, and interpreting/compiling it into machine instructions on the corresponding platform for execution. Each Java instruction is defined in detail in the Java Virtual Machine Specification, such as how to fetch operands, how to process operands, and where to place the processing results.

features

  • compile once, run everywhere
  • automatic memory management
  • Automatic Garbage Collection

The location of the JVM

image-20200704183048061

The JVM runs on top of the operating system, it has no direct interaction with the hardware
image-20210507104030823


1.6. The overall structure of the JVM

image-20200704183436495

  • HotSpot VM is one of the masterpieces of high-performance virtual machines currently on the market.
  • It uses an architecture in which an interpreter and a just-in-time compiler coexist.
  • Today, the operating performance of Java programs has been reborn, and has reached the point where it can compete with C/C++ programs.

1.7. Java code execution process

image-20200704210429535
 

1.8. Architecture Model of JVM

The instruction stream input by the Java compiler mainly has two architectures:

  • Stack-based instruction set architecture
  • Register-Based Instruction Set Architecture

Specifically: the difference between these two architectures:

Based on the characteristics of the stack architecture

  • Easier to design and implement, suitable for resource-constrained systems
  • Avoid the problem of register allocation: use zero address instruction to allocate
  • Most of the instructions in the instruction stream are zero-address instructions, and their execution depends on the operation stack. The instruction set is smaller, and the compiler is easy to implement
  • Does not require hardware support, better portability, better cross-platform

Features of register-based architecture

  • Typical applications are x86 binary instruction sets: such as traditional PCs and Android's Davlik virtual machine
  • The instruction set architecture is completely dependent on hardware and has poor portability
  • Excellent performance and more efficient execution
  • It takes fewer instructions to complete an operation
  • In most cases, the instruction set based on the register architecture tends to be dominated by one-address instructions, two-address instructions and three-address instructions, while the instruction set based on the stack architecture is dominated by zero-address instructions.

Example 1

Also perform the logical operation of 2+3, and the instructions are as follows:

Stack-based computing process (taking the Java virtual machine as an example):

iconst_2 //常量2入栈
istore_1
iconst_3 // 常量3入栈
istore_2
iload_1
iload_2
iadd //常量2/3出栈,执行相加
istore_0 // 结果5入栈

And the register-based calculation flow

mov eax,2 //将eax寄存器的值设为1
add eax,3 //使eax寄存器的值加3

Summarize

Due to the cross-platform design, Java instructions are designed based on the stack. The CPU architecture of different platforms is different, so it cannot be designed as register-based. The advantage is that it is cross-platform, the instruction set is small, and the compiler is easy to implement. The disadvantage is that the performance decreases, and more instructions are needed to realize the same function.

1.9. JVM life cycle

virtual machine startup

The startup of the Java virtual machine is accomplished by creating an initial class (initial class) through the bootstrap class loader, which is specified by the specific implementation of the virtual machine.

virtual machine execution

  • A running Java virtual machine has a clear mission: to execute Java programs.
  • It runs when the program starts and stops when the program ends.
  • When a so-called Java program is executed, what is actually being executed is a process called the Java virtual machine.

virtual machine exit

There are several situations as follows:

  • The program ends normally
  • The program terminates abnormally when it encounters an exception or error during execution
  • The Java virtual machine process terminated due to an operating system usage error
  • A thread calls the exit method of the Runtime class or the system class, or the halt method of the Runtime class, and the Java security manager also allows this exit or halt operation.
  • In addition, the JNI (Java Native Interface) specification describes the exit of the Java virtual machine when the JNI Invocation API is used to load or unload the Java virtual machine.


1.10. The development history of JVM

Sun Classic VM

  • In 1996, when Java1.0 was released, it was the world's first commercial Java virtual machine, which was eliminated by JDK1.4.
  • Only an interpreter is provided inside this virtual machine.
  • If you use the JIT compiler, you need to plug it in. Interpreters and compilers don't work together.

Exact VM

  • Exact Memory Management: Accurate memory management, when jdk1.2, Sun provides this virtual machine.
  • The virtual machine can know what type of data is in a certain location in memory
  • Dimensions with modern high-performance virtual machines: hotspot detection, compiler and interpreter hybrid working mode
  • It is only used briefly on the solaris platform, and it is still a classic vm on other platforms

HotSpot VM

  • Originally designed by a small company called "Longview Technologies". In 1997, the company was acquired by Sun; in 2009, Sun was acquired by Oracle.
  • In JDK1.3, HotSpot VM becomes the default virtual machine. Default VM for Sun/Oracle JDK and OpenJDK
  • The default virtual machines introduced in this article are all HotSpot, and the related mechanism mainly refers to the Gc mechanism of HotSpot. (For example, the other two commercial virtual machines have no concept of method area)
  • The compiler and interpreter work together to strike a balance between optimized program response time and best execution performance

JRockit

  • Focus on server-side applications. It can pay less attention to the program startup speed, so JRockit does not include a parser implementation inside, and all codes are compiled and executed by a just-in-time compiler.

  • Extensive industry benchmarks show that the JRockit JVM is the fastest JVM in the world

  • In 2008, JRockit was acquired by Oracle.

  • Oracle's work of integrating two excellent virtual machines is roughly completed in JDK8. The way of integration is to transplant the excellent features of JRockit on the basis of HotSpot.

IBM J9

  • Full name: IBM Technology for Java Virtual Machine, referred to as IT4J, internal code name: J9

  • Developed by IBM and widely used in various IBM Java products.

  • Currently, it is one of the three most influential commercial virtual machines and is also known as the fastest Java virtual machine in the world.

  • In 2017, IBM released the open source J9VM, named openJ9, and handed it over to the EClipse Foundation for management

KVM and CDC/CLDC Hotspots

  • Oracle's two virtual machines in the Java ME product line are: CDC/CLDC HotSpot Implementation VM

  • KVM (Kilobyte) is an early product of CLDC-HI

  • KVM is simple, lightweight, and highly portable, and is used on low-end devices: smart controllers, sensors, and non-smart phones

Blue VM

  • Azul VM is a Java virtual machine that Azul Systems has made a lot of improvements on the basis of HotSpot and runs on Azul Systems' proprietary hardware Vega system

  • In 2010, AzulSystems began to shift from hardware to software, and released its own Zing JVM, which can provide features close to the Vega system on the general x86 platform

Liquid VM

  • Developed by BEA, it runs directly on its own Hypervisor system

  • As the JRockit virtual machine ends development, the Liquid vM project also ceases

Apache Harmony

  • Apache launched, a Java runtime platform compatible with JDK1.5 and JDK1.6

  • It is an open source JVM jointly developed by IBM and Intel, suppressed by the equally open source OpenJDK

  • Apache Harmony has not been commercialized on a large scale, but its Java class library code has been absorbed into the Android SDK

Micorsoft JVM

  • In order to support Java Applets in the IE3 browser, Microsoft developed the Microsoft JVM

  • It can only run on the Windows platform, and it was the Java VM with the best performance under Windows at that time

  • In 1997, Sun successfully sued Microsoft for trademark infringement, and Microsoft WindowsXP SP3 erased its VM

Taobao JVM

  • Based on OpenJDK, a customized version of AlibabaJDK, referred to as AJDK, is developed, which is the cornerstone of the entire Alibaba Java system

  • An optimized, deeply customized and open-source high-performance server-version Java virtual machine based on OpenJDK Hotspot VM.

  • The performance of the application on Ali products is high, and the hardware relies heavily on Intel's CPU, which loses compatibility but improves performance

  • At present, it has been launched on Taobao and Tmall, replacing all the official JVM versions of oracle.

Dalvik VM

  • Developed by Google, applied to Android system, and provided JIT in Android2.2, developing rapidly

  • Dalvik VM can only be called a virtual machine, not a "Java virtual machine". It does not follow the Java virtual machine specification and cannot directly execute Java Class files.

  • Based on the register architecture, not the stack architecture of jvm

Grail VM

  • In April 2018, oracle Labs released Graal VM, known as "Run Programs Faster Anywhere"

  • Graal VM is a cross-language full-stack virtual machine enhanced on the basis of HotSpot VM, which can be used as a running platform for "any language", including: Java, Scala, Groovy, Kotlin; C, C++, Javascript, Ruby, Python, R wait

  • Support the mixed use of each other's interfaces and objects in different languages, and support the use of already written local library files in these languages

  • The working principle is to build a language-oriented interpreter through the Truffle tool set, and convert the source code or the intermediate format after source code compilation into an intermediate representation that can be accepted by Graal VM

  • Graal VM most likely to replace HotSpot

2. Class loading subsystem

2.1. Overview of memory structure

  • Class file
  • class loading subsystem
  • runtime data area
    • method area
    • heap
    • program counter
    • virtual machine stack native method stack
  • implement
  • engine
  • native method interface
  • native method library

If you want to write a Java virtual machine by hand, what structures should you mainly consider?

  • class loader
  • execution engine

2.2. Class loader and class loading process

The role of the class loader subsystem

  • The class loader subsystem is responsible for loading Class files from the file system or the network, and class files have specific file identifiers at the beginning of the file.
  • ClassLoader is only responsible for loading class files, and whether it can run is determined by Execution Engine.
  • The loaded class information is stored in a memory space called the method area. In addition to class information, the method area also stores runtime constant pool information, which may also include string literals and numeric constants (this part of constant information is the memory map of the constant pool part in the Class file)

Class loader ClasLoader role

  • The class file exists on the local hard disk. It can be understood as a template drawn by the designer on paper. Finally, when the template is executed, it is loaded into the JVM to instantiate n identical instances based on this file.
  • The class file is loaded into the JVM, called the DNA metadata template, and placed in the method area.
  • In the .class file->JVM->finally become a metadata template, this process requires a transportation tool (Class Loader) to play the role of a courier.

class loading process

/**
 *示例代码
 */
public class HelloLoader {
    public static void main(String[] args) {
        System.out.println("Hello World!");
    }
}

The above sample code is represented by a flowchart:

loading stage

  1. Get the binary byte stream defining this class by its fully qualified name
  2. The static storage structure represented by this byte stream is converted into the runtime data structure of the method area
  3. Generate a java.lang.Class object representing this class in memory as the access entry for this class data in the method area

Supplement: The way to load class files

  • Load directly from local system
  • Obtained through the network, typical scenario: Web Applet
  • Read from the zip archive and become the basis of jar and war formats in the future
  • Runtime calculation generation, the most used is: dynamic proxy technology
  • Generated by other files, typical scenario: JSP application
  • Extract .class files from proprietary databases, relatively rare
  • Obtained from encrypted files, typical protection measures against decompilation of Class files

link phase

  • Verify (Verify) :
    • The purpose is to ensure that the information contained in the byte stream of the Class file meets the requirements of the current virtual machine, to ensure the correctness of the loaded class, and not to endanger the safety of the virtual machine itself.
    • It mainly includes four types of verification, file format verification, metadata verification, bytecode verification, and symbol reference verification.
  • Prepare (Prepare) :
    • Allocate memory for a class variable and set the default initial value of the class variable, which is zero.
    • The static modified with final is not included here, because the final will be allocated at compile time, and the preparation phase will be explicitly initialized;
    • Here, instance variables will not be allocated for initialization, class variables will be allocated in the method area, and instance variables will be allocated to the Java heap along with the object.
  • Resolve :
    • The process of converting symbolic references in the constant pool to direct references.
    • In fact, parsing operations are often accompanied by JVM execution after initialization.
    • A symbolic reference is a set of symbols that describe the referenced object. The literal form of symbol reference is clearly defined in the Class file format of "Java Virtual Machine Specification". A direct reference is a pointer directly to the target, a relative offset, or a handle that indirectly locates the target.
    • The parsing action is mainly for classes or interfaces, fields, class methods, interface methods, method types, etc. Corresponding to CONSTANT_Class_info, CONSTANT_Fieldref_info, CONSTANT_Methodref_info, etc. in the constant pool.

initialization phase

  • The initialization phase is the process of executing the class constructor method <clinit>().
  • This method does not need to be defined, it is the combination of the assignment actions of all class variables in the class automatically collected by the javac compiler and the statements in the static code block.
  • Instructions in a constructor method are executed in the order in which the statements appear in the source file.
  • <clinit>() is different from a class constructor. (Association: the constructor is <init>() from the perspective of the virtual machine)
  • If the class has a parent class, the JVM will ensure that the <clinit>() of the parent class has been executed before the <clinit>() of the subclass is executed.
  • The virtual machine must ensure that the <clinit>() method of a class is locked synchronously under multi-threading.

2.3. Class loader classification

The JVM supports two types of class loaders. They are Bootstrap ClassLoader and User-Defined ClassLoader respectively.

Conceptually speaking, a custom class loader generally refers to a type of class loader customized by the developer in the program, but the Java Virtual Machine Specification does not define it in this way, but loads all classes derived from the abstract class ClassLoader Classifiers are divided into custom class loaders.

No matter how the type of class loader is divided, there are always only three of our most common class loaders in the program, as follows:

The relationship among the four here is a containment relationship. It is not the upper and lower layers, nor is it the inheritance relationship between the parent and child classes.

2.3.1. The loader that comes with the virtual machine

Bootstrap ClassLoader (Bootstrap ClassLoader)

  • This class loading is implemented in C/C++ language and nested inside the JVM.
  • It is used to load Java's core library (JAVA_HOME/jre/lib/rt.jar, resources.jar or content under the sun.boot.class.path path) to provide the classes needed by the JVM itself
  • Does not inherit from ava.lang.ClassLoader, no parent loader.
  • Load extension classes and application class loaders, and specify them as their parent class loaders.
  • For security reasons, the Bootstrap startup class loader only loads classes whose package names start with java, javax, sun, etc.

Extension ClassLoader

  • Written in Java language, implemented by sun.misc.Launcher$ExtClassLoader.
  • Derived from the ClassLoader class
  • The parent class loader is the startup class loader
  • Load the class library from the directory specified by the java.ext.dirs system property, or load the class library from the jre/lib/ext subdirectory (extension directory) of the JDK installation directory. If user-created JARs are placed in this directory, they will also be automatically loaded by the extension class loader.

Application class loader (system class loader, AppClassLoader)

  • Written in java language, implemented by sun.misc.LaunchersAppClassLoader
  • Derived from the ClassLoader class
  • The parent class loader is the extension class loader
  • It is responsible for loading the class library under the path specified by the environment variable classpath 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 can be obtained through the ClassLoader#getSystemclassLoader() method

2.3.2. User-defined class loader

In Java's daily application development, class loading is almost performed by the cooperation of the above three types of loaders. When necessary, we can also customize the class loader to customize the way the class is loaded. Why custom classloader?

  • Loading classes in isolation
  • Modify the way the class is loaded
  • Extended load source
  • Prevent source code leakage

User-defined class loader implementation steps:

  1. Developers can implement their own class loaders by inheriting the abstract class ava.lang.ClassLoader to meet some special needs
  2. Before JDK1.2, when customizing the class loader, always inherit the ClassLoader class and rewrite the loadClass() method, so as to realize the custom class loading class, but after JDK1.2, it is no longer recommended for users to override loadclass() method, it is recommended to write custom class loading logic in the findClass() method
  3. When writing a custom class loader, if there are no too complicated requirements, you can directly inherit the URLClassLoader class, so that you can avoid writing the findClass() method and the way to obtain the bytecode stream yourself, so that the custom class loader Write more concisely.

2.4. Instructions for using ClassLoader

The ClassLoader class is an abstract class, and all subsequent class loaders inherit from ClassLoader (excluding the startup class loader)

sun.misc.Launcher It is the entry application of a java virtual machine

Ways to get ClassLoader

  • Method 1: Get the current ClassLoader
    clazz.getClassLoader()
  • Method 2: Get the ClassLoader of the current thread context
    Thread.currentThread().getContextClassLoader()
  • Method 3: Obtain the ClassLoader of the system
    ClassLoader.getSystemClassLoader()
  • Method 4: Get the ClassLoader of the caller
    DriverManager.getCallerClassLoader()

2.5. Parental delegation mechanism

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

working principle

  1. If a class loader receives a class loading request, it does not load it first, but delegates the request to the parent class loader for execution;
  2. If the parent class loader still has its parent class loader, it will further delegate upwards, recurse in turn, and the request will eventually reach the top-level startup class loader;
  3. If the parent class loader can complete the class loading task, it will return successfully. If the parent class loader cannot complete the loading task, the child loader will try to load it by itself. This is the parent delegation mode.

example

When we load jdbc.jar for database connection, the first thing we need to know is that jdbc.jar is implemented based on the SPI interface, so when loading, parent delegation will be performed, and finally SPI will be loaded from the root loader The core class, and then load the SPI interface class, and then perform reverse delegation, and load the implementation class jdbc.jar through the thread context class loader.

Advantage

  • Avoid duplicate loading of classes
  • Protect the security of the program and prevent the core API from being tampered with at will
    • Custom class: java.lang.String
    • Custom class: java.lang.ShkStart (error: preventing the creation of classes beginning with java.lang)

Sandbox Security Mechanism

Customize the String class, but when loading the custom String class, it will first use the bootstrap class loader to load, and the bootstrap class loader will first load the files that come with jdk during the loading process (java\lang in the rt.jar package \String.class), the error message says that there is no main method, because the string class in the rt.jar package is loaded. This can ensure the protection of the java core source code, which is the sandbox security mechanism.

2.6. Others

How to determine whether two class objects are the same

In the JVM, there are two necessary conditions to indicate whether two class objects are the same class:

  • The full class name of the class must match, including the package name.
  • The ClassLoader (referring to the ClassLoader instance object) that loads this class must be the same.

In other words, in the JVM, even if the two class objects (class objects) originate from the same Class file and are loaded by the same virtual machine, as long as the ClassLoader instance objects that load them are different, the two class objects are also different. equal.

a reference to the class loader

The JVM must know whether a type was loaded by the boot loader or by the user class loader. If a type is loaded by a user class loader, the JVM will save a reference to the class loader as part of the type information in the method area. When resolving a reference from one type to another, the JVM needs to ensure that the class loaders for both types are the same.

Active and passive use of classes

The use of classes by Java programs is divided into: active use and passive use.

Active use can be divided into seven situations:

  • Create an instance of the class
  • Access a static variable of a class or interface, or assign a value to the static variable
  • Calling a static method of a class
  • Reflection (eg: Class.forName("com.atguigu.Test"))
  • Initialize a subclass of a class
  • The class marked as the startup class when the Java virtual machine starts
  • The dynamic language support provided by JDK 7:
    the parsing result of java.lang.invoke.MethodHandle instance
    REF_getStatic, REF_putStatic, REF_invokeStatic handle corresponding class is not initialized, then initialize

In addition to the above seven cases, other ways of using Java classes are regarded as passive use of classes, and will not lead to class initialization.

3. Runtime data area and program counter

3.1. Runtime data area

3.1.1. Overview

This section mainly talks about the runtime data area, which is the part in the figure below, which is the stage after the class loading is completed

 After the previous stages: class loading -> verification -> preparation -> parsing -> initialization are completed, the execution engine will be used to use the class, and the execution engine will use the runtime data area

 Memory is a very important system resource. It is the intermediate warehouse and bridge between the hard disk and the CPU. It carries the real-time operation of the operating system and applications. efficient and stable operation. Different JVMs have some differences in memory division methods and management mechanisms. Combined with the JVM virtual machine specification, let's discuss the classic JVM memory layout.

 The data we get through disk or network IO needs to be loaded into the memory first, and then the CPU gets the data from the memory to read, that is to say, the memory acts as a bridge between the CPU and the disk

The Java virtual machine defines several types of runtime data areas that will be used during the running of the program, some of which will be created when the virtual machine starts and destroyed when the virtual machine exits. Others are one-to-one correspondence with threads, and these data areas corresponding to threads will be created and destroyed as threads start and end.

Gray ones are private to a single thread, red ones are shared by multiple threads. Right now:

  • Each thread: independently includes the program counter, stack, and local stack.
  • Sharing between threads: heap, off-heap memory (permanent generation or metaspace, code cache)

There is only one Runtime instance per JVM. It is the runtime environment, which is equivalent to the frame in the middle of the memory structure: the runtime environment.

3.1.2. Threads

A thread is a unit of execution in a program. JVM allows an application to have multiple threads of execution in parallel. In the Hotspot JVM, each thread maps directly to a native thread of the operating system.

When a Java thread is ready to execute, an operating system native thread is also created at the same time. After the execution of the Java thread terminates, the native thread is also recycled.

The operating system is responsible for scheduling all threads to any available CPU. Once the native thread is successfully initialized, it calls the run() method in the Java thread.

3.1.3. JVM system thread

If you use the console or any debugging tool, you can see that there are many threads running in the background. These background threads do not include the calling public static void main(String[] args)main thread and all threads created by the main thread itself.

These main background system threads are mainly the following in Hotspot JVM:

  • Virtual machine thread: The operation of this thread will only appear when the JVM reaches a safe point. The reason these operations have to happen in different threads is that they all require the JVM to reach a safe point where the heap doesn't change. This type of thread execution includes "stop-the-world" garbage collection, thread stack collection, thread suspension, and biased lock revocation.
  • Periodic task thread: This thread is the embodiment of time period events (such as interrupts), and they are generally used for scheduling execution of periodic operations.
  • GC thread: This thread provides support for different kinds of garbage collection behaviors in the JVM.
  • Compiler thread: This thread compiles bytecode into native code at runtime.
  • Signal dispatch thread: This thread receives a signal and sends it to the JVM, which handles it internally by calling the appropriate method

3.2. Program Counter (PC Register)

In the program counter register (Program Counter Register) in the JVM, the name of the Register is derived from the register of the CPU, and the register stores the scene information related to the instruction. The CPU can only run if it has data loaded into the registers. Here, it is not a physical register in a broad sense. It may be more appropriate to translate it into a PC counter (or instruction counter) (also called a program hook), and it is not easy to cause some unnecessary misunderstandings. The PC register in the JVM is an abstract simulation of the physical PC register.

effect

The PC register is used to store the address pointing to the next instruction, which is the instruction code to be executed. The next instruction is read by the execution engine.

It's such a small memory space that it's almost negligible. It is also the fastest storage area.

In the JVM specification, each thread has its own program counter, which is private to the thread, and its life cycle is consistent with the life cycle of the thread.

There is only one method executing in a thread at any time, the so-called current method. The program counter stores the JVM instruction address of the Java method being executed by the current thread; or, if a native method is being executed, the unspecified value undefined.

It is an indicator of program control flow. Basic functions such as branching, looping, jumping, exception handling, and thread recovery all need to rely on this counter to complete.

When the bytecode interpreter works, it changes the value of this counter to select the next bytecode instruction to be executed.

It is the only area in the Java Virtual Machine Specification that does not specify any OutofMemoryError conditions.

for example

public int minus(){
    intc = 3;
    intd = 4; 
    return c - d;
}

Bytecode file:

0: iconst_3
1: istore_1
2: iconst_4
3: istore_2
4: iload_1
5: iload_2
6: isub
7: ireturn

What is the use of using the PC register to store the bytecode instruction address? Why use the PC register to record the execution address of the current thread?

Because the CPU needs to switch each thread continuously, after switching back at this time, you have to know where to start to continue execution.

The bytecode interpreter of the JVM needs to change the value of the PC register to clarify what bytecode instruction should be executed next.

Why is the PC register set as private?

In order to accurately record the address of the current bytecode instruction being executed by each thread, a PC register is assigned to each thread, and each thread can perform independent calculations without mutual interference.

4. Virtual machine stack

4.1. Virtual machine stack overview

4.1.1. The background of the virtual machine stack

Due to the cross-platform design, Java instructions are designed based on the stack. The CPU architecture of different platforms is different, so it cannot be designed as register-based.

The advantage is that it is cross-platform, the instruction set is small, and the compiler is easy to implement. The disadvantage is that the performance decreases, and more instructions are needed to realize the same function.

4.1.2. Stack and heap in memory

The stack is the unit of runtime, while the heap is the unit of storage

  • The stack solves the running problem of the program, that is, how the program executes, or how to process data.
  • The heap solves the problem of data storage, that is, how and where to put data

4.1.3. Basic content of the virtual machine stack

What is the Java virtual machine stack?

Java Virtual Machine Stack (Java Virtual Machine Stack), also known as the Java stack in the early days. Each thread will create a virtual machine stack when it is created, and each stack frame (Stack Frame) is stored inside, corresponding to each Java method call, which is private to the thread.

life cycle

The life cycle is consistent with the thread

effect

In charge of the operation of the Java program, it saves the local variables and partial results of the method, and participates in the calling and returning of the method.

The characteristics of the stack 

The stack is a fast and efficient way to allocate storage, and its access speed is second only to the sequence counter.

There are only two direct operations of the JVM on the Java stack:

  • Each method is executed, accompanied by push (push, push)
  • Popping work after execution

对于栈来说不存在垃圾回收问题(栈存在溢出的情况)

栈中可能出现的异常

Java 虚拟机规范允许Java栈的大小是动态的或者是固定不变的。

  • 如果采用固定大小的Java虚拟机栈,那每一个线程的Java虚拟机栈容量可以在线程创建的时候独立选定。如果线程请求分配的栈容量超过Java虚拟机栈允许的最大容量,Java虚拟机将会抛出一个StackOverflowError 异常。
  • 如果Java虚拟机栈可以动态扩展,并且在尝试扩展的时候无法申请到足够的内存,或者在创建新的线程时没有足够的内存去创建对应的虚拟机栈,那Java虚拟机将会抛出一个 OutOfMemoryError 异常。
public static void main(String[] args) {
    test();
}
public static void test() {
    test();
}
//抛出异常:Exception in thread"main"java.lang.StackoverflowError
//程序不断的进行递归调用,而且没有退出条件,就会导致不断地进行压栈。

设置栈内存大小

我们可以使用参数 -Xss选项来设置线程的最大栈空间,栈的大小直接决定了函数调用的最大可达深度

public class StackDeepTest{ 
    private static int count=0; 
    public static void recursion(){
        count++; 
        recursion(); 
    }
    public static void main(String args[]){
        try{
            recursion();
        } catch (Throwable e){
            System.out.println("deep of calling="+count); 
            e.printstackTrace();
        }
    }
}

4.2. 栈的存储单位

4.2.1. 栈中存储什么?

每个线程都有自己的栈,栈中的数据都是以栈帧(Stack Frame)的格式存在。

在这个线程上正在执行的每个方法都各自对应一个栈帧(Stack Frame)。

栈帧是一个内存区块,是一个数据集,维系着方法执行过程中的各种数据信息。

4.2.2. 栈运行原理

JVM直接对Java栈的操作只有两个,就是对栈帧的压栈和出栈,遵循“先进后出”/“后进先出”原则。

在一条活动线程中,一个时间点上,只会有一个活动的栈帧。即只有当前正在执行的方法的栈帧(栈顶栈帧)是有效的,这个栈帧被称为当前栈帧(Current Frame),与当前栈帧相对应的方法就是当前方法(Current Method),定义这个方法的类就是当前类(Current Class)。

执行引擎运行的所有字节码指令只针对当前栈帧进行操作。

如果在该方法中调用了其他方法,对应的新的栈帧会被创建出来,放在栈的顶端,成为新的当前帧。

不同线程中所包含的栈帧是不允许存在相互引用的,即不可能在一个栈帧之中引用另外一个线程的栈帧。

If the current method calls other methods, when the method returns, the current stack frame will return the execution result of this method to the previous stack frame, and then, the virtual machine discards the current stack frame, making the previous stack frame become the current stack frame again.

There are two ways to return a function in a Java method, one is a normal function return, using the return instruction; the other is to throw an exception. Either way, the stack frame will be popped.

public class CurrentFrameTest{
    public void methodA(){
        system.out.println("当前栈帧对应的方法->methodA");
        methodB();
        system.out.println("当前栈帧对应的方法->methodA");
    }
    public void methodB(){
        System.out.println("当前栈帧对应的方法->methodB");
    }
}

4.2.3. The internal structure of the stack frame

Each stack frame stores:

  • Local Variables Table (Local Variables)
  • Operand Stack (or Expression Stack)
  • Dynamic Linking (or a method reference to the runtime constant pool)
  • Method return address (Return Address) (or the definition of method exit normally or abnormally)
  • Some additional information​​​​

The stack under each parallel thread is private, so each thread has its own stack, and there are many stack frames in each stack. The size of the stack frame is mainly determined by the local variable table and the operand stack.

4.3. Local Variables

Local variable tables are also called local variable arrays or local variable tables

  • Defined as a numeric array, it is mainly used to store method parameters and local variables defined in the method body. These data types include various basic data types, object references (reference), and returnAddress types.
  • Since the local variable table is built on the thread's stack and is the private data of the thread, there is no data security problem
  • The required capacity of the local variable table is determined at compile time and stored in the maximum local variables data item of the Code attribute of the method. The size of the local variable table is not changed during the execution of the method.
  • The number of method nested calls is determined by the size of the stack. Generally speaking, the larger the stack, the more nested method calls. For a function, the more parameters and local variables it has, the larger the local variable table will be, and the larger its stack frame will be, so as to meet the increasing demand for the information passed by the method call. In turn, function calls will take up more stack space, resulting in a reduction in the number of nested calls.
  • The variables in the local variable table are only valid in the current method call. When the method is executed, the virtual machine completes the transfer process of the parameter value to the parameter variable list by using the local variable table. When the method call ends, as the method stack frame is destroyed, the local variable table will also be destroyed.

4.3.1. Understanding about Slot

  • Local variable table, the most basic storage unit is Slot (variable slot)
  • The storage of the parameter value always starts at index0 of the local variable array and ends at the index of the array length -1.
  • Various basic data types (8 types), reference type (reference), and returnAddress type variables known at compile time are stored in the local variable table.
  • In the local variable table, types within 32 bits only occupy one slot (including the returnAddress type), and types of 64 bits (long and double) occupy two slots. byte, short, and char are converted to int before storage, boolean is also converted to int, 0 means false, non-zero means true.
  • The JVM will assign an access index to each Slot in the local variable table, through which the local variable value specified in the local variable table can be successfully accessed
  • When an instance method is called, its method parameters and local variables defined inside the method body will be copied to each slot in the local variable table in order
  • If you need to access a 64bit local variable value in the local variable table, you only need to use the previous index. (For example: access to long or double type variables)
  • If the current frame is created by a construction method or an instance method, then the object reference this will be stored in the slot with index 0, and the rest of the parameters will continue to be arranged in the order of the parameter list.

4.3.2. Slot reuse

The slots in the local variable table in the stack frame can be reused. If a local variable has passed its scope, the new local variable declared after its scope is likely to reuse the slot of the expired local variable. bit, so as to achieve the purpose of saving resources.

public class SlotTest {
    public void localVarl() {
        int a = 0;
        System.out.println(a);
        int b = 0;
    }
    public void localVar2() {
        {
            int a = 0;
            System.out.println(a);
        }
        //此时的就会复用a的槽位
        int b = 0;
    }
}

 4.3.3. Static variables vs. local variables

After the parameter list is allocated, it is allocated according to the order and scope of the variables defined in the method body.

The class variable table has two opportunities for initialization. The first time is in the "preparation phase", which performs system initialization and sets zero values ​​​​for the class variables, and the other time is in the "initialization" phase, giving the programmer the initial defined in the code value.

Different from class variable initialization, the local variable table does not have a system initialization process, which means that once a local variable is defined, it must be manually initialized, otherwise it cannot be used.

public void test(){
    int i;
    System. out. println(i); //编译不通过
}

4.3.4. Supplementary Notes

In the stack frame, the part most closely related to performance tuning is the local variable table mentioned above. When the method is executed, the virtual machine uses the local variable table to complete the transfer of the method.

The variables in the local variable table are also important garbage collection root nodes, as long as the objects directly or indirectly referenced in the local variable table will not be recycled.

4.4. Operand Stack

In addition to the local variable table, each independent stack frame also contains a last-in-first-out (Last-In-First-Out) operand stack, which can also be called an expression stack (Expression Stack).

Operand stack, in the process of method execution, according to the bytecode instruction, write data to the stack or extract data, that is, push (push) and pop (pop)

  • Some bytecode instructions push values ​​onto the operand stack, others pop operands off the stack. Use them and push the result onto the stack
  • For example: perform operations such as copying, exchanging, and summing

public void testAddOperation(){
    byte i = 15; 
    int j = 8; 
    int k = i + j;
}

 Compiled bytecode instruction information

public void testAddOperation(); 
    Code:
    0: bipush 15
    2: istore_1 
    3: bipush 8
    5: istore_2 
    6:iload_1 
    7:iload_2 
    8:iadd
    9:istore_3 
    10:return

The operand stack is mainly used to save the intermediate results of the calculation process, and at the same time as a temporary storage space for variables during the calculation process.

The operand stack is a working area of ​​the JVM execution engine. When a method starts to execute, a new stack frame will also be created. The operand stack of this method is empty.

Each operand stack has a clear stack depth for storing values. The required maximum depth is defined at compile time and stored in the Code attribute of the method, which is the value of max_stack.

Any element in the stack can be any Java data type

  • The 32bit type occupies a stack unit depth
  • The 64bit type occupies two stack unit depths

The operand stack does not access data by accessing the index, but can only complete a data access through standard push and pop operations

If the called method has a return value, its return value will be pushed into the operand stack of the current stack frame, and the next bytecode instruction to be executed in the PC register will be updated.

The data type of the elements in the operand stack must strictly match the sequence of bytecode instructions, which is verified by the compiler during the compiler, and verified again during the data flow analysis stage of the class verification stage in the class loading process.

It is said that the interpretation engine of the Java virtual machine is a stack-based execution engine, and the stack refers to the operand stack.

4.5. Code Tracking

public void testAddOperation() {
    byte i = 15;
    int j = 8;
    int k = i + j;
}

Use the javap command to decompile the class file: javap -v class name.class

public void testAddoperation(); 
    Code:
  0: bipush 15 
  2: istore_1 
  3: bipush 8
  5: istore_2
  6: iload_1
  7: iload_2
  8: iadd
  9: istore_3
    10: return

  

4.6. Top Of Stack Cashing Technology

As mentioned earlier, the zero-address instructions used by the virtual machine based on the stack architecture are more compact, but it is necessary to use more push and pop instructions to complete an operation, which also means that more instructions will be required. The number of instruction dispatch and memory read/write times.

Since operands are stored in memory, frequent execution of memory read/write operations will inevitably affect execution speed. In order to solve this problem, the designers of HotSpot JVM proposed the top-of-stack cache (Tos, Top-of-Stack Cashing) technology, which caches all the top elements of the stack in the registers of the physical CPU, thereby reducing the read/write of memory times to improve the execution efficiency of the execution engine.

4.7. Dynamic Linking

Dynamic link, method return address, additional information: Some places are called frame data area

Each stack frame internally contains a reference to the method to which the stack frame belongs in the runtime constant pool. The purpose of including this reference is to support the code of the current method to achieve dynamic linking (Dynamic Linking). For example: invokedynamic command

When a Java source file is compiled into a bytecode file, all variable and method references are stored as symbolic references (Symbolic Reference) in the constant pool of the class file. For example: when describing that a method calls another method, it is represented by a symbolic reference pointing to the method in the constant pool, then the function of dynamic linking is to convert these symbolic references into direct references to the calling method.

 The role of the constant pool is to provide some symbols and constants to facilitate the identification of instructions

4.8. Method call: parsing and assignment

In the JVM, the conversion of symbolic references to direct references for calling methods is related to the method's binding mechanism

4.8.1. Static linking

When a bytecode file is loaded into the JVM, if the called target method is known at compile time and remains unchanged at runtime, the process of converting the symbolic reference of the calling method into a direct reference is called static link

4.8.2. Dynamic linking

If the called method cannot be determined during compilation, the symbol of the called method can only be converted into a direct reference during program runtime. Since this reference conversion process is dynamic, it is also called dynamic linking.

Static linking and dynamic linking are not nouns, but verbs, which is the key to understanding.

The binding mechanism of the corresponding method is: early binding (Early Binding) and late binding (Late Binding). Binding is a process where a symbolic reference to a field, method, or class is replaced by a direct reference, which happens only once.

4.8.3. Early Binding

Early binding means that if the called target method is known at compile time and remains unchanged at runtime, the method can be bound to the type to which it belongs. Which one is it, so you can use static linking to convert symbolic references to direct references.

4.8.4. Late Binding

If the called method cannot be determined at compile time, the related method can only be bound according to the actual type at program runtime. This binding method is also called late binding.

With the emergence of high-level languages, there are more and more object-oriented programming languages ​​similar to Java. Although these programming languages ​​have certain differences in grammatical style, they always maintain a commonality with each other. , that is, they all support object-oriented features such as encapsulation, inheritance, and polymorphism. Since this type of programming language has polymorphic features, it naturally has two binding methods: early binding and late binding.

Any ordinary method in Java actually has the characteristics of virtual functions, which are equivalent to virtual functions in C language (in C, you need to use the keyword virtual to explicitly define). If you do not want a method to have the characteristics of a virtual function in a Java program, you can use the keyword final to mark this method.

4.8.5. Virtual and non-virtual methods

If the method determines the specific calling version at compile time, this version is immutable at runtime. Such methods are called non-virtual methods.

Static methods, private methods, final methods, instance constructors, and superclass methods are all non-virtual methods. Other methods are called virtual methods.

Parsing can be done during the parsing phase of class loading. The following is an example of a non-virtual method:

class Father{
    public static void print(String str){
        System. out. println("father "+str); 
    }
    private void show(String str){
        System. out. println("father"+str);
    }
}
class Son extends Father{
    public class VirtualMethodTest{
        public static void main(String[] args){
            Son.print("coder");
            //Father fa=new Father();
            //fa.show("atguigu.com");
        }
    }

The following method call instructions are provided in the virtual machine:

Ordinary call instruction :

  • invokestatic: Invokes a static method, and the parsing phase determines the only method version
  • invokespecial: Invoke methods, private and parent methods, and the only method version is determined in the parsing phase
  • invokevirtual: call all virtual methods
  • invokeinterface: call interface method

Dynamic call instruction:

  • invokedynamic: dynamically parse out the method that needs to be called, and then execute

The first four instructions are solidified inside the virtual machine, and the execution of the method call cannot be human-intervened, while the invokedynamic instruction supports the user to determine the method version. Among them, the methods invoked by the invokestatic instruction and the invokespecial instruction are called non-virtual methods, and the rest (except those modified by fina1) are called virtual methods.

About the invokednamic command

  • The JVM bytecode instruction set has always been relatively stable. It was not until Java7 that an invokedynamic instruction was added. This is an improvement made by Java to support "dynamic type language".
  • However, Java7 does not provide a method to directly generate invokedynamic instructions, and it is necessary to use ASM, a low-level bytecode tool, to generate invokedynamic instructions. Until the emergence of Java8's Lambda expression and the generation of invokedynamic instructions, there was no direct way to generate them in Java.
  • The essence of the dynamic language type support added in Java7 is the modification of the Java virtual machine specification, not the modification of the Java language rules. This part is relatively complicated, and the method call in the virtual machine is added. The most direct beneficiary It is a dynamic language compiler running on the Java platform.

Dynamically typed language and statically typed language

The difference between a dynamically typed language and a statically typed language lies in whether the type is checked at compile time or at runtime. If the former is satisfied, it is a statically typed language, and vice versa, it is a dynamically typed language.

To put it more bluntly, a static type language is to judge the type information of the variable itself; a dynamic type language is to judge the type information of the variable value, the variable has no type information, and the variable value has type information, which is an important feature of the dynamic language .

4.8.6. The nature of method overriding

The essence of method rewriting in the Java language:

  1. Find the actual type of the object executed by the first element at the top of the operand stack, denoted C.
  2. If a method that matches the description in the constant and the simple name is found in the type C, the access authority check is performed, and if it passes, the direct reference of this method is returned, and the search process ends; if not, it returns java.lang. IllegalAccessError exception.
  3. Otherwise, according to the inheritance relationship from bottom to top, carry out the search and verification process in the second step for each parent class of C.
  4. If no suitable method is found, a java.1ang.AbstractMethodserror exception is thrown.

Introduction to IllegalAccessError

The program attempted to access or modify a property or call a method that you do not have permission to access. Normally, this will cause a compiler exception. This error, if it occurs at runtime, indicates that an incompatible change has taken place in a class.

4.8.7. Method call: virtual method table

In object-oriented programming, dynamic dispatch is frequently used. If you have to search for a suitable target in the method metadata of the class during each dynamic dispatch, it may affect the execution efficiency. Therefore, in order to improve performance, the JVM implements it by creating a virtual method table (virtual method table) in the method area of ​​the class (non-virtual methods will not appear in the table). Use indexed tables instead of lookups.

Each class has a virtual method table, which stores the actual entry of each method.

When was the virtual method table created?

The virtual method table will be created and initialized during the linking phase of class loading. After the initial value of the variable of the class is prepared, the JVM will also initialize the method table of the class.

Example 1:

 Example 2:

4.9. Method return address (return address)

Store the value of the pc register calling this method. There are two ways to end a method:

  • normal execution completed
  • Unhandled exception occurred, abnormal exit

No matter which method is used to exit, after the method exits, it returns to the place where the method was called. When the method exits normally, the value of the caller's pc counter is used as the return address, that is, the address of the next instruction after the instruction that calls the method. For those who exit by exception, the return address must be determined through the exception table, and this part of information is generally not saved in the stack frame.

After a method starts executing, there are only two ways to exit the method:

  1. When the execution engine encounters a bytecode instruction (return) returned by any method, the return value will be passed to the upper-layer method caller, referred to as the normal completion exit;
    1. After a method is called normally, which return instruction needs to be used depends on the actual data type of the method return value.
    2. In bytecode instructions, return instructions include ireturn (used when the return value is boolean, byte, char, short, and int), lreturn (Long type), freturn (Float type), dreturn (Double type), and areturn. In addition, there is a method declared as void by the return instruction, which is used by instance initialization methods, class and interface initialization methods.
  2. An exception (Exception) is encountered during the execution of the method, and the exception is not handled within the method, that is, as long as no matching exception handler is found in the exception table of the method, the method will exit, referred to as abnormal completion exit.

During method execution, the exception handling when an exception is thrown is stored in an exception handling table, so that it is convenient to find the exception handling code when an exception occurs

Exception table:
from to target type
4	 16	  19   any
19	 21	  19   any

In essence, the exit of the method is the process of popping the current stack frame. At this time, it is necessary to restore the local variable table of the upper method, the operand stack, the operand stack that pushes the return value into the caller's stack frame, set the PC register value, etc., so that the caller method can continue to execute.

The difference between the normal completion exit and the abnormal completion exit is that the exit through the abnormal completion exit will not produce any return value to its upper caller.

4.10. Some additional information

Some additional information related to the implementation of the Java virtual machine is also allowed to be carried in the stack frame. For example: information that provides support for program debugging.

4.11. Stack-related interview questions

  • Example of stack overflow? (StackOverflowError)
    • Set the size of the stack by -Xss
  • Can adjusting the stack size ensure that there will be no overflow?
    • not guaranteed not to overflow
  • Is the larger the stack memory allocated, the better?
    • No, the probability of OOM is reduced for a certain period of time, but it will occupy other thread spaces because the entire space is limited.
  • Does garbage collection involve the virtual machine stack?
    • Won't
  • Are local variables defined in methods thread-safe?
    • Analyze specific issues. If the object is generated internally and dies internally without being returned to the outside, then it is thread-safe, otherwise it is thread-unsafe.

5. Native method interface and native method stack

5.1. What are native methods?

Simply put, a Native Method is an interface for Java to call non-Java code. A Native Method is a Java method whose implementation is implemented in a non-Java language, such as C. This feature is not unique to Java. Many other programming languages ​​have this mechanism. For example, in C, you can use extern "c" to tell the c compiler to call a c function.

A native method is a Java method whose implementation is provided by non-java code.

When defining a native method, the implementation body is not provided (somewhat like defining a Java interface), because the implementation body is implemented by a non-java language outside.

The role of the local interface is to integrate different programming languages ​​for Java, and its original intention is to integrate C/C++ programs.

5.2. Why use Native Method?

Java is very convenient to use, but it is not easy to implement certain levels of tasks in Java, or when we care about the efficiency of the program, problems arise.

Interaction with the Java environment

Sometimes Java applications need to interact with the environment outside of Java, which is the main reason for the existence of native methods. This is the case when Java needs to exchange information with some underlying system, like an operating system or some hardware.

The native method is just such a communication mechanism: it provides us with a very concise interface, and we don't need to understand the tedious details outside the Java application.

Interaction with the operating system

The JVM supports the Java language itself and the runtime library. It is the platform on which Java programs live. It consists of an interpreter (interpreting bytecode) and some libraries connected to native code. However, it is not a complete system after all, and it often depends on the support of an underlying system. These underlying systems are often powerful operating systems. By using native methods, we can use Java to realize the interaction between jre and the underlying system, and even some parts of JVM are written in c. Also, if we want to use some features of the encapsulated operating system that the Java language itself does not provide, we also need to use native methods.

Sun's Java

Sun's interpreter is implemented in C, which allows it to interact with the outside world like some ordinary C. Most of the jre is implemented in Java, and it also interacts with the outside world through some native methods.

For example: the setPriority() method of the class java.lang.Thread is implemented in Java, but it calls the local method setPriority() in the class. This native method is implemented in C and is implanted inside the JVM. On the Windows 95 platform, this native method will eventually call the Win32 setPriority() API. This is a specific implementation of a local method directly provided by the JVM, and more often the local method is provided by an external dynamic link library (external dynamic link library) and then called by the JVM.

status quo

At present, this method is used less and less, except for hardware-related applications, such as driving printers through Java programs or managing production equipment through Java systems, which are relatively rare in enterprise-level applications. Because the communication between heterogeneous fields is very developed now, for example, Socket communication can be used, and Web Service can also be used, etc., so I won’t introduce much.

5.3. Native method stack

The Java virtual machine stack is used to manage the calls of Java methods, and the native method stack is used to manage the calls of native methods.

The native method stack is also thread-private.

Allows to be implemented as fixed or dynamically expandable memory size. (same in terms of out-of-memory)

  • If the stack size requested by the thread exceeds the maximum size allowed by the native method stack, the Java virtual machine will throw a StackOverflowError exception.
  • If the native method stack can be dynamically expanded, and cannot apply for enough memory when trying to expand, or if there is not enough memory to create the corresponding native method stack when creating a new thread, then the Java virtual machine will throw an OutOfMemoryError abnormal.

Native methods are implemented using the C language.

Its specific method is to register the native method in the Native Method Stack, and load the native method library when the Execution Engine executes.

 When a thread calls a native method, it enters a whole new world that is no longer limited by the virtual machine. It has the same permissions as the virtual machine.

  • Native methods can access the runtime data area inside the virtual machine through the native method interface.
  • It can even directly use registers in the local processor
  • Allocate any amount of memory directly from the heap in native memory.

Not all JVMs support native methods. Because the Java virtual machine specification does not clearly require the language used, specific implementation methods, data structures, etc. of the local method stack. If the JVM product does not intend to support native methods, it is also unnecessary to implement the native method stack

In the Hotspot JVM, the local method stack and the virtual machine stack are directly combined into one.

6. Heap

6.1. Core overview of Heap

The heap is unique to a JVM process, that is, a process has only one JVM, but the process contains multiple threads, and they share the same heap space.

There is only one heap memory in a JVM instance, and the heap is also the core area of ​​Java memory management.

The Java heap area is created when the JVM starts, and is the largest memory space managed by the JVM.

The "Java Virtual Machine Specification" stipulates that the heap can be in a physically discontinuous memory space, but logically it should be considered continuous.

All threads share the Java heap, but can also divide thread-private buffers (Thread Local Allocation Buffer).

The description of the Java heap in the "Java Virtual Machine Specification" is: All object instances and arrays should be allocated on the heap at runtime.

The heap is the run-time data area from which memory for all class instances and arrays is allocated。

Arrays and objects may never be stored on the stack because the stack frame holds a reference to the location of the object or array on the heap.

After the method ends, the objects in the heap will not be removed immediately, but will only be removed during garbage collection.

The heap is the key area for GC (Garbage Collection, Garbage Collector) to perform garbage collection.

6.1.1. Heap Memory Subdivision

Java 7 and earlier heap memory is logically divided into three parts: newborn area + retirement area + permanent area

  • Young Generation Space Young/New is divided into Eden District and Survivor District
  • Tenure generation space Old/Tenure
  • Permanent Space Perm

Java 8 and later heap memory is logically divided into three parts: newborn area + retirement area + metaspace

  • Young Generation Space Young/New is divided into Eden District and Survivor District
  • Tenure generation space Old/Tenure
  • Meta Space Meta Space Meta

Agreement: new generation area (generation) <=> young generation, retirement area <=> elderly area (generation), permanent area <=> permanent generation

6.1.2. Internal structure of heap space (JDK7) 

6.1.3. Internal structure of heap space (JDK8)

6.2. Setting the heap memory size and OOM

6.2.1. Setting the size of the heap space

The Java heap area is used to store Java object instances, so the size of the heap has been set when the JVM starts, and you can set it through the options "-Xmx" and "-Xms".

  • "-Xms" is used to indicate the starting memory of the heap area, which is equivalent to-XX:InitialHeapSize
  • "-Xmx" is used to indicate the maximum memory of the heap area, which is equivalent to-XX:MaxHeapSize

Once the memory size in the heap area exceeds the maximum memory specified by "-Xmx", an OutOfMemoryError exception will be thrown.

Usually, the two parameters -Xms and -Xmx are configured with the same value. The purpose is to improve performance without re-partitioning the size of the computing heap after the ava garbage collection mechanism cleans up the heap.

by default

  • Initial memory size: physical computer memory size / 64
  • Maximum memory size: physical computer memory size / 4

6.2.2. Example of OutOfMemory

/**
* -Xms10m -Xmx10m
**/
public class OOMTest {
    public static void main(String[]args){
        ArrayList<Picture> list = new ArrayList<>();
        while(true){
            try {
                Thread.sleep(20);
            } catch (InterruptedException e){
                e.printStackTrace();
            }
            list.add(new Picture(new Random().nextInt(1024*1024)));
        }
    }
}
Exception in thread "main" java.lang.OutofMemoryError: Java heap space
    at com.atguigu. java.Picture.<init>(OOMTest. java:25)
    at com.atguigu.java.O0MTest.main(OOMTest.java:16)

6.3. Young Generation and Old Generation

Java objects stored in the JVM can be divided into two categories:

  • One type is a transient object with a short life cycle, which is created and destroyed very quickly
  • The life cycle of another type of object is very long, and in some extreme cases, it can be consistent with the life cycle of the JVM

If the Java heap area is further subdivided, it can be divided into young generation (YoungGen) and old generation (oldGen)

Among them, the young generation can be divided into Eden space, Survivor0 space and Survivor1 space (sometimes also called from area, to area)

 The following parameters are generally not adjusted during development: 

Configure the proportion of the new generation and the old generation in the heap structure.

  • By default -XX:NewRatio=2, it means that the young generation occupies 1, the old generation occupies 2, and the young generation occupies 1/3 of the entire heap
  • It can be modified -XX:NewRatio=4, indicating that the new generation occupies 1, the old generation occupies 4, and the new generation occupies 1/5 of the entire heap

However, since the JVM enables the adaptive memory allocation policy (-XX:+UseAdaptiveSizePolicy) by default, the ratio of the new generation to the old generation may not be 1:2 when observed.

In HotSpot, the default ratio of the Eden space to the other two survivor spaces is 8:1:1. -XX:SurvivorRatioThis ratio can be adjusted by options , such as -XX:SurvivorRatio=8

Almost all Java objects are newly created in the Eden area. Most of the destruction of Java objects is carried out in the new generation. (IBM's special research shows that 80% of the objects in the new generation are "live and die")

You can use the option " -Xmn" to set the maximum memory size of the new generation. This parameter generally uses the default value.

(-XX:NewRatio和-Xmn同时设置时以-Xmn为准)

6.4. Graphical object allocation process

Allocating memory for new objects is a very rigorous and complex task. JVM designers not only need to consider how and where to allocate memory, but also need to consider GC because the memory allocation algorithm is closely related to the memory recovery algorithm. Whether memory fragmentation will be generated in the memory space after memory reclamation is performed.

  1. The new objects are placed in the Eden area first. There is a size limit for this zone.
  2. When the space in Eden is full, the program needs to create objects again, and the garbage collector of the JVM will perform garbage collection (MinorGC) on the Eden area to destroy the objects in the Eden area that are no longer referenced by other objects. Then load the new object and put it in the Eden Park
  3. Then move the remaining objects in Eden to Survivor 0.
  4. If garbage collection is triggered again, the ones that survived last time will be placed in Survivor 0. If they are not recycled, they will be placed in Survivor 1.
  5. If you go through garbage collection again, it will be put back into Survivor 0 at this time, and then go to Survivor 1 again.
  6. When can I go to the nursing home? The number of times can be set. The default is 15 times. Parameters can be set: set-Xx:MaxTenuringThreshold= N
  7. In the elderly care area, it is relatively leisurely. When the memory in the retirement area is insufficient, GC: Major GC is triggered again to clean up the memory in the retirement area
  8. If the Elderly District executes Major GC and finds that objects cannot be saved, an OOM exception will occur.

flow chart

Summarize

  • Summary for the survivors s0 and s1 areas: there is an exchange after copying, whoever is empty and who is to
  • About garbage collection: frequently collected in the new area, rarely collected in the old area, and almost never collected in the permanent generation and meta space

常用调优工具(在JVM下篇:性能监控与调优篇会详细介绍)

  • JDK命令行
  • Eclipse:Memory Analyzer Tool
  • Jconsole
  • VisualVM
  • Jprofiler
  • Java Flight Recorder
  • GCViewer
  • GC Easy

6.5. Minor GC,MajorGC、Full GC

JVM在进行GC时,并非每次都对上面三个内存区域一起回收的,大部分时候回收的都是指新生代。

针对Hotspot VM的实现,它里面的GC按照回收区域又分为两大种类型:一种是部分收集(Partial GC),一种是整堆收集(FullGC)

  • 部分收集:不是完整收集整个Java堆的垃圾收集。其中又分为:
    • 新生代收集(Minor GC / Young GC):只是新生代的垃圾收集
    • 老年代收集(Major GC / Old GC):只是老年代的圾收集。
      • 目前,只有CMSGC会有单独收集老年代的行为。
      • 注意,很多时候Major GC会和Full GC混淆使用,需要具体分辨是老年代回收还是整堆回收。
    • 混合收集(MixedGC):收集整个新生代以及部分老年代的垃圾收集。
      • 目前,只有G1 GC会有这种行为
  • 整堆收集(Full GC):收集整个java堆和方法区的垃圾收集。

6.5.1. 最简单的分代式GC策略的触发条件

年轻代GC(Minor GC)触发机制

  • 当年轻代空间不足时,就会触发MinorGC,这里的年轻代满指的是Eden代满,Survivor满不会引发GC。(每次Minor GC会清理年轻代的内存。)
  • 因为Java对象大多都具备朝生夕灭的特性.,所以Minor GC非常频繁,一般回收速度也比较快。这一定义既清晰又易于理解。
  • Minor GC会引发STW,暂停其它用户的线程,等垃圾回收结束,用户线程才恢复运行

老年代GC(Major GC / Full GC)触发机制

  • 指发生在老年代的GC,对象从老年代消失时,我们说 “Major GC” 或 “Full GC” 发生了
  • Major Gc appears, often accompanied by at least one Minor GC (but not absolute, in the collection strategy of Paralle1 Scavenge collector, there is a strategy selection process for MajorGC directly)
    • That is, when there is insufficient space in the old generation, it will try to trigger Minor Gc first. If there is not enough space after that, Major GC is triggered
  • The speed of Major GC is generally more than 10 times slower than that of Minor GC, and the time of STW is longer
  • If the memory is not enough after Major GC, OOM will be reported

Full GC trigger mechanism (detailed later):

There are five situations that trigger Full GC execution:

  1. When calling System.gc(), the system suggests to execute Full GC, but not necessarily
  2. Insufficient space in the old generation
  3. Insufficient space in method area
  4. After Minor GC, the average size of the old generation is larger than the available memory of the old generation
  5. When copying from Eden area, survivor space0 (From Space) area to survivor space1 (To Space) area, if the object size is larger than the available memory of To Space, the object is transferred to the old generation, and the available memory of the old generation is smaller than the object size

Explanation: Full GC should be avoided as much as possible during development or tuning. This will temporarily shorten the time

6.6. Heap space generation idea

Why should the Java heap be divided into generations? Doesn't it work properly regardless of generation?

After research, different objects have different life cycles. 70%-99% of objects are temporary objects.

  • The new generation: consists of Eden and two survivors of the same size (also known as from/to, s0/s1), and to is always empty.
  • Old generation: stores objects that survive multiple GCs in the new generation.

In fact, it is completely possible to not divide into generations. The only reason for generation is to optimize GC performance. If there is no generation, then all the objects are together, just like shutting all the people in a school in a classroom. During GC, it is necessary to find which objects are useless, so that all areas of the heap will be scanned. And many objects are born and die, if it is divided into generations, put the newly created object in a certain place, and when the GC, first recycle the area that stores the "lived and died" objects, so that it will be freed. Come out with a lot of space.

6.7. Memory allocation strategy

If the object is still alive after Eden is born and after the first Minor GC, and can be accommodated by Survivor, it will be moved to the survivor space, and the object age will be set to 1. Every time the object survives a MinorGC in the survivor area, its age will increase by 1 year. When its age increases to a certain level (the default is 15 years old, in fact, each JVM and each GC are different), it will be promoted. to the old age.

The age threshold for objects to be promoted to the old generation can be -XX:MaxTenuringThresholdset through options.

The principles of object allocation for different age groups are as follows:

  • Priority allocation to Eden
  • Large objects are directly allocated to the old generation (try to avoid too many large objects in the program)
  • Long-lived objects are allocated to the old generation
  • Dynamic object age judgment: If the sum of the size of all objects of the same age in the survivor area is greater than half of the Survivor space, objects whose age is greater than or equal to this age can directly enter the old age without waiting for the required age MaxTenuringThreshold.
  • Space Allocation Guarantee:-XX:HandlePromotionFailure

6.8. Allocating memory for objects: TLAB

6.8.1. Why is there TLAB (Thread Local Allocation Buffer)?

  • The heap area is a thread-shared area, and any thread can access the shared data in the heap area
  • Since the creation of object instances is very frequent in the JVM, it is not thread-safe to divide the memory space from the heap in a concurrent environment
  • In order to prevent multiple threads from operating the same address, it is necessary to use mechanisms such as locking, which will affect the allocation speed.

6.8.2. What is TLAB?

  • From the perspective of memory model rather than garbage collection, the Eden area continues to be divided, and the JVM allocates a private cache area for each thread, which is included in the Eden space.
  • When multiple threads allocate memory at the same time, using TLAB can avoid a series of non-thread-safety problems, and can also improve the throughput of memory allocation, so we can call this memory allocation method a fast allocation strategy.
  • All JVMs derived from OpenJDK provide a TLAB design.

6.8.3. Re-specification of TLAB

  • Although not all object instances can successfully allocate memory in TLAB, the JVM uses TLAB as the first choice for memory allocation.
  • In the program, the developer can -XX:UseTLABset whether to open the TLAB space through the option " ".
  • 默认情况下,TLAB空间的内存非常小,仅占有整个Eden空间的1%,可以通过选项 -XX:TLABWasteTargetPercent”设置TLAB空间所占用Eden空间的百分比大小。
  • 一旦对象在TLAB空间分配内存失败时,JVM就会尝试着通过使用加锁机制确保数据操作的原子性,从而直接在Eden空间中分配内存

6.9. 小结:堆空间的参数设置

官网地址:java官网参考文档

// 详细的参数内容会在JVM下篇:性能监控与调优篇中进行详细介绍,这里先熟悉下
-XX:+PrintFlagsInitial  //查看所有的参数的默认初始值
-XX:+PrintFlagsFinal  //查看所有的参数的最终值(可能会存在修改,不再是初始值)
-Xms  //初始堆空间内存(默认为物理内存的1/64)
-Xmx  //最大堆空间内存(默认为物理内存的1/4)
-Xmn  //设置新生代的大小。(初始值及最大值)
-XX:NewRatio  //配置新生代与老年代在堆结构的占比
-XX:SurvivorRatio  //设置新生代中Eden和S0/S1空间的比例
-XX:MaxTenuringThreshold  //设置新生代垃圾的最大年龄
-XX:+PrintGCDetails //输出详细的GC处理日志
//打印gc简要信息:①-Xx:+PrintGC ② - verbose:gc
-XX:HandlePromotionFalilure://是否设置空间分配担保

在发生Minor GC之前,虚拟机会检查老年代最大可用的连续空间是否大于新生代所有对象的总空间。

  • 如果大于,则此次Minor GC是安全的
  • 如果小于,则虚拟机会查看-XX:HandlePromotionFailure设置值是否允担保失败。
    • 如果HandlePromotionFailure=true,那么会继续检查老年代最大可用连续空间是否大于历次晋升到老年代的对象的平均大小。
      • 如果大于,则尝试进行一次Minor GC,但这次Minor GC依然是有风险的;
      • 如果小于,则改为进行一次Full GC。
    • 如果HandlePromotionFailure=false,则改为进行一次Full Gc。

在JDK6 Update24之后,HandlePromotionFailure参数不会再影响到虚拟机的空间分配担保策略,观察openJDK中的源码变化,虽然源码中还定义了HandlePromotionFailure参数,但是在代码中已经不会再使用它。JDK6 Update 24之后的规则变为只要老年代的连续空间大于新生代对象总大小或者历次晋升的平均大小就会进行Minor GC,否则将进行FullGC。

6.10. 堆是分配对象的唯一选择么?

在《深入理解Java虚拟机》中关于Java堆内存有这样一段描述:

随着JIT编译期的发展与逃逸分析技术逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙的变化,所有的对象都分配到堆上也渐渐变得不那么“绝对”了。

In the Java virtual machine, objects are allocated memory in the Java heap, which is a common knowledge. However, there is a special case, that is, if after escape analysis (Escape Analysis), it is found that an object does not escape the method, then it may be optimized to be allocated on the stack. This eliminates the need to allocate memory on the heap and garbage collection. This is also the most common off-heap storage technique.

In addition, the aforementioned TaoBaoVM based on the deep customization of OpenJDK, in which the innovative GCIH (GC invisible heap) technology implements off-heap, moves Java objects with a long life cycle from the heap to outside the heap, and GC cannot manage the internals of GCIH Java objects, in order to achieve the purpose of reducing the frequency of GC recovery and improving the efficiency of GC recovery.

6.10.1. Overview of Escape Analysis

How to allocate objects on the heap to the stack requires the use of escape analysis.

This is a cross-function global data flow analysis algorithm that can effectively reduce the synchronization load and memory heap allocation pressure in Java programs.

Through escape analysis, the Java Hotspot compiler can analyze the scope of use of a new object reference and decide whether to allocate this object on the heap.

The basic behavior of escape analysis is to analyze the dynamic scope of objects:

  • When an object is defined in a method, and the object is only used inside the method, it is considered that no escape has occurred.
  • When an object is defined in a method and it is referenced by an external method, it is considered to have escaped. For example, as a call parameter passed to other places.

Example 1

public void my_method() {
    V v = new V();
    // use v
    // ....
    v = null;
}

Objects that have not escaped can be allocated to the stack. With the end of the method execution, the stack space is removed, and each stack contains many stack frames.

public static StringBuffer createStringBuffer(String s1, String s2) {
    StringBuffer sb = new StringBuffer();
    sb.append(s1);
    sb.append(s2);
    return sb;
}

If the above method StringBuffer sbdoes not want to escape, you can write it like this

public static String createStringBuffer(String s1, String s2) {
    StringBuffer sb = new StringBuffer();
    sb.append(s1);
    sb.append(s2);
    return sb.toString();
}

parameter settings

After the JDK 6u23 version, escape analysis has been enabled by default in HotSpot

If using an earlier version, developers can:

  • Option " -XX:+DoEscapeAnalysis" to explicitly enable escape analysis
  • Use the option " -XX:+PrintEscapeAnalysis" to view the filtering results of escape analysis

Conclusion : If local variables can be used in development, do not use them defined outside the method.

6.10.2. Escape Analysis: Code Optimization

Using escape analysis, the compiler can optimize the code as follows:

1. Stack allocation: convert heap allocation to stack allocation. If an object is allocated in a subroutine, the object may be a candidate for allocation on the stack rather than on the heap if a pointer to the object never escapes

2. Synchronization omission: If an object is found to be accessed by only one thread, then the operation of this object may not consider synchronization.

3. Separation of objects or scalar replacement: Some objects may not need to exist as a continuous memory structure and can be accessed, then part (or all) of the object may not be stored in memory, but stored in CPU registers.

stack allocation

According to the results of escape analysis during compilation, the JIT compiler finds that if an object does not escape the method, it may be optimized to be allocated on the stack. After the allocation is completed, it continues to execute in the call stack, and finally the thread ends, the stack space is reclaimed, and the local variable object is also reclaimed. This eliminates the need for garbage collection.

Common stack allocation scenarios

In the escape analysis, it has been explained. They are assigning values ​​to member variables, method return values, and passing instance references.

synchronous omission

The cost of thread synchronization is quite high, and the consequence of synchronization is to reduce concurrency and performance.

When dynamically compiling a synchronization block, the JIT compiler can use escape analysis to determine whether the lock object used by the synchronization block can only be accessed by one thread and has not been released to other threads. If not, then the JIT compiler will cancel the synchronization of this part of the code when compiling the synchronization block. This can greatly improve concurrency and performance. This process of canceling synchronization is called synchronization elision, also called lock elimination.

example

public void f() {
    Object hellis = new Object();
    synchronized(hellis) {
        System.out.println(hellis);
    }
}

The hellis object is locked in the code, but the life cycle of the hellis object is only in the f() method, and will not be accessed by other threads, so it will be optimized in the JIT compilation stage, and optimized into:

public void f() {
    Object hellis = new Object();
	System.out.println(hellis);
}

scalar substitution

A scalar is a piece of data that cannot be broken down into smaller pieces. The primitive data types in Java are scalars.

In contrast, those data that can be decomposed are called aggregates, and objects in Java are aggregates because they can be decomposed into other aggregates and scalars.

In the JIT stage, if after escape analysis, it is found that an object will not be accessed by the outside world, then after JIT optimization, the object will be disassembled into several member variables contained in it to replace it. This process is known as scalar substitution.

example

public static void main(String args[]) {
    alloc();
}
private static void alloc() {
    Point point = new Point(1,2);
    System.out.println("point.x" + point.x + ";point.y" + point.y);
}
class Point {
    private int x;
    private int y;
}

The above code, after scalar substitution, becomes

private static void alloc() {
    int x = 1;
    int y = 2;
    System.out.println("point.x = " + x + "; point.y=" + y);
}

It can be seen that after the escape analysis of the aggregated quantity Point, it was found that it did not escape, so it was replaced with two scalars. So what are the benefits of scalar substitution? That is, it can greatly reduce the heap memory usage. Because once there is no need to create objects, then there is no need to allocate heap memory. Scalar substitution provides a good basis for on-stack allocation.

Scalar substitution parameter setting

Parameters -XX:EliminateAllocations: Scalar replacement is turned on (default is turned on), allowing objects to be scattered and allocated on the stack.

The above code performs 100 million allocs in the main function. Call to create an object. Since the User object instance needs to occupy about 16 bytes of space, the cumulative allocated space reaches nearly 1.5GB. If the heap space is less than this value, GC will inevitably occur. Run the above code with the following parameters:

-server -Xmx100m -Xms100m -XX:+DoEscapeAnalysis -XX:+PrintGC -XX:+EliminateAllocations

Here the parameters are set as follows:

  • Parameters -server: Start Server mode, because escape analysis can only be enabled in server mode.
  • Parameters -XX:+DoEscapeAnalysis: enable escape analysis
  • Parameters -Xmx10m: Specifies the maximum heap space of 10MB
  • Parameters -XX:+PrintGC: Gc log will be printed
  • Parameters -XX:+EliminateAllocations: Enable scalar replacement (open by default), allowing objects to be scattered and allocated on the stack. For example, if an object has two fields, id and name, then these two fields will be treated as two independent local variables for allocation

6.10.3. Escape Analysis Summary: Escape Analysis Is Not Mature

The paper on escape analysis was published in 1999, but it was not realized until JDK1.6, and this technology is not very mature until now.

The fundamental reason is that there is no guarantee that the performance consumption of escape analysis will be higher than his consumption. Although after escape analysis, scalar replacement, stack allocation, and lock elimination can be done. However, escape analysis itself also requires a series of complex analysis, which is actually a relatively time-consuming process.

Although this technology is not very mature, it is also a very important means in the optimization technology of the real-time compiler.

Note that there are some opinions that through escape analysis, the JVM will allocate objects on the stack that will not escape. This is theoretically feasible, but it depends on the choice of the JVM designer. As far as I know, this is not done in the Oracle Hotspot JVM. This has been stated in the documents related to escape analysis, so it can be clearly stated that all object instances are created on the heap.

At present, many books are still based on the previous version of JDK7. JDK has undergone great changes. The cache and static variables of intern strings were once allocated on the permanent generation, and the permanent generation has been replaced by the metadata area. However, the intern string cache and static variables are not transferred to the metadata area, but are directly allocated on the heap, so this also conforms to the conclusion of the previous point: object instances are all allocated on the heap.

chapter summary

The young generation is the area where objects are born, grow, and die. An object is created and applied here, and finally it is collected by the garbage collector and ends its life.

Objects with a long life cycle placed in the old generation are usually Java objects filtered and copied from the survivor area. Of course, there are special cases. We know that ordinary objects will be allocated on TLAB; if the object is large, JVM will try to allocate it directly in other locations in Eden; if the object is too large, it is completely impossible to find a long enough continuous free space in the new generation Space, JVM will be directly allocated to the old generation. When GC only occurs in the young generation, the act of reclaiming young generation objects is called MinorGc.

When GC occurs in the old age, it is called MajorGc or FullGC. Generally, the frequency of MinorGC is much higher than that of MajorGC, that is, the frequency of garbage collection in the old generation will be much lower than that in the young generation.

7. Method area

7.1. Interaction between stack, heap and method area

7.2. Understanding of method area

Official documentation: Chapter 2. The Structure of the Java Virtual Machine (oracle.com)

7.2.1. Where is the method area?

The "Java Virtual Machine Specification" clearly states: "Although all method areas are logically part of the heap, some simple implementations may not choose to perform garbage collection or compression." But for HotSpotJVM, the method The area also has an alias called Non-Heap (non-heap), the purpose is to separate it from the heap.

Therefore, the method area is regarded as a memory space independent of the Java heap. 

7.2.2. Basic understanding of method area

  • The method area (Method Area), like the Java heap, is a memory area shared by each thread.
  • The method area is created when the JVM starts, and its actual physical memory space can be discontinuous just like the Java heap area.
  • The size of the method area, like the heap space, can be fixed or expandable.
  • The size of the method area determines how many classes the system can store. If the system defines too many classes and causes the method area to overflow, the virtual machine will also throw a memory overflow error: java.lang.OutOfMemoryError: PermGen spaceorjava.lang.OutOfMemoryError: Metaspace
    • Load a large number of third-party jar packages; Tomcat deploys too many projects (30~50); generate a large number of reflection classes dynamically
  • Closing the JVM will release the memory in this area.

7.2.3. Evolution of method area in HotSpot

In jdk7 and before, it is customary to call the method area the permanent generation. Starting with jdk8, the permanent generation is replaced by the metaspace.

Essentially, the method area and the permanent generation are not equivalent. Only for hotspot. The "Java Virtual Machine Specification" does not make uniform requirements on how to implement the method area. For example: The concept of permanent generation does not exist in BEA JRockit / IBM J9.

Looking at it now, it was not a good idea to use the permanent generation back then. Causes Java programs to be more prone to OOM (over -XX:MaxPermsizelimit)

When it came to JDK8, the concept of permanent generation was finally completely abandoned, and it was replaced by Metaspace, which is implemented in local memory like JRockit and J9.

The essence of the metaspace is similar to that of the permanent generation, which is the implementation of the method area in the JVM specification. However, the biggest difference between the metaspace and the permanent generation is that the metaspace is not in the memory set by the virtual machine, but uses local memory

The permanent generation and metaspace are not only changed in name, but the internal structure is also adjusted

According to the "Java Virtual Machine Specification", if the method area cannot meet the new memory allocation requirements, an OOM exception will be thrown

7.3. Set method area size and OOM

7.3.1. Set the memory size of the method area

The size of the method area does not have to be fixed, and the JVM can be dynamically adjusted according to the needs of the application. 

jdk7 and before

  • Set the permanent generation initial allocation space by. The default value is 20.75M-XX:Permsize
  • Set the maximum allocatable space of the permanent generation by 32-bit machine default is 64M, 64-bit machine mode is 82M-XX:MaxPermsize
  • When the class information capacity loaded by the JVM exceeds this value, an exception will be reported OutOfMemoryError:PermGen space.

After JDK8

  • The metadata area size can be specified using parameters -XX:MetaspaceSizeand-XX:MaxMetaspaceSize
  • The default is platform dependent. Under windows, -XX:MetaspaceSize=21M -XX:MaxMetaspaceSize=-1//即没有限制.
  • Unlike the permanent generation, if you do not specify a size, the virtual machine will by default use up all available system memory. If the metadata area overflows, the virtual machine will also throw an exceptionOutOfMemoryError:Metaspace
  • -XX:MetaspaceSize: Set the initial metaspace size. For a 64-bit server-side JVM, the default -XX:MetaspaceSizevalue is 21MB. This is the initial high water mark, once this water mark is hit, Full GC will be triggered and unload useless classes (that is, the class loaders corresponding to these classes are no longer alive), and then the high water mark will be reset. The value of the new high-water mark depends on how much metaspace is freed after GC. If the freed space is insufficient, MaxMetaspaceSizeincrease the value appropriately if it does not exceed. If there is too much space to be freed, reduce this value appropriately.
  • If the initial high-water mark is set too low, the high-water mark adjustment described above can occur many times. Through the log of the garbage collector, it can be observed that the Full GC is called multiple times. In order to avoid frequent GC, it is recommended to -XX:MetaspaceSizeset it to a relatively high value.

example

/**
 * jdk8中:
 * -XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m
 * jdk6中:
 * -XX:PermSize=10m -XX:MaxPermSize=10m
 */
public class OOMTest extends ClassLoader{
    public static void main(String[] args){
        int j = 0;
        try{
            OOMTest test = new OOMTest();
            for (int i=0;i<10000;i++){
                //创建Classwriter对象,用于生成类的二进制字节码
                ClassWriter classWriter = new ClassWriter(0);
                //指明版本号,public,类名,包名,父类,接口
                classWriter.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC, "Class" + i, nu1l, "java/lang/Object", null);
                //返回byte[]
                byte[] code = classWriter.toByteArray();
                //类的加载
                test.defineClass("Class" + i, code, 0, code.length); //CLass对象
                j++;
            }
        } finally{
            System.out.println(j);
        }
    }
}

7.3.2. How to solve these OOMs

  1. To solve OOM exceptions or heap space exceptions, the general method is to first analyze the dumped heap dump snapshots through a memory image analysis tool (such as Eclipse Memory Analyzer). The focus is to confirm whether the objects in memory are necessary or not. It is to first distinguish whether there is a memory leak (Memory Leak) or a memory overflow (Memory Overflow)
  2. If it is a memory leak, you can further use the tool to view the reference chain from the leaked object to GC Roots. Then you can find out how the leaked objects are associated with GCRoots and prevent the garbage collector from reclaiming them automatically. After mastering the type information of the leaked object and the information of the GCRoots reference chain, the location of the leaked code can be located more accurately.
  3. If there is no memory leak, in other words, the objects in the memory must still be alive, then you should check the heap parameters (and) of the virtual machine, -Xmxcompare -Xmswith the physical memory of the machine to see if it can be increased, and check from the code Whether there are some objects whose life cycle is too long and the holding state time is too long, try to reduce the memory consumption of the program runtime.

7.4. Internal structure of the method area

7.4.1. What is stored in the Method Area?

The book "In-depth Understanding of the Java Virtual Machine" describes the method area (Method Area) storage content as follows:

It is used to store type information loaded by the virtual machine, constants, static variables, code cache compiled by the just-in-time compiler, etc.

7.4.2. Internal structure of the method area

type information

For each loaded type (class class, interface interface, enumeration enum, annotation annotation), the JVM must store the following type information in the method area:

  1. The full valid name of this type (fullname = packagename.classname)
  2. The fully qualified name of the immediate superclass of this type (for interface or java.lang.object, there is no superclass)
  3. Modifiers of this type (public, abstract, some subset of final)
  4. an ordered list of the direct interfaces of this type

Domain (Field) information

The JVM must save information about all fields of a type and the order in which fields are declared in the method area.

Domain related information includes: domain name, domain type, domain modifier (a subset of public, private, protected, static, final, volatile, transient)

Method information

The JVM must preserve the following information for all methods, as well as domain information including declaration order:

  1. method name
  2. The method's return type (or void)
  3. Number and types of method parameters (in order)
  4. Method modifiers (a subset of public, private, protected, static, final, synchronized, native, abstract)
  5. Method bytecodes (bytecodes), operand stack, local variable table and size (except for abstract and native methods)
  6. Exception table (except abstract and native methods): start position, end position of each exception handling, offset address of code processing in program counter, constant pool index of caught exception class

non-final class variables

  • Static variables are associated with the class, loaded as the class is loaded, and they become a logical part of the class data
  • A class variable is shared by all instances of the class, and you can access it even when there is no instance of the class
public class MethodAreaTest {
    public static void main(String[] args) {
        Order order = new Order();
        order.hello();
        System.out.println(order.count);
    }
}
class Order {
    public static int count = 1;
    public static void hello() {
        System.out.println("hello!");
    }
}

Supplementary Note: Global constants (static final)

Class variables declared final are handled differently, and each global constant is assigned at compile time.

7.4.3. Runtime Constant Pool VS Constant Pool

  • The method area, which contains the runtime constant pool
  • Bytecode file, which contains a constant pool
  • To figure out the method area, you need to understand ClassFile clearly, because the information about loading classes is in the method area.
  • To figure out the runtime constant pool in the method area, you need to understand the constant pool in ClassFile.

Official document: Chapter 4. The class File Format

In addition to descriptor information such as class version information, fields, methods, and interfaces, an effective bytecode file also contains a piece of information that is the constant pool table (Constant Pool Table), including various literals and pairs of types, Symbolic references to fields and methods

Why do you need a constant pool?

Classes and interfaces in a java source file are compiled to generate a bytecode file. The bytecode in Java needs data support. Usually this kind of data is too large to be directly stored in the bytecode. In another way, it can be stored in the constant pool. This bytecode contains pointers to the constant pool. references. The runtime constant pool is used during dynamic linking, which was introduced earlier.

For example: the following code:

public class SimpleClass {
    public void sayHello() {
        System.out.println("hello");
    }
}

Although it is only 194 bytes, structures such as String, System, PrintStream, and Object are used in it. The amount of code here is actually very small. If there is more code, there will be more referenced structures, so a constant pool is needed here.

What is in the constant pool?

The data types stored in the constant pool include:

  • quantity value
  • string value
  • class reference
  • field reference
  • method reference

For example the following piece of code:

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

Object obj = new Object();will be translated into the following bytecode:

0: new #2  // Class java/lang/Object
1: dup
2: invokespecial // Method java/lang/Object "<init>"() V

summary

The constant pool can be regarded as a table. The virtual machine instructions find the class name, method name, parameter type, literal value and other types to be executed according to this constant table.

7.4.4. Runtime constant pool

  • The Runtime Constant Pool is part of the method area.
  • The constant pool table (Constant Pool Table) is a part of the Class file, which is used to store various literals and symbol references generated during compilation. This part of the content will be stored in the runtime constant pool in the method area after the class is loaded.
  • Runtime constant pool, after loading classes and interfaces to the virtual machine, the corresponding runtime constant pool will be created.
  • The JVM maintains a constant pool for each loaded type (class or interface). Data items in a pool, like array items, are accessed by index.
  • The runtime constant pool contains a variety of different constants, including numeric literals that have been defined at compile time, and method or field references that can only be obtained after runtime parsing. At this time, it is no longer the symbolic address in the constant pool, but the real address.
  • Another important feature of the runtime constant pool compared to the Class file constant pool is that it is dynamic.
  • The runtime constant pool is similar to the symbol table (symboltable) in traditional programming languages, but the data it contains is richer than the symbol table.
  • When creating a runtime constant pool of a class or interface, if the memory space required to construct the runtime constant pool exceeds the maximum value that the method area can provide, the JVM will throw an OutOfMemoryError exception.

7.5. Example of using method area

public class MethodAreaDemo {
    public static void main(String args[]) {
        int x = 500;
        int y = 100;
        int a = x / y;
        int b = 50;
        System.out.println(a+b);
    }
}

7.6. Details of the evolution of the method area

  1. First of all, it is clear: only Hotspot has permanent generation. For BEA JRockit, IBMJ9, etc., there is no concept of permanent generation. In principle, how to implement the method area belongs to the implementation details of the virtual machine, and is not subject to the "Java Virtual Machine Specification", and does not require uniformity
  2. Changes in the method area in Hotspot

 

7.6.1. Why is the permanent generation replaced by metaspace?

Official website address: JEP 122: Remove the Permanent Generation (java.net)

JRockit is the result of integration with HotSpot, because JRockit has no permanent generation, so they do not need to configure permanent generation

With the arrival of Java8, the permanent generation is no longer seen in the HotSpot VM. But this does not mean that the metadata information of the class has disappeared. These data are moved to a local memory area that is not connected to the heap, this area is called metaspace (Metaspace).

由于类的元数据分配在本地内存中,元空间的最大可分配空间就是系统可用内存空间。

这项改动是很有必要的,原因有:

  • 为永久代设置空间大小是很难确定的。在某些场景下,如果动态加载类过多,容易产生Perm区的oom。比如某个实际Web工 程中,因为功能点比较多,在运行过程中,要不断动态加载很多类,经常出现致命错误。
    而元空间和永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。 因此,默认情况下,元空间的大小仅受本地内存限制。
"Exception in thread 'dubbo client x.x connector' java.lang.OutOfMemoryError:PermGen space"
  • 对永久代进行调优是很困难的。

有些人认为方法区(如HotSpot虚拟机中的元空间或者永久代)是没有垃圾收集行为的,其实不然。《Java虚拟机规范》对方法区的约束是非常宽松的,提到过可以不要求虚拟机在方法区中实现垃圾收集。事实上也确实有未实现或未能完整实现方法区类型卸载的收集器存在(如JDK 11时期的ZGC收集器就不支持类卸载)。 一般来说这个区域的回收效果比较难令人满意,尤其是类型的卸载,条件相当苛刻。但是这部分区域的回收有时又确实是必要的。以前Sun公司的Bug列表中,曾出现过的若干个严重的Bug就是由于低版本的HotSpot虚拟机对此区域未完全回收而导致内存泄漏

方法区的垃圾收集主要回收两部分内容:常量池中废弃的常量和不再使用的类型

7.6.2. StringTable为什么要调整位置?

jdk7中将StringTable放到了堆空间中。因为永久代的回收效率很低,在full gc的时候才会触发。而full gc是老年代的空间不足、永久代不足时才会触发。

这就导致StringTable回收效率不高。而我们开发中会有大量的字符串被创建,回收效率低,导致永久代内存不足。放到堆里,能及时回收内存。

7.6.3. Where are static variables stored?

/**
 * 静态引用对应的对象实体始终都存在堆空间
 * jdk7:
 * -Xms200m -Xmx200m -XX:PermSize=300m -XX:MaxPermSize=300m -XX:+PrintGCDetails
 * jdk8:
 * -Xms200m -Xmx200m-XX:MetaspaceSize=300m -XX:MaxMetaspaceSize=300m -XX:+PrintGCDetails
 */
public class StaticFieldTest {
    private static byte[] arr = new byte[1024 * 1024 * 100];
    public static void main(String[] args) {
        System.out.println(StaticFieldTest.arr);
        
        try {
            Thread.sleep(1000000);
        } catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}
/**
 * staticobj、instanceobj、Localobj存放在哪里?
 */
public class StaticobjTest {
    static class Test {
        static ObjectHolder staticobj = new ObjectHolder();
        ObjectHolder instanceobj = new ObjectHolder();
        void foo(){
            ObjectHolder localobj = new ObjectHolder();
            System.out.println("done");
        }    
    }
    private static class ObjectHolder{
        public static void main(String[] args) {
            Test test = new StaticobjTest.Test();
            test.foo();
        }
    }
}

staticobj is stored in the method area along with the type information of Test, instanceobj is stored in the Java heap along with the object instance of Test, and localobject is stored in the local variable table of the stack frame of the foo() method.
The test found that the addresses of the data of the three objects in memory all fall within the range of the Eden area, so the conclusion: as long as it is an object instance, it must be allocated in the Java heap.

Then, I found a place to refer to the staticobj object, which is in an instance of java.lang.Class, and gave the address of this instance. Viewing the object instance through the Inspector, it can be clearly seen that this is indeed a java. An object instance of type lang.Class with an instance field named staticobj.

From the conceptual model defined in the "Java Virtual Machine Specification", all Class-related information should be stored in the method area, but the "Java Virtual Machine Specification" does not stipulate how to implement the method area, which is It has become a thing that allows different virtual machines to be flexibly controlled by themselves. The HotSpot virtual machine of JDK7 and later versions chooses to store the static variable and the mapping class object of the type at the Java language end together in the Java heap, which is clearly verified from our experiments

7.7. Garbage collection in the method area

Some people think that the method area (such as the metaspace or permanent generation in the Hotspot virtual machine) has no garbage collection behavior, but it is not. The "Java Virtual Machine Specification" has very loose constraints on the method area. It has been mentioned that the virtual machine may not be required to implement garbage collection in the method area. In fact, there are indeed collectors that do not implement or fail to fully implement type unloading in the method area (for example, the zGC collector in the JDK11 period does not support class unloading).

Generally speaking, the recovery effect in this area is relatively difficult to be satisfactory, especially the type of unloading, the conditions are quite harsh. But the recovery of this part of the area is sometimes necessary. In the previous bug list of sun company, several serious bugs appeared because the low version of the HotSpot virtual machine did not fully reclaim this area, resulting in memory leaks.

Garbage collection in the method area mainly recycles two parts: obsolete constants in the constant pool and types that are no longer used.

First, let’s talk about the two main types of constants stored in the constant pool in the method area: literals and symbolic references. Literal quantities are closer to the constant concept at the Java language level, such as text strings, constant values ​​declared as final, and so on. Symbolic references belong to the concept of compilation principles, including the following three types of constants:

  • fully qualified names of classes and interfaces
  • Field names and descriptors
  • method name and descriptor

The recovery strategy of the HotSpot virtual machine for the constant pool is very clear. As long as the constants in the constant pool are not referenced anywhere, they can be recycled.

Recycling obsolete constants is very similar to reclaiming objects in the Java heap.

It is relatively simple to determine whether a constant is "obsolete", but it is more stringent to determine whether a type belongs to a "class that is no longer used". The following three conditions need to be met at the same time:

  • All instances of this class have been recycled, that is, there are no instances of this class and any derived subclasses in the Java heap.
  • The class loader that loads this class has been recycled. This condition is usually difficult to achieve unless it is a carefully designed scenario where the class loader can be replaced, such as OSGi, JSP reloading, etc.
  • The java.lang.Class object corresponding to this class is not referenced anywhere, and the methods of this class cannot be accessed through reflection anywhere.

The Java virtual machine is allowed to recycle useless classes that meet the above three conditions. What I mean here is only "allowed", not the same as objects, which will inevitably be recycled if they are not referenced. Regarding whether to recycle the type , the HotSpot virtual machine provides -Xnoclassgcparameters to control, and you can also use -verbose:classand to view class loading and unloading information-XX:+TraceClassLoading-XX:+TraceClassUnLoading

In scenarios where reflection, dynamic proxies, CGLib and other bytecode frameworks are used extensively, and frequent custom class loaders such as JSP and OSGi are dynamically generated, the Java virtual machine is usually required to have the ability to unload types to ensure that methods will not be region causing excessive memory pressure.

Summarize

8. Object instantiation and direct memory

8.1. Object instantiation 

8.1.1. How objects are created

  • new: the most common way, the static method of Xxx, the static method of XxxBuilder/XxxFactory
  • Class's newInstance method: the way of reflection, only the constructor with empty parameters can be called, and the permission must be public
  • Constructor's newInstance(XXX): Reflection, you can call constructors with empty parameters and parameters
  • Use clone(): do not call any constructors, require the current class to implement the Cloneable interface, implement clone()
  • Use serialization: get a binary stream of an object from a file, from the network
  • Third-party library Objenesis 

8.1.2. Steps to create an object

The above is to look at the object creation process from the perspective of bytecode, and now analyze it from the perspective of execution steps

1. Determine whether the class corresponding to the object is loaded, linked, and initialized

When the virtual machine encounters a new instruction, it first checks whether the parameter of this instruction can locate a symbolic reference of a class in the constant pool of Metaspace, and checks whether the class represented by the symbolic reference has been loaded, parsed and initialized (that is, to judge whether the class meta information exists).

If not, then in the parent delegation mode, use the current class loader to find the corresponding .class file with ClassLoader + package name + class name key;

  • If the file is not found, a ClassNotFoundException is thrown
  • If found, load the class and generate the corresponding Class object

2. Allocate memory for the object

First calculate the size of the space occupied by the object, and then allocate a piece of memory in the heap for the new object. If the instance member variable is a reference variable, only the space for the reference variable is allocated, which is 4 bytes in size

If the memory is organized : the virtual machine will use the pointer collision method (Bump The Point) to allocate memory for the object.

  • It means that all used memory is on one side, free memory is on the other side, and a pointer is placed in the middle as an indicator of the demarcation point. Allocating memory is just moving the pointer to the free side by a distance equal to the size of the object. If the garbage collector chooses Serial, ParNew is based on the compression algorithm, and the virtual machine adopts this allocation method. Generally, when using a collector with a Compact (finishing) process, pointer collisions are used.

If the memory is irregular : The virtual machine needs to maintain a free list (Free List) to allocate memory for objects.

  • Used memory and unused memory are interleaved, then the virtual machine will use the free list to allocate memory for objects. It means that the virtual machine maintains a list, records which memory blocks are available, and finds a large enough space from the list to allocate to the object instance when reassigning, and updates the content on the list.

Which allocation method to choose is determined by whether the Java heap is regular, and whether the Java heap is regular is determined by whether the garbage collector used has a compacting function.

3. Dealing with concurrency issues

  • Use CAS failure retry and area locking to ensure the atomicity of updates
  • Each thread pre-allocates a TLAB: -XX:+UseTLABset by setting parameters

4. Initialize the allocated memory

All attributes set default values ​​to ensure that object instance fields can be used directly when they are not assigned

5. Set the object header of the object

Store data such as the class of the object (that is, the metadata information of the class), the HashCode of the object, the GC information of the object, and the lock information in the object header of the object. Exactly how this process is set up depends on the JVM implementation.

6. Execute the init method to initialize

From the perspective of the Java program, the initialization has officially begun. Initialize member variables, execute the instantiation code block, call the constructor of the class, and assign the first address of the object in the heap to the reference variable.

Therefore, generally speaking (determined by the invokespecial instruction in the bytecode), the new instruction will be followed by the execution method to initialize the object according to the programmer's wishes, so that a truly usable object will be created.

Operations that assign values ​​to object properties

  • default initialization of properties
  • explicit initialization
  • code block initialization
  • initialization in the constructor

The process of object instantiation

  1. Load class meta information
  2. allocate memory for the object
  3. Dealing with concurrency issues
  4. Default initialization of properties (zero value initialization)
  5. Set object header information
  6. Display initialization of properties, initialization in code blocks, initialization in constructors

8.2. Object memory layout

8.2.1. Object header (Header)

The object header contains two parts, namely the runtime metadata (Mark Word) and the type pointer. If it is an array, you also need to record the length of the array

runtime metadata

  • Hash value (HashCode)
  • GC generational age
  • lock status flag
  • locks held by threads
  • Bias thread ID
  • time stamp

type pointer

Point to the class metadata InstanceKlass to determine the type of the object.

8.2.2. Instance Data

It is the effective information actually stored by the object, including various types of fields defined in the program code (including fields inherited from the parent class and owned by itself)

  • Fields of the same width are always allocated together
  • Variables defined in the parent class will appear before the subclass
  • If the CompactFields parameter is true (default is true): the narrow variable of the subclass may be inserted into the gap of the parent class variable

8.2.3. Alignment padding (Padding)

It is not necessary and has no special meaning, it only serves as a placeholder

example

public class Customer{
    int id = 1001;
    String name;
    Account acct;

    {
        name = "匿名客户";
    }

    public Customer() {
        acct = new Account();
    }
}

public class CustomerTest{
    public static void main(string[] args){
        Customer cust=new Customer();
    }
}

icon

8.3. Object access location 

How does the JVM access its internal object instance through the object reference in the stack frame?

 8.3.1. Handle access

 The address of the stable handle is stored in the reference. When the object is moved (it is common to move objects during garbage collection), only the instance data pointer in the handle will be changed, and the reference itself does not need to be modified.

8.3.2. Direct pointers (used by HotSpot)

8.4. Direct Memory

8.4.1. Direct memory overview

It is not part of the virtual machine runtime data area, nor is it a memory area defined in the Java Virtual Machine Specification. Direct memory is the memory range outside the Java heap that is directly applied to the system. Originated from NIO, Native memory is manipulated through DirectByteBuffer stored in the heap. Generally, the speed of accessing direct memory is better than that of the Java heap, that is, the read and write performance is high.

  • Therefore, for performance considerations, direct memory may be considered in occasions with frequent reads and writes.
  • Java's NIO library allows Java programs to use direct memory for data buffers

8.4.2. Non-direct buffers

To use IO to read and write files, you need to interact with the disk, and you need to switch from user mode to kernel mode. In kernel mode, two copies of memory are required to store duplicate data, which is inefficient.

8.4.3. Direct cache

When using NIO, the direct buffer area allocated by the operating system can be directly accessed by java code, and there is only one copy. NIO is suitable for reading and writing operations on large files. 

 May also cause OutOfMemoryError exception

Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory 
    at java.nio.Bits.reserveMemory(Bits.java:693)
    at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:123)
    at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:311)
    at com.atguigu.java.BufferTest2.main(BufferTest2.java:20)

The direct memory size can MaxDirectMemorySizebe set via . If not specified, the default is consistent with the maximum value of the heap -Xmx parameter

9. Execution engine

9.1. Execution Engine Overview

The execution engine belongs to the lower layer of the JVM, which includes an interpreter, a just-in-time compiler, and a garbage collector

The execution engine is one of the core components of the Java virtual machine.

"Virtual machine" is a concept relative to "physical machine". Both machines have code execution capabilities. The difference is that the execution engine of a physical machine is directly built on the processor, cache, instruction set, and operating system levels. , while the execution engine of the virtual machine is implemented by software itself, so the structure of the instruction set and execution engine can be customized without being restricted by physical conditions, and it can execute those instruction set formats that are not directly supported by the hardware.

The main task of the JVM is to load bytecodes into it, but bytecodes cannot run directly on the operating system, because bytecode instructions are not equivalent to local machine instructions, and what it contains are only some that can Bytecode instructions, symbol tables, and other auxiliary information recognized by the JVM. 

Then, if you want to run a Java program, the task of the execution engine (Execution Engine) is to interpret/compile bytecode instructions into local machine instructions on the corresponding platform. In simple terms, the execution engine in the JVM acts as a translator from high-level language to machine language.

9.1.1. Execution engine workflow

  1. What kind of bytecode instructions the execution engine needs to execute during execution depends entirely on the PC register.
  2. Whenever an instruction operation is executed, the PC register will update the address of the next instruction to be executed.
  3. Of course, during the execution of the method, the execution engine may accurately locate the object instance information stored in the Java heap through the object reference stored in the local variable table, and locate the target object through the metadata pointer in the object header. type information.

 

From the appearance point of view, the input and output of the execution engines of all Java virtual machines are consistent: the input is the bytecode binary stream, the processing process is the equivalent process of bytecode parsing and execution, and the output is the execution process.

9.2. Java code compilation and execution process 

Most of the program code needs to go through the steps in the above figure before it is converted into the target code of the physical machine or the instruction set that the virtual machine can execute.

Java code compilation is done by a Java source code compiler (front-end compiler), and the flow chart is as follows: 

The execution of Java bytecode is completed by the JVM execution engine (back-end compiler), and the flow chart is as follows 

9.2.1. What is an Interpreter? What is a JIT compiler?

Interpreter: When the Java virtual machine starts, it will interpret the bytecode line by line according to the predefined specifications, and "translate" the content in each bytecode file into the local machine instructions of the corresponding platform for execution.

JIT (Just In Time Compiler) compiler: It is a virtual machine that directly compiles the source code into a machine language related to the local machine platform.

9.2.2. Why is Java a semi-compiled and semi-interpreted language?

In the era of JDK1.0, it is more accurate to position the Java language as "interpretation and execution". Later, Java also developed a compiler that can directly generate native code. Now when the JVM executes Java code, it usually combines interpretation and execution with compilation and execution.

icon

9.3. Machine code, instructions, assembly language

9.3.1. Machine code

Various instructions expressed in binary encoding are called machine instruction codes. At the beginning, people used it to write programs, which is machine language.

Although machine language can be understood and accepted by computers, it is too different from human language, so it is not easy to be understood and memorized by people, and it is easy to make mistakes when programming with it.

Once the program written in it is input into the computer, the CPU directly reads and runs it, so it has the fastest execution speed compared with programs written in other languages.

Machine instructions are closely related to the CPU, so different types of CPUs correspond to different machine instructions.

9.3.2. Commands

Since the machine code is a binary sequence composed of 0 and 1, the readability is really poor, so people invented instructions.

The instruction is to simplify the specific 0 and 1 sequence in the machine code into the corresponding instruction (generally abbreviated in English, such as mov, inc, etc.), and the readability is slightly better

Because different hardware platforms perform the same operation, the corresponding machine codes may be different, so the same instruction (such as mov) on different hardware platforms may have different corresponding machine codes.

9.3.3. Instruction Set

Different hardware platforms have different supported instructions. Therefore, the instructions supported by each platform are called the instruction set of the corresponding platform. as usual

  • The x86 instruction set corresponds to the x86 architecture platform
  • ARM instruction set, corresponding to the platform of ARM architecture

9.3.4. Assembly language

Because the readability of the instructions is still too poor, people invented assembly language.

In assembly language, mnemonics (Mnemonics) are used to replace the opcodes of machine instructions, and <mark address symbols (Symbol) or labels (Label) are used to replace the addresses of instructions or operands. On different hardware platforms, assembly language corresponds to different machine language instruction sets, which are converted into machine instructions through the assembly process.

Since computers only understand instruction codes, programs written in assembly language must be translated into machine instruction codes before computers can recognize and execute them.

9.3.5. High-level languages

In order to make programming easier for computer users, various high-level computer languages ​​appeared later. High-level language is closer to human language than machine language and assembly language

When a computer executes a program written in a high-level language, it still needs to interpret and compile the program into machine instruction codes. The program that completes this process is called an interpreter or a compiler. 

High-level languages ​​are not directly translated into machine instructions, but translated into assembly language codes, such as C and C++ mentioned below

C, C++ source program execution process

 

The compilation process can be divided into two phases: compilation and assembly.

Compilation process: read the source program (character stream), analyze it lexically and grammatically, and convert high-level language instructions into functionally equivalent assembly codes

Assembly process: actually refers to the process of translating assembly language code into target machine instructions.

9.3.6. Bytecode

Bytecode is a binary code (file) in an intermediate state (intermediate code), which is more abstract than machine code and needs to be translated by an interpreter to become machine code

Bytecode is mainly to achieve specific software operation and software environment, and has nothing to do with the hardware environment.

The implementation of bytecode is through compilers and virtual machines. The compiler compiles the source code into bytecode, and the virtual machine on a specific platform translates the bytecode into instructions that can be executed directly. The typical application of bytecode is: Java bytecode

9.4. Interpreter

The original intention of JVM designers is simply to meet the cross-platform characteristics of Java programs, so they avoid using static compilation to directly generate local machine instructions, thus giving birth to the realization that the interpreter uses line-by-line interpretation of bytecodes to execute programs at runtime. idea.

The role of the interpreter in the true sense is a runtime "translator", which "translates" the content in the bytecode file into the local machine instruction execution of the corresponding platform.

After a bytecode instruction is interpreted and executed, the interpretation operation is performed according to the next bytecode instruction that needs to be executed recorded in the PC register.

9.4.2. Interpreter classification

In the development history of Java, there are two sets of interpretation executors, namely the ancient bytecode interpreter and the now commonly used template interpreter.

  • The bytecode interpreter simulates the execution of the bytecode through pure software code during execution, which is very inefficient.
  • The template interpreter associates each bytecode with a template function, and the template function directly generates the machine code when this bytecode is executed, thereby greatly improving the performance of the interpreter.

In HotSpot VM, the interpreter is mainly composed of Interpreter module and Code module.

  • Interpreter module: implements the core functions of the interpreter
  • Code module: used to manage the local machine instructions generated by the HotSpot VM at runtime

9.4.3. Status

Because the interpreter is very simple in design and implementation, in addition to the Java language, there are many high-level languages ​​that are also executed based on the interpreter, such as Python, Perl, Ruby, etc. But today, interpreter-based execution has become synonymous with inefficiency, and is often ridiculed by some C/C++ programmers.

To solve this problem, the JVM platform supports a technology called just-in-time compilation. The purpose of just-in-time compilation is to prevent the function from being interpreted and executed, but to compile the entire function body into machine code. Every time the function is executed, only the compiled machine code is executed. This method can greatly improve the execution efficiency.

But in any case, the execution mode based on the interpreter still made an indelible contribution to the development of the intermediate language.

9.5. JIT Compiler

9.5.1. Execution classification of Java code

  • The first is to compile the source code into a bytecode file, and then convert the bytecode file into machine code execution through the interpreter at runtime
  • The second is compilation and execution (directly compiled into machine code, but you must know that the machine code compiled on different machines is different, and bytecode can be cross-platform). In order to improve execution efficiency, modern virtual machines use just-in-time compilation technology (JIT, Just In Time) to compile methods into machine codes before executing them.

HotSpot VM is one of the masterpieces of high-performance virtual machines currently on the market. It uses an architecture in which an interpreter and a just-in-time compiler coexist. When the Java virtual machine is running, the interpreter and the just-in-time compiler can cooperate with each other, learn from each other, and try to choose the most appropriate way to weigh the time of compiling native code and the time of directly interpreting and executing code.

Today, the running performance of Java programs has been reborn, and has reached the point where it can compete with C/C++ programs.

here comes the problem

Some developers will be surprised, since the HotSpot VM has a built-in JIT compiler, why do you need to use an interpreter to "drag" the execution performance of the program? For example, JRockit VM does not contain an interpreter inside, and all bytecodes are compiled and executed by a just-in-time compiler.

First of all, it is clear: When the program starts, the interpreter can take effect immediately, saving the compilation time and executing it immediately. In order for the compiler to function, it takes a certain amount of execution time to compile the code into native code. However, after compiling to native code, the execution efficiency is high.

So: Although the execution performance of the program in the JRockit VM will be very efficient, the program will inevitably take longer to compile at startup. For server-side applications, startup time is not the focus, but for those application scenarios that are concerned about startup time, it may be necessary to adopt an architecture where an interpreter and a just-in-time compiler coexist in exchange for a balance point. In this mode, when the Java virtual machine starts, the interpreter can take effect first, without having to wait for the JIT compiler to complete all compilations before executing, which can save a lot of unnecessary compilation time. Over time, the compiler comes into play, compiling more and more code into native code for higher execution efficiency.

At the same time, interpreted execution is used as an "escape door" for the compiler when the aggressive optimization of the compiler is not established.

9.5.2. HotSpot JVM Execution Mode

When the virtual machine is started, the interpreter can play a role first, without having to wait for the instant compiler to compile and then execute, which can save a lot of unnecessary compilation time. And as the running time of the program goes on, the just-in-time compiler gradually plays a role, and compiles valuable bytecodes into local machine instructions according to the hotspot detection function in exchange for higher program execution efficiency.

the case came

Pay attention to the subtle dialectical relationship between interpreted execution and compiled execution in the online environment. The load that the machine can withstand in the hot state is greater than that in the cold state. If the traffic in the hot state is used to cut the flow, the server in the cold state may die because it cannot carry the traffic.

During the release process of the production environment, the release is performed in batches, divided into multiple batches according to the number of machines, and the number of machines in each batch accounts for at most 1/8 of the entire cluster. There was once such a failure case: a programmer released in batches on the publishing platform, and when entering the total number of releases, he mistakenly entered the number of releases into two batches. If it is in a hot state, half of the machines can barely carry the traffic under normal circumstances. However, since the newly started JVMs are interpreted and executed, hot code statistics and JIT dynamic compilation have not yet been performed. After the machines are started, the current 1/2 release is successful. All the servers of the server crashed immediately. This failure shows the existence of JIT. — Ali Team  

9.5.3. Concept explanation

The "compilation period" of the Java language is actually an "uncertain" operation process, because it may refer to a front-end compiler (actually called "compiler front-end" is more accurate) to convert .java files into .class files process;

It may also refer to the process by which the virtual machine's back-end runtime compiler (JIT compiler, Just In Time Compiler) converts bytecode into machine code.

It may also refer to the process of directly compiling .java files into local machine code using a static ahead-of-time compiler (AOT compiler, Ahead of Time Compiler).

  • Front-end compiler: Sun's Javac, the incremental compiler (ECJ) in Eclipse JDT.
  • JIT compiler: C1 and C2 compilers of HotSpot VM.
  • AOT compilers: GNU Compiler for the Java (GCJ), Excelsior JET.

9.5.4. Hot code and detection technology

Of course, whether it is necessary to start the JIT compiler to directly compile the bytecode into the local machine instructions of the corresponding platform depends on the frequency of the code being called and executed. Regarding the bytecode that needs to be compiled into native code, also known as "hot code", the JIT compiler will make deep optimizations for those frequently called "hot code" at runtime, and compile it directly into Corresponding to the local machine instructions of the platform, so as to improve the execution performance of Java programs.

A method that is called multiple times, or a loop body with a large number of loops inside the method body can be called "hot code", so it can be compiled into local machine instructions by the JIT compiler. Since this compilation method occurs during the execution of the method, it is called on-stack replacement, or OSR (On Stack Replacement) compilation for short.

How many times does a method have to be called, or how many times does a loop body need to be executed to meet this standard? A clear threshold is necessarily required before the JIT compiler will compile these "hot codes" into local machine instructions for execution. This mainly relies on the hotspot detection function.

The current hotspot detection method adopted by the HotSpot VM is based on counters.

Using counter-based hotspot detection, HotSpot VM will create two different types of counters for each method, namely the method invocation counter (Invocation Counter) and the back edge counter (Back Edge Counter).

  • The method call counter is used to count the number of method calls
  • The edge counter is used to count the number of loops executed by the loop body

method call counter

This counter is used to count the number of times the method is called. Its default threshold is 1500 times in Client mode and 10000 times in Server mode. Exceeding this threshold triggers JIT compilation.

This threshold can be manually set through virtual machine parameters -XX:CompileThreshold.

When a method is called, it will first check whether there is a JIT-compiled version of the method, and if it exists, the compiled native code will be used first for execution. If there is no compiled version, add 1 to the call counter value of this method, and then determine whether the sum of the method call counter and the return edge counter exceeds the threshold value of the method call counter. If the threshold has been exceeded, a code compilation request for the method will be submitted to the just-in-time compiler.

hot spot attenuation

If no settings are made, the method call counter counts not the absolute number of times the method is called, but a relative execution frequency, that is, the number of times the method is called within a period of time. When a certain time limit is exceeded, if the number of method calls is still not enough to submit it to the just-in-time compiler for compilation, the call counter of this method will be reduced by half. This process is called method call counter heat decay (Counter Decay) , and this period of time is called the half-life period of this method (Counter Half Life Time)

The action of heat decay is carried out by the way when the virtual machine performs garbage collection. You can use the virtual machine parameters -XX:-UseCounterDecayto turn off the heat decay and let the method counter count the absolute number of method calls. In this way, as long as the system runs long enough, most methods will be compiled into native code.

In addition, you can use -XX:CounterHalfLifeTimeparameters to set the time of the half-life cycle in seconds.

Back edge counter

Its function is to count the number of times the loop body code is executed in a method, and the instruction that jumps after encountering the control flow in the bytecode is called "Back Edge". Apparently, the purpose of setting up back-to-edge counter statistics is to trigger OSR compilation.

9.5.5. HotSpotVM can set the program execution method

By default, the HotSpot VM adopts an architecture in which an interpreter and a just-in-time compiler coexist. Of course, developers can explicitly specify for the Java virtual machine through commands according to specific application scenarios whether to use an interpreter for execution at runtime, or Executed entirely with a just-in-time compiler. As follows:

  • -Xint: Execute the program completely in interpreter mode;
  • -Xcomp: Execute the program completely in just-in-time compiler mode. If there is a problem with just-in-time compilation, the interpreter will step in to execute
  • -Xmixed: Use the mixed mode of interpreter + just-in-time compiler to jointly execute the program.

9.5.6. JIT classification in HotSpotVM

There are two types of JIT compilers, namely C1 and C2. There are two JIT compilers embedded in the HotSpot VM, namely Client Compiler and Server Compiler, but in most cases we simply call them C1 compiler and C2 translater. Developers can explicitly specify which kind of just-in-time compiler the Java virtual machine uses at runtime through the following command, as follows:

  • -client: Specifies that the Java virtual machine runs in Client mode and uses the C1 compiler; the C1 compiler will perform simple and reliable optimization of the bytecode, which takes less time to achieve faster compilation speed.
  • -server: Specifies that the Java virtual machine runs in server mode and uses the C2 compiler. C2 performs time-consuming optimization and aggressive optimization, but the optimized code execution efficiency is higher.

Tiered Compilation strategy: Program interpretation and execution (without performance monitoring) can trigger C1 compilation, compiling bytecode into machine code, simple optimization can be performed, and performance monitoring can also be added. C2 compilation will be based on performance monitoring Information is radically optimized.

However, after the Java7 version, once the developer explicitly specifies the command "-server" in the program, the hierarchical compilation strategy will be enabled by default, and the C1 compiler and the C2 compiler will cooperate with each other to perform the compilation task.

Different optimization strategies of C1 and C2 compilers

Different compilers have different optimization strategies, and the C1 compiler mainly includes method inlining, devirtualization, and redundancy elimination.

  • Method inlining: compile the referenced function code to the reference point, reduce the generation of stack frames, reduce parameter passing and jump process
  • Devirtualization: inlining the only implementing class
  • Redundancy Elimination: Fold some code that will not be executed during runtime

The optimization of C2 is mainly at the global level, and escape analysis (as mentioned earlier, it is not mature) is the basis of optimization. Based on escape analysis, there are the following optimizations in C2:

  • Scalar Replacement: Replaces property values ​​of aggregated objects with scalar values
  • Allocation on the stack: for unescaped objects, the object is allocated on the stack instead of the heap
  • Synchronous elimination: Clear synchronous operations, usually referred to as synchronized

Summarize

Generally speaking, the performance of machine code compiled by JIT is higher than that of interpreter. The startup time of the C2 compiler is slower than that of the C1 compiler. After the system runs stably, the execution speed of the C2 compiler is much faster than that of the C1 compiler.

10. StringTable

10.1. Basic properties of String

  • String: a string, represented by a pair of "" quotes
  • String is declared final and cannot be inherited
  • String implements the Serializable interface: indicates that the string supports serialization.
  • String implements the Comparable interface: it means that the string can be compared in size
  • String defined final char[] value in jdk8 and before to store string data. Change to byte[] in JDK9

10.1.1. String storage structure changes in jdk9

Official website address: JEP 254: Compact Strings (java.net)

motivation

The current implementation of the String class stores characters in a char array, using two bytes (16 bits) per character. Data collected from many different applications shows that strings are a major component of heap usage, and moreover, most string objects contain only Latin-1 characters. These characters only require one byte of storage, so half of the internal character arrays of these string objects are unused.

illustrate

We propose to change the internal representation of the String class from an array of UTF-16 characters to an array of bytes plus an encoding flag field. The new String class will store character encodings as ISO-8859-1/Latin-1 (one byte per character) or UTF-16 (two bytes per character), depending on the content of the string. The encoding flag will indicate which encoding is used.

String-related classes such as AbstractStringBuilder, StringBuilder, and StringBuffer will be updated to use the same representation, as will HotSpot VM's intrinsic string operations.

This is purely an implementation change, no changes to the existing public interface. There are currently no plans to add any new public APIs or other interfaces.

The prototyping work done so far has confirmed the expected reduction in memory footprint, a large reduction in GC activity, and a slight performance regression in some corner cases.

Conclusion: String is no longer stored in char[], but changed to byte[] plus encoding mark, which saves some space

public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
    @Stable
    private final byte[] value;
}

10.1.2. Basic properties of String

String: Represents an immutable sequence of characters. Short name: immutability.

  • When reassigning a string, it is necessary to rewrite the specified memory area for assignment, and the original value cannot be used for assignment.
  • When performing concatenation operations on existing strings, it is necessary to re-specify the memory area assignment, and the original value assignment cannot be used.
  • When calling the replace() method of string to modify the specified character or string, it is also necessary to re-specify the memory area for assignment, and the original value cannot be used for assignment.

Assign a value to a string by means of a literal value (different from new), and the string value at this time is declared in the string constant pool.

The string constant pool will not store strings with the same content

String's String Pool is a fixed-size Hashtable with a default size and length of 1009. If there are too many Strings put into the String Pool, serious Hash conflicts will result, resulting in a very long linked list, and the direct impact of a long linked list is that the performance will drop significantly when calling String.intern.

Use -XX:StringTablesizecan set the length of StringTable

  • StringTable is fixed in jdk6, which is the length of 1009, so if there are too many strings in the constant pool, the efficiency will drop rapidly. StringTablesize setting is not required
  • In jdk7, the default value of the StringTable length is 60013, and the StringTablesize setting is not required
  • In JDK8, if you set the length of StringTable, 1009 is the minimum value that can be set

10.2. String memory allocation

There are 8 basic data types and a special type String in the Java language. In order to make them faster and save memory during operation, these types provide a concept of constant pool.

The constant pool is similar to a cache provided by the Java system level. The constant pools of the eight basic data types are coordinated by the system, and the constant pool of the String type is special. There are two main ways to use it.

  • String objects declared directly using double quotes will be directly stored in the constant pool.
  • If it is not a String object declared with double quotes, you can use the intern() method provided by String. This will be discussed later

Java 6 and before, the string constant pool is stored in the permanent generation

In Java 7, Oracle engineers made a big change to the logic of the string pool, that is, to adjust the location of the string constant pool to the Java heap

  • All strings are saved in the heap (Heap), just like other ordinary objects, so that you only need to adjust the heap size when tuning the application.
  • The concept of the string constant pool was originally used a lot, but this change gives us enough reasons for us to reconsider using it in Java 7 String.intern().

Java8 metaspace, string constants are on the heap 

Why should StringTable be adjusted? 

In JDK 7, internal strings are no longer allocated in the permanent generation of the Java heap, but in major parts of the Java heap (called the young and old generations), along with other objects created by the application. This change will result in more data residing in the main Java heap and less data in the permanent generation, so the heap may need to be resized. Most applications will see a relatively small difference in heap usage due to this change, but large applications that load many classes or make heavy use of the String.intern() method will see a more noticeable difference.

10.3. Basic operations on String

/**
* 观察加载了多个String
**/
@Test
public void test1() {
    System.out.print1n("1"); //2321
    System.out.println("2");
    System.out.println("3");
    System.out.println("4");
    System.out.println("5");
    System.out.println("6");
    System.out.println("7");
    System.out.println("8");
    System.out.println("9");
    System.out.println("10"); //2330
    System.out.println("1"); //2321
    System.out.println("2"); //2322
    System.out.println("3");
    System.out.println("4");
    System.out.println("5");
    System.out.print1n("6");
    System.out.print1n("7");
    System.out.println("8");
    System.out.println("9");
    System.out.println("10");//2330
}

The Java language specification requires that identical string literals should contain the same Unicode character sequence (constants containing the same code point sequence), and must point to the same String class instance.

class Memory {
    public static void main(String[] args) {//line 1
        int i= 1;//line 2
        Object obj = new Object();//line 3
        Memory mem = new Memory();//Line 4
        mem.foo(obj);//Line 5
    }//Line 9
    private void foo(Object param) {//line 6
        String str = param.toString();//line 7
        System.out.println(str);
    }//Line 8
}


10.4. String concatenation operation

  • The splicing result of constants and constants is in the constant pool, the principle is compile-time optimization
  • Variables with the same content will not exist in the constant pool
  • As long as one of them is a variable, the result is on the heap. The principle of variable splicing is StringBuilder
  • If the splicing result calls the intern() method, actively put the string object that is not in the constant pool into the pool, and return the address of this object

Example 1

  public static void test1() {
      // 都是常量,前端编译期会进行代码优化
      // 通过idea直接看对应的反编译的class文件,会显示 String s1 = "abc"; 说明做了代码优化
      String s1 = "a" + "b" + "c";  
      String s2 = "abc"; 
  
      // true,有上述可知,s1和s2实际上指向字符串常量池中的同一个值
      System.out.println(s1 == s2); 
  }

Example 2

public static void test5() {
    String s1 = "javaEE";
    String s2 = "hadoop";

    String s3 = "javaEEhadoop";
    String s4 = "javaEE" + "hadoop";    
    String s5 = s1 + "hadoop";
    String s6 = "javaEE" + s2;
    String s7 = s1 + s2;

    System.out.println(s3 == s4); // true 编译期优化
    System.out.println(s3 == s5); // false s1是变量,不能编译期优化
    System.out.println(s3 == s6); // false s2是变量,不能编译期优化
    System.out.println(s3 == s7); // false s1、s2都是变量
    System.out.println(s5 == s6); // false s5、s6 不同的对象实例
    System.out.println(s5 == s7); // false s5、s7 不同的对象实例
    System.out.println(s6 == s7); // false s6、s7 不同的对象实例

    String s8 = s6.intern();
    System.out.println(s3 == s8); // true intern之后,s8和s3一样,指向字符串常量池中的"javaEEhadoop"
}

Example 3

public void test6(){
    String s0 = "beijing";
    String s1 = "bei";
    String s2 = "jing";
    String s3 = s1 + s2;
    System.out.println(s0 == s3); // false s3指向对象实例,s0指向字符串常量池中的"beijing"
    String s7 = "shanxi";
    final String s4 = "shan";
    final String s5 = "xi";
    String s6 = s4 + s5;
    System.out.println(s6 == s7); // true s4和s5是final修饰的,编译期就能确定s6的值了
}
  • Without final modification, it is a variable. For example, s1 and s2 in line s3 will be spliced ​​by new StringBuilder
  • Use final modification, which is a constant. Code optimization will be performed by the compiler. In actual development, if you can use final, try to use

Example 4

public void test3(){
    String s1 = "a";
    String s2 = "b";
    String s3 = "ab";
    String s4 = s1 + s2;
    System.out.println(s3==s4);
}

bytecode

We take a look at the bytecode of Example 4, and we can find that s1 + s2a StringBuilder object is actually new, and the append method is used to add s1 and s2, and finally the toString method is called to assign it to s4

 0 ldc #2 <a>
 2 astore_1
 3 ldc #3 <b>
 5 astore_2
 6 ldc #4 <ab>
 8 astore_3
 9 new #5 <java/lang/StringBuilder>
12 dup
13 invokespecial #6 <java/lang/StringBuilder.<init>>
16 aload_1
17 invokevirtual #7 <java/lang/StringBuilder.append>
20 aload_2
21 invokevirtual #7 <java/lang/StringBuilder.append>
24 invokevirtual #8 <java/lang/StringBuilder.toString>
27 astore 4
29 getstatic #9 <java/lang/System.out>
32 aload_3
33 aload 4
35 if_acmpne 42 (+7)
38 iconst_1
39 goto 43 (+4)
42 iconst_0
43 invokevirtual #10 <java/io/PrintStream.println>
46 return

String concatenation operation performance comparison

public class Test
{
    public static void main(String[] args) {
        int times = 50000;

        // String
        long start = System.currentTimeMillis();
        testString(times);
        long end = System.currentTimeMillis();
        System.out.println("String: " + (end-start) + "ms");

        // StringBuilder
        start = System.currentTimeMillis();
        testStringBuilder(times);
        end = System.currentTimeMillis();
        System.out.println("StringBuilder: " + (end-start) + "ms");

        // StringBuffer
        start = System.currentTimeMillis();
        testStringBuffer(times);
        end = System.currentTimeMillis();
        System.out.println("StringBuffer: " + (end-start) + "ms");
    }

    public static void testString(int times) {
        String str = "";
        for (int i = 0; i < times; i++) {
            str += "test";
        }
    }

    public static void testStringBuilder(int times) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < times; i++) {
            sb.append("test");
        }
    }

    public static void testStringBuffer(int times) {
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < times; i++) {
            sb.append("test");
        }
    }
}

// 结果
String: 7963ms
StringBuilder: 1ms
StringBuffer: 4ms

In this experiment, 50,000 cycles are performed, and the time of the String splicing method is about 8000 times that of the StringBuilder.append method, and the time of the StringBuffer.append() method is about 4 times that of the StringBuilder.append() method

It can be seen that the speed of the append method of StringBuilder is not a little bit faster than the method of directly using "+" splicing on String

Then, in actual development, for operations that require multiple or a large number of splicing, we should use StringBuilder for append operations as much as possible without considering thread safety issues.

In addition, what other operations can help us improve the efficiency of string operations?

The initialization size of the StringBuilder empty parameter constructor is 16. Then, if you know in advance the number of Strings that need to be concatenated, you should directly use the constructor with parameters to specify the capacity to reduce the number of expansions (you can check the source code yourself for the expansion logic)

/**
 * Constructs a string builder with no characters in it and an
 * initial capacity of 16 characters.
 */
public StringBuilder() {
    super(16);
}

/**
 * Constructs a string builder with no characters in it and an
 * initial capacity specified by the {@code capacity} argument.
 *
 * @param      capacity  the initial capacity.
 * @throws     NegativeArraySizeException  if the {@code capacity}
 *               argument is less than {@code 0}.
 */
public StringBuilder(int capacity) {
    super(capacity);
}

10.5. Use of intern()

When the intern method is called, if the pool already contains a string equal to this String object, as determined by the equals(Object) method, then the string in the pool is returned. Otherwise, this String object is added to the pool and a reference to this String object is returned.
It follows that for any two strings s and t, s.intern() == t.intern() is true if and only if s.equals(t) is true.
All literal strings and constant expressions taking string values ​​are interned.
Returns a string with the same content as this string, but guaranteed to be from a unique pool of strings.

intern is a native method that calls the underlying C method

public native String intern();

If it is not a String object declared with double quotes, you can use the intern method provided by String, which will query whether the current string exists from the string constant pool, and if it does not exist, put the current string into the constant pool.

String myInfo = new string("I love atguigu").intern();

That is to say, if the String.intern method is called on any string, the class instance pointed to by the returned result must be exactly the same as the string instance that appears directly in the form of a constant. Therefore, the following expressions must evaluate to true

("a"+"b"+"c").intern() == "abc"

In layman's terms, Interned string is to ensure that there is only one copy of the string in memory, which can save memory space and speed up the execution of string manipulation tasks. Note that this value will be stored in the String Intern Pool (String Intern Pool)

10.5.1. The use of intern: JDK6 vs JDK7/8

/**
 * ① String s = new String("1")
 * 创建了两个对象
 * 		堆空间中一个new对象
 * 		字符串常量池中一个字符串常量"1"(注意:此时字符串常量池中已有"1")
 * ② s.intern()由于字符串常量池中已存在"1"
 * 
 * s  指向的是堆空间中的对象地址
 * s2 指向的是堆空间中常量池中"1"的地址
 * 所以不相等
 */
String s = new String("1");
s.intern();
String s2 = "1";
System.out.println(s==s2); // jdk1.6 false jdk7/8 false

/*
 * ① String s3 = new String("1") + new String("1")
 * 等价于new String("11"),但是,常量池中并不生成字符串"11";
 *
 * ② s3.intern()
 * 由于此时常量池中并无"11",所以把s3中记录的对象的地址存入常量池
 * 所以s3 和 s4 指向的都是一个地址
*/
String s3 = new String("1") + new String("1");
s3.intern();
String s4 = "11";
System.out.println(s3==s4); //jdk1.6 false jdk7/8 true

 To summarize the use of String's intern():

In JDK1.6, try to put this string object into the string pool.

  • If there is one in the string pool, it will not be put in. Returns the address of an object in an existing string pool
  • If not, this object will be copied, put into the string pool, and the object address in the string pool will be returned

Starting from JDK1.7, try to put this string object into the string pool.

  • If there is one in the string pool, it will not be put in. Returns the address of an object in an existing string pool
  • If not, it will copy the reference address of the object, put it into the string pool, and return the reference address in the string pool

 

11. Garbage collection overview and algorithm

11.1. Garbage Collection Overview

11.1.1. What is garbage

Garbage collection is not a byproduct of the Java language. As early as 1960, the first Lisp language that started to use memory dynamic allocation and garbage collection technology was born.

There are three classic problems with garbage collection:

  • What memory needs to be reclaimed?
  • When is it recycled?
  • How to recycle?

What is trash?

Garbage refers to an object that does not have any pointers in the running program, and this object is the garbage that needs to be recycled.

If the garbage in the memory is not cleaned up in time, the memory space occupied by these garbage objects will be kept until the end of the application, and the reserved space cannot be used by other objects, and may even cause memory overflow.

11.1.2. Why GC is needed

If you want to learn GC, you first need to understand why GC is needed?

For high-level languages, a basic understanding is that if garbage collection is not performed, the memory will be consumed sooner or later, because the memory space is continuously allocated without recycling, it is like constantly producing domestic garbage without cleaning it.

除了释放没用的对象,垃圾回收也可以清除内存里的记录碎片。碎片整理将所占用的堆内存移到堆的一端,以便JVM将整理出的内存分配给新的对象。

随着应用程序所应付的业务越来越庞大、复杂,用户越来越多,没有GC就不能保证应用程序的正常进行。而经常造成STW的GC又跟不上实际的需求,所以才会不断地尝试对GC进行优化。

11.1.3. 早期垃圾回收

在早期的C/C++时代,垃圾回收基本上是手工进行的。开发人员可以使用new关键字进行内存申请,并使用delete关键字进行内存释放。比如以下代码:

MibBridge *pBridge= new cmBaseGroupBridge();
//如果注册失败,使用Delete释放该对象所占内存区域
if (pBridge->Register(kDestroy) != NO ERROR)
	delete pBridge;

这种方式可以灵活控制内存释放的时间,但是会给开发人员带来频繁申请和释放内存的管理负担。倘若有一处内存区间由于程序员编码的问题忘记被回收,那么就会产生内存泄漏,垃圾对象永远无法被清除,随着系统运行时间的不断增长,垃圾对象所耗内存可能持续上升,直到出现内存溢出并造成应用程序崩溃。

在有了垃圾回收机制后,上述代码极有可能变成这样

MibBridge *pBridge = new cmBaseGroupBridge(); 
pBridge->Register(kDestroy);

现在,除了Java以外,C#、Python、Ruby等语言都使用了自动垃圾回收的思想,也是未来发展趋势,可以说这种自动化的内存分配和来及回收方式已经成为了线代开发语言必备的标准。

11.1.4. Java垃圾回收机制

自动内存管理,无需开发人员手动参与内存的分配与回收,这样降低内存泄漏和内存溢出的风险

  • 没有垃圾回收器,java也会和cpp一样,各种悬垂指针,野指针,泄露问题让你头疼不已。

自动内存管理机制,将程序员从繁重的内存管理中释放出来,可以更专心地专注于业务开发

oracle官网关于垃圾回收的介绍 https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/toc.html

担忧

对于Java开发人员而言,自动内存管理就像是一个黑匣子,如果过度依赖于“自动”,那么这将会是一场灾难,最严重的就会弱化Java开发人员在程序出现内存溢出时定位问题和解决问题的能力。

此时,了解JVM的自动内存分配和内存回收原理就显得非常重要,只有在真正了解JVM是如何管理内存后,我们才能够在遇见outofMemoryError时,快速地根据错误异常日志定位问题和解决问题。

当需要排查各种内存溢出、内存泄漏问题时,当垃圾收集成为系统达到更高并发量的瓶颈时,我们就必须对这些“自动化”的技术实施必要的监控和调节。

GC主要关注的区域

GC主要关注于 方法区 和堆中的垃圾收集

垃圾收集器可以对年轻代回收,也可以对老年代回收,甚至是全栈和方法区的回收。其中,Java堆是垃圾收集器的工作重点

从次数上讲:

  • 频繁收集Young区
  • 较少收集Old区
  • 基本不收集Perm区(元空间)

11.2. 垃圾回收相关算法

对象存活判断

在堆里存放着几乎所有的Java对象实例,在GC执行垃圾回收之前,首先需要区分出内存中哪些是存活对象,哪些是已经死亡的对象。只有被标记为己经死亡的对象,GC才会在执行垃圾回收时,释放掉其所占用的内存空间,因此这个过程称为垃圾标记阶段。

当一个对象已经不再被任何的存活对象继续引用时,就可以宣判为已经死亡。

判断对象存活一般有两种方式:引用计数算法和可达性分析算法。

11.2.1.  标记阶段:引用计数算法

方式一:引用计数算法

引用计数算法(Reference Counting)比较简单,对每个对象保存一个整型的引用计数器属性。用于记录对象被引用的情况。

对于一个对象A,只要有任何一个对象引用了A,则A的引用计数器就加1;当引用失效时,引用计数器就减1。只要对象A的引用计数器的值为0,即表示对象A不可能再被使用,可进行回收。

优点:实现简单,垃圾对象便于辨识;判定效率高,回收没有延迟性。

缺点:

  • 它需要单独的字段存储计数器,这样的做法增加了存储空间的开销。
  • 每次赋值都需要更新计数器,伴随着加法和减法操作,这增加了时间开销。
  • 引用计数器有一个严重的问题,即无法处理循环引用的情况。这是一条致命缺陷,导致在Java的垃圾回收器中没有使用这类算法。

循环引用

当p的指针断开的时候,内部的引用形成一个循环,这就是循环引用

举例

Test whether the reference counting algorithm is used in Java

public class RefCountGC {
    // 这个成员属性的唯一作用就是占用一点内存
    private byte[] bigSize = new byte[5*1024*1024];
    // 引用
    Object reference = null;

    public static void main(String[] args) {
        RefCountGC obj1 = new RefCountGC();
        RefCountGC obj2 = new RefCountGC();
        
        obj1.reference = obj2;
        obj2.reference = obj1;
        
        obj1 = null;
        obj2 = null;
        // 显示的执行垃圾收集行为
        // 这里发生GC,obj1和obj2是否被回收?
        System.gc();
    }
}
// 运行结果
PSYoungGen: 15490K->808K(76288K)] 15490K->816K(251392K)

The above-mentioned behavior of GC collection, so it can be proved that the algorithm used in the JVM is not a reference counter

summary

The reference counting algorithm is the resource recycling choice of many languages, such as Python, which is more popular due to artificial intelligence, and it supports both reference counting and garbage collection mechanisms.

Which one is best depends on the scenario. There are large-scale practices in the industry that only retain the reference counting mechanism to improve throughput.

Java did not choose reference counting because it has a basic problem, that is, it is difficult to deal with circular reference relationships.

How does Python solve circular references?

  • Manual release: It is easy to understand, that is, to release the reference relationship at the right time. Use weak references weakref, weakref is a standard library provided by Python, designed to solve circular references.

11.2.2. Marking Phase: Reachability Analysis Algorithm

Reachability analysis algorithms (root search algorithm, tracing garbage collection)

Compared with the reference counting algorithm, the reachability analysis algorithm not only has the characteristics of simple implementation and high execution efficiency, but more importantly, the algorithm can effectively solve the problem of circular references in the reference counting algorithm and prevent memory leaks.

Compared with the reference counting algorithm, the accessibility analysis here is chosen by Java and C#. This type of garbage collection is also commonly called Tracing Garbage Collection

The so-called "GCRoots" root set is a set of references that must be active.

The basic idea

  • The reachability analysis algorithm starts from the root object collection (GCRoots), and searches whether the target objects connected by the root object collection are reachable in a top-down manner.
  • After using the reachability analysis algorithm, the surviving objects in the memory will be directly or indirectly connected by the root object collection, and the path traveled by the search is called the reference chain (Reference Chain)
  • If the target object is not connected by any reference chain, it is unreachable, which means that the object has died and can be marked as a garbage object.
  • In the reachability analysis algorithm, only objects that can be directly or indirectly connected by the root object collection are surviving objects.

In the Java language, GC Roots includes the following types of elements:

  • Object 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 known as native methods) in the native method stack
  • The object referenced by the class static attribute in the method area, for example: the reference type static variable of the Java class
  • Objects referenced by constants in the method area, for example: references in the string constant pool (String Table)
  • 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.

In addition to these fixed GC Roots collections, depending on the garbage collector selected by the user and the currently reclaimed memory area, other objects can also be added "temporarily" to form a complete GC Roots collection. For example: generational collection and partial recycling (PartialGC).

If you only perform garbage collection for a certain area in the Java heap (for example: typically only for the new generation), you must consider that the memory area is an implementation detail of the virtual machine itself, and it is not isolated and closed. Objects in this area are entirely possible It is referenced by objects in other regions. At this time, it is necessary to add the associated region objects to the GCRoots collection to ensure the accuracy of the accessibility analysis.

Tip: Since Root uses the stack method to store variables and pointers, if a pointer stores objects in the heap memory but does not store them in the heap memory, then it is a Root.

Notice

If you want to use the reachability analysis algorithm to determine whether the memory can be reclaimed, then the analysis work must be performed in a snapshot that can guarantee consistency. If this point is not satisfied, the accuracy of the analysis results cannot be guaranteed.

This is also an important reason why GC must "stop The World".

  • Even in the CMS collector, which claims to (almost) never pause, it must pause when enumerating the root node.

11.2.3. Object finalization mechanism

The Java language provides an object finalization mechanism to allow developers to provide custom processing logic before an object is destroyed.

When the garbage collector finds that there is no reference pointing to an object, that is: before garbage collecting this object, it will always call the finalize() method of this object.

The finalize() method is allowed to be overridden in subclasses to release resources when the object is recycled. Usually, some resource release and cleanup work is performed in this method, such as closing files, sockets, and database connections.

Never actively call the finalize() method of an object. It should be called by the garbage collection mechanism. The reasons include the following three points:

  • It may cause the object to be resurrected during finalize().
  • The execution time of the finalize() method is not guaranteed. It is completely determined by the GC thread. In extreme cases, if the GC does not occur, the finalize() method will have no chance to execute.
  • A bad finalize() can seriously affect the performance of GC.

Functionally, the finalize() method is similar to the destructor in C, but Java uses an automatic memory management mechanism based on the garbage collector, so the finalize() method is essentially different from the destructor in C .

Due to the existence of the finalize() method, objects in the virtual machine are generally in three possible states.

To live or to die?

If an object cannot be accessed from any root node, it means that the object is no longer used. Generally speaking, this object needs to be recycled. But in fact, it is not "must die", at this time they are temporarily in the "probation" stage. An untouchable object may "resurrect" itself under a certain condition. If so, its recycling is unreasonable. For this reason, three possible states of the object in the virtual machine are defined. as follows:

  • Reachable: Starting from the root node, this object can be reached.
  • Resurrectable: All references to the object are released, but it is possible for the object to be resurrected in finalize().
  • Untouchable: The finalize() of the object is called, and it is not resurrected, then it will enter the untouchable state. Untouchable objects cannot be resurrected because finalize() will only be called once.

Among the above three states, the distinction is made due to the existence of the finalize() method. Only when the object is untouchable can it be recycled.

Specific process

To determine whether an object objA is recyclable, at least two marking processes are required:

  1. If there is no reference chain from object objA to GC Roots, the first marking is performed.
  2. Filter to determine whether it is necessary to execute the finalize() method for this object
  3. 如果对象objA没有重写finalize()方法,或者finalize()方法已经被虚拟机调用过,则虚拟机视为“没有必要执行”,objA被判定为不可触及的。
  4. 如果对象objA重写了finalize()方法,且还未执行过,那么objA会被插入到F-Queue队列中,由一个虚拟机自动创建的、低优先级的Finalizer线程触发其finalize()方法执行。
  5. finalize()方法是对象逃脱死亡的最后机会,稍后GC会对F-Queue队列中的对象进行第二次标记。如果objA在finalize()方法中与引用链上的任何一个对象建立了联系,那么在第二次标记时,objA会被移出“即将回收”集合。之后,对象会再次出现没有引用存在的情况。在这个情况下,finalize方法不会被再次调用,对象会直接变成不可触及的状态,也就是说,一个对象的finalize方法只会被调用一次。

举例

public class CanReliveObj {
    // 类变量,属于GC Roots的一部分
    public static CanReliveObj canReliveObj;

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("调用当前类重写的finalize()方法");
        canReliveObj = this;
    }

    public static void main(String[] args) throws InterruptedException {
        canReliveObj = new CanReliveObj();
        canReliveObj = null;
        System.gc();
        System.out.println("-----------------第一次gc操作------------");
        // 因为Finalizer线程的优先级比较低,暂停2秒,以等待它
        Thread.sleep(2000);
        if (canReliveObj == null) {
            System.out.println("obj is dead");
        } else {
            System.out.println("obj is still alive");
        }

        System.out.println("-----------------第二次gc操作------------");
        canReliveObj = null;
        System.gc();
        // 下面代码和上面代码是一样的,但是 canReliveObj却自救失败了
        Thread.sleep(2000);
        if (canReliveObj == null) {
            System.out.println("obj is dead");
        } else {
            System.out.println("obj is still alive");
        }

    }
}

运行结果:

-----------------第一次gc操作------------
调用当前类重写的finalize()方法
obj is still alive
-----------------第二次gc操作------------
obj is dead

在第一次GC时,执行了finalize方法,但finalize()方法只会被调用一次,所以第二次该对象被GC标记并清除了。

11.2.4. MAT与JProfiler的GC Roots溯源

MAT是什么?

MAT是Memory Analyzer的简称,它是一款功能强大的Java堆内存分析器。用于查找内存泄漏以及查看内存消耗情况。

MAT是基于Eclipse开发的,是一款免费的性能分析工具。

大家可以在 Eclipse Memory Analyzer Open Source Project | The Eclipse Foundation 下载并使用MAT

获取dump文件

方式一:命令行使用 jmap

方式二:使用JVisualVM导出

捕获的heap dump文件是一个临时文件,关闭JVisualVM后自动删除,若要保留,需要将其另存为文件。

可通过以下方法捕获heap dump:

  • Right-click on the application in the left "Application" sub-window and select Heap Dump.
  • Click the Heap Dump button in the Monitor subtab.

Heap dumps for native applications are opened as a subtab of the Applications tab. At the same time, the heap dump corresponds to a node with a time stamp in the Application (application) column on the left.

Right-click the node and select save as (save as) to save the heap dump locally.

Method 3: Open the Dump file with MAT

JProfiler's GC Roots traceability

In actual development, we generally do not search for all GC Roots, but may just search for the entire link of an object, or GC Roots traceability. At this time, we can use JProfiler

11.2.5. Sweeping Phase: Mark-Sweep Algorithm

After successfully distinguishing between living objects and dead objects in memory, the next task of GC is to perform garbage collection to release the memory space occupied by useless objects so that there is enough available memory space to allocate memory for new objects.

At present, the three common garbage collection algorithms in the JVM are mark-sweep algorithm (Mark-Sweep), copy algorithm (copying), mark-compression algorithm (Mark-Compact)

Mark-Clear Algorithm (Mark-Sweep)

The Mark-Sweep algorithm (Mark-Sweep) is a very basic and common garbage collection algorithm, which was proposed by J. McCarthy et al. in 1960 and applied to the Lisp language.

Implementation process

When the effective memory space (available memory) in the heap is exhausted, the entire program will be stopped (also known as stop the world), and then two tasks will be performed, the first is marking, and the second is to clear

  • Mark: Collector traverses from the reference root node and marks all referenced objects. Generally, it is recorded as a reachable object in the Header of the object.
  • Cleanup: Collector traverses the heap memory linearly from beginning to end. If an object is found not marked as a reachable object in its Header, it will be recycled

shortcoming

  • The efficiency of the mark-and-sweep algorithm is not high
  • When performing GC, the entire application needs to be stopped, and the user experience is poor
  • 这种方式清理出来的空闲内存是不连续的,产生内碎片,需要维护一个空闲列表

何为清除?

这里所谓的清除并不是真的置空,而是把需要清除的对象地址保存在空闲的地址列表里。下次有新对象需要加载时,判断垃圾的位置空间是否够,如果够,就存放覆盖原有的地址。

11.2.6. 清除阶段:复制算法

复制(Copying)算法

为了解决标记-清除算法在垃圾收集效率方面的缺陷,M.L.Minsky于1963年发表了著名的论文,“使用双存储区的Lisp语言垃圾收集器CA LISP Garbage Collector Algorithm Using Serial Secondary Storage)”。M.L.Minsky在该论文中描述的算法被人们称为复制(Copying)算法,它也被M.L.Minsky本人成功地引入到了Lisp语言的一个实现版本中。

核心思想

将活着的内存空间分为两块,每次只使用其中一块,在垃圾回收时将正在使用的内存中的存活对象复制到未被使用的内存块中,之后清除正在使用的内存块中的所有对象,交换两个内存的角色,最后完成垃圾回收

优点

  • 没有标记和清除过程,实现简单,运行高效
  • 复制过去以后保证空间的连续性,不会出现“碎片”问题。

缺点

  • 此算法的缺点也是很明显的,就是需要两倍的内存空间。
  • 对于G1这种分拆成为大量region的GC,复制而不是移动,意味着GC需要维护region之间对象引用关系,不管是内存占用或者时间开销也不小

特别的

如果系统中的垃圾对象很多,复制算法需要复制的存活对象数量并不会太大,或者说非常低才行

应用场景

在新生代,对常规应用的垃圾回收,一次通常可以回收70% - 99% 的内存空间。回收性价比很高。所以现在的商业虚拟机都是用这种收集算法回收新生代。

11.2.7. 清除阶段:标记-压缩(整理)算法

标记-压缩(或标记-整理、Mark-Compact)算法

The efficiency of the replication algorithm is based on the premise of fewer surviving objects and more garbage objects. This situation often occurs in the young generation, but in the old generation, it is more common that most objects are live objects. If the replication algorithm is still used, the cost of replication will be high due to the large number of surviving objects. Therefore, based on the characteristics of old generation garbage collection, other algorithms need to be used.

The mark-clear algorithm can indeed be applied in the old generation, but the algorithm is not only inefficient, but also generates memory fragments after memory recovery is performed, so JVM designers need to improve on this basis. Mark-compression (Mark-Compact) algorithm was born.

Around 1970, researchers such as GLSteele, CJChene and DsWise released mark-compression algorithms. In many modern garbage collectors, one uses the mark-compact algorithm or an improved version thereof.

Implementation process

  1. The first stage is the same as the mark-and-clean algorithm, marking all referenced objects from the root node
  2. The second stage compresses all surviving objects to one end of the memory and discharges them sequentially.
  3. Afterwards, all spaces outside the bounds are cleared.

The final effect of the mark-compression algorithm is equivalent to performing a memory defragmentation after the mark-sweep algorithm is executed. Therefore, it can also be called the mark-sweep-compression (Mark-Sweep-Compact) algorithm.

The essential difference between the two is that the mark-sweep algorithm is a non-moving recovery algorithm, and the mark-compression is mobile. Whether to move the surviving objects after recycling is a risky decision with both advantages and disadvantages. It can be seen that the marked surviving objects will be sorted and arranged in order according to the memory address, while the unmarked memory will be cleaned up. In this way, when we need to allocate memory for a new object, the JVM only needs to hold the start address of a memory, which is obviously much less overhead than maintaining a free list.

Bump the Pointer

If the memory space is distributed in a regular and orderly manner, that is, the used and unused memory are on their own sides, and a marker pointer that records the starting point of the next allocation is maintained between each other. When allocating memory for a new object, only need By modifying the offset of the pointer to allocate the new object in the first free memory location, this allocation method is called pointer collision (Bump tHe Pointer).

advantage

  • Eliminates the disadvantage of scattered memory areas in the mark-clear algorithm. When we need to allocate memory for a new object, the JVM only needs to hold a starting address of the memory.
  • Eliminates the high cost of halving memory in the copy algorithm.

shortcoming

  • In terms of efficiency, the mark-sort algorithm is lower than the copy algorithm.
  • While moving the object, if the object is referenced by other objects, you also need to adjust the address of the reference
  • During the moving process, the user application needs to be suspended all the way. Namely: STW

11.2.8. Summary

Mark-Sweep

Mark-Compact

Copying

rate

medium

slowest

fastest

space overhead

Less (but builds up debris)

Less (does not accumulate debris)

Typically requires 2x the space of live objects (does not accumulate debris)

moving object

no

yes

yes

11.2.9. Generational collection algorithm

Among all the above algorithms, none of them can completely replace other algorithms, and they all have their own unique advantages and characteristics. The generational collection algorithm came into being.

The generational collection algorithm is based on the fact that different objects have different life cycles. Therefore, objects with different life cycles can be collected in different ways to improve recycling efficiency. Generally, the Java heap is divided into the new generation and the old generation, so that different recycling algorithms can be used according to the characteristics of each age to improve the efficiency of garbage collection.

During the running of a Java program, a large number of objects will be generated, some of which are related to business information, such as Session objects, threads, and socket connections in Http requests. These objects are directly linked to business, so their life cycle is relatively long. However, there are still some objects, which are mainly temporary variables generated during the running of the program. The life cycle of these objects will be relatively short, such as: String objects. Use once and recycle.

At present, almost all GCs use the generational collection algorithm to perform garbage collection.

In HotSpot, based on the concept of generation, the memory recovery algorithm used by GC must combine the characteristics of the young generation and the old generation.

Young Generation

The characteristics of the young generation: the area is smaller than the old generation, the object life cycle is short, the survival rate is low, and the recycling is frequent.

In this case, the recycling speed of the replication algorithm is the fastest. The efficiency of the copy algorithm is only related to the size of the current surviving objects, so it is very suitable for the recovery of the young generation. The problem of low memory utilization of the replication algorithm is alleviated by the design of two survivors in hotspot.

Old generation (Tenured Gen)

The characteristics of the old generation: the area is larger, the object life cycle is long, the survival rate is high, and the collection is not as frequent as the young generation.

In this case, there are a large number of objects with a high survival rate, and the replication algorithm becomes obviously inappropriate. It is usually implemented by mark-sweep or a mixture of mark-sweep and mark-collate.

  • The overhead of the Mark phase is proportional to the number of surviving objects.
  • The overhead of the Sweep phase is directly related to the size of the managed area.
  • The overhead of the Compact phase is proportional to the data of the surviving objects.

Take the CMS recycler in HotSpot as an example. The CMS is implemented based on Mark-Sweep, and the recycling efficiency of objects is very high. For the fragmentation problem, CMS adopts the Serial Old recycler based on the Mark-Compact algorithm as a compensation measure: when the memory recovery is not good (Concurrent Mode Failure caused by fragmentation), the Serial Old will be used to perform Full GC to achieve the memory recovery of the old generation. tidy.

The idea of ​​generation is widely used by existing virtual machines. Almost all garbage collectors distinguish between young and old generations

11.2.X. Incremental collection algorithm, partition algorithm

incremental collection algorithm

With the above-mentioned existing algorithm, the application software will be in a Stop the World state during the garbage collection process. In the Stop the World state, all threads of the application will be suspended, suspending all normal work, and waiting for the completion of garbage collection. If the garbage collection time is too long, the application will be suspended for a long time, which will seriously affect the user experience or system stability. In order to solve this problem, the research on the real-time garbage collection algorithm directly leads to the birth of the incremental collection (Incremental Collecting) algorithm.

basic idea

If processing all the garbage at one time requires a long pause in the system, then the garbage collection thread and the application thread can be executed alternately. Each time, the garbage collection thread collects only a small area of ​​memory space and then switches to the application thread. Repeat in turn until garbage collection is complete.

In general, the basis of the incremental collection algorithm is still the traditional mark-sweep and copy algorithm. The incremental collection algorithm allows garbage collection threads to complete marking, cleaning, or copying in a phased manner by properly handling conflicts between threads

shortcoming

In this way, since the application code is executed intermittently during the garbage collection process, the system pause time can be reduced. However, because of the consumption of thread switching and context switching, the overall cost of garbage collection will increase, resulting in a decrease in system throughput.

partition algorithm

Generally speaking, under the same conditions, the larger the heap space, the longer the time required for a GC, and the longer the pause related to GC. In order to better control the pause time generated by GC, a large memory area is divided into multiple small blocks. According to the target pause time, several small areas are reasonably reclaimed each time instead of the entire heap space, thereby reducing one GC resulting pause.

The generation algorithm will divide the object into two parts according to the life cycle length, and the partition algorithm will divide the entire heap space into different continuous small areas.

Each cell is used independently and recycled independently. The advantage of this algorithm is that it can control how many cells are recovered at one time.

12. Garbage collection related concepts

12.1. Understanding of System.gc()

By default, calling system.gc() or Runtime.getRuntime().gc() will explicitly trigger Full GC, and recycle the old generation and the new generation at the same time, trying to release the memory occupied by the discarded object.

However, the System.gc() call comes with a disclaimer that the call to the garbage collector is not guaranteed. (immediate effect not guaranteed)

JVM implementers can determine the GC behavior of the JVM through the System.gc() call. Under normal circumstances, garbage collection should be performed automatically without manual triggering, otherwise it would be too much trouble. In some special cases, like we're writing a performance benchmark, we can call System.gc() between runs

public class SystemGCTest {
    public static void main(String[] args) {
        new SystemGCTest();
        System.gc();// 提醒JVM的垃圾回收器执行gc,但是不确定是否马上执行gc
        // 与Runtime.getRuntime().gc();的作用一样
        
        System.runFinalization();//强制执行使用引用的对象的finalize()方法
    }

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("SystemGCTest 重写了finalize()");
    }
}

12.2. Memory overflow and memory leak

Out of Memory (OOM)

Although memory overflow is easier to understand than memory leak, memory overflow is also one of the culprits that cause program crashes.

Since GC has been developing, under normal circumstances, unless the memory occupied by the application grows very fast, causing garbage collection to fail to keep up with the speed of memory consumption, OOM is not prone to occur.

In most cases, the GC will perform garbage collection of various age groups. If it is really not enough, it will be enlarged and an exclusive Full GC operation will be performed. At this time, a large amount of memory will be recovered for continued use by the application.

The explanation for OutOfMemoryError in the javadoc is that there is no free memory, and the garbage collector cannot provide more memory.

First of all, let’s talk about the situation where there is no free memory: it means that the heap memory of the Java virtual machine is not enough. There are two reasons:

  1. The heap memory setting of the Java virtual machine is not enough.
    1. For example: there may be a memory leak problem;
    2. It is very likely that the size of the heap is unreasonable, for example, to process a considerable amount of data, but the JVM heap size is not explicitly specified or the specified value is too small. Can be adjusted through -Xmsparameters -Xmx.
  2. A large number of large objects are created in the code and cannot be collected by the garbage collector for a long time (there are references)
    1. For the old version of Oracle JDK, because the size of the permanent generation is limited, and the JVM is very inactive for permanent generation garbage collection (such as constant pool recycling, unloading types that are no longer needed), so when we continue to add new types , OutOfMemoryError is also very common in the permanent generation, especially when there are a large number of dynamic types generated at runtime; similar intern string caches occupy too much space, which can also cause OOM problems. The corresponding exception information will be marked and related to the permanent generation: " java.lang.OutOfMemoryError: PermGen space".
    2. With the introduction of the metadata area, the memory in the method area is no longer so embarrassing, so the corresponding OOM has improved. When OOM appears, the exception information becomes: " java.lang.OutofMemoryError:Metaspace". Insufficient direct memory can also cause OOM.

There is an implicit meaning in this, before throwing OutOfMemoryError, usually the garbage collector will be triggered, doing its best to clean up the space.

  • For example: in the analysis of the reference mechanism, it involves that the JVM will try to recycle the object pointed to by the soft reference.
  • In java.nio.BIts.reserveMemory()the method, we can clearly see that System.gc()it will be called to clean up the space.

Of course, not in every case the garbage collector will be triggered

  • For example, if we allocate a super large object, like a super large array exceeding the maximum value of the heap, the JVM can judge that garbage collection cannot solve this problem, so it directly throws OutOfMemoryError.

Memory Leak

Also known as "storage leak". Strictly speaking, only when the objects are no longer used by the program, but the GC cannot reclaim them, is it called a memory leak.

But in the actual situation, some bad practices (or negligence) will cause the life cycle of the object to become very long or even lead to 00M, which can also be called "memory leak" in a broad sense.

Although a memory leak will not cause the program to crash immediately, once a memory leak occurs, the available memory in the program will be gradually eroded until all the memory is exhausted, and an OutOfMemory exception will eventually appear, causing the program to crash.

Note that the storage space here does not refer to physical memory, but refers to the size of virtual memory, which depends on the size set in the disk swap area.

example

  1. Singleton pattern. The life cycle of a singleton is as long as that of the application. If you hold a reference to an external object, the external object cannot be recycled, which will cause a memory leak.
  2. Some resources that provide close are not closed resulting in memory leaks
    1. Database connection (dataSource.getConnection())
    2. network connection (socket)
    3. The io connection must be closed manually

12.3. Stop The World

Stop-the-World, referred to as STW, refers to the pause of the application during the occurrence of GC events. When a pause occurs, the entire application thread will be suspended without any response, a bit like a stuck feeling, this pause is called STW.

Enumerating root nodes (GC Roots) in the reachability analysis algorithm will cause all Java execution threads to stall.

  • Analysis work must be performed in a consistent snapshot
  • Consistency means that the entire execution system appears to be frozen at a certain point in time during the entire analysis
  • If the object reference relationship is still changing during the analysis process, the accuracy of the analysis results cannot be guaranteed

The application thread interrupted by STW will resume after completing the GC. Frequent interruption will make the user feel like a movie stuck due to slow network speed, so we need to reduce the occurrence of STW.

The STW event has nothing to do with which GC is used, and all GCs have this event.

Even G1 cannot completely avoid the Stop-the-World situation. It can only be said that the garbage collector is getting better and better, the recycling efficiency is getting higher and higher, and the pause time is shortened as much as possible.

STW is automatically initiated and completed by the JVM in the background. When the user is not visible, all the normal working threads of the user are stopped.

Do not use System.gc() in development will cause Stop-the-World.

12.4. Parallelism and Concurrency in Garbage Collection

Concurrent

In the operating system, it refers to several programs in a period of time between being started and running and finishing running, and these programs are all running on the same processor.

Concurrency is not "simultaneous" in the true sense, but the CPU divides a time period into several time segments (time intervals), and then switches back and forth between these time intervals. Since the CPU processing speed is very fast, as long as the time Proper spacing can make users feel that multiple applications are running at the same time.

Parallel

When the system has more than one CPU, when one CPU executes one process, another CPU can execute another process, and the two processes do not seize CPU resources from each other, and can be performed at the same time, which we call parallel (Parallel).

In fact, the factor that determines parallelism is not the number of CPUs, but the number of cores of the CPU. For example, multiple cores of a CPU can also be parallelized.

It is suitable for weak interaction scenarios such as scientific computing and background processing.

Concurrency vs Parallel

  • Concurrency refers to multiple things happening at the same time at the same time.
  • Parallel refers to multiple things happening at the same time at the same time.
  • Multiple concurrent tasks preempt resources from each other.
  • Multiple parallel tasks do not preempt resources from each other.
  • Parallelism occurs only in the case of multiple CPUs or a CPU with multiple cores.
  • Otherwise, things that seem to happen at the same time are actually executed concurrently.

Concurrency and Parallelism in Garbage Collection

Concurrency and parallelism, in the context of talking about garbage collectors, can be explained as follows:

Parallel

Refers to multiple garbage collection threads working in parallel, but at this time the user thread is still in a waiting state. Such as ParNew, Parallel Scavenge, Parallel Old;

Serial

Compared with the concept of parallelism, single-threaded execution. If the memory is not enough, the program is suspended, and the JM garbage collector is started for garbage collection. After recycling, start the program's thread again.

Concurrent

It means that the user thread and the garbage collection thread are executed at the same time (but not necessarily in parallel, they may be executed alternately), and the garbage collection thread will not stop the running of the user program during execution. The user program continues to run, while the garbage collector thread runs on another CPU; such as: CMS, G1

12.5. Safe Points and Safe Areas

safe point

When the program is executed, it is not possible to stop and start GC at all places. Only at specific positions can it stop and start GC. These positions are called "safe points (Safepoint)".

The choice of Safe Point is very important. If it is too small, it may cause the GC to wait too long. If it is too frequent, it may cause runtime performance problems. The execution time of most instructions is very short, and it is usually based on "whether it has the characteristics of allowing the program to execute for a long time" as the standard. For example: Select some instructions with a long execution time as Safe Point, such as method call, loop jump and exception jump.

How to check that all threads run to the nearest safe point and stop when GC occurs?

Preemptive interrupt : (currently no virtual machine is used)

All threads are interrupted first. If there are still threads that are not at the safe point, restore the thread and let the thread run to the safe point.

proactive interrupt

Set an interrupt flag, and each thread actively polls this flag when it runs to the Safe Point. If the interrupt flag is true, it will interrupt itself and suspend it. (with polling mechanism)

Safe Resion

The Safepoint mechanism ensures that when the program is executed, it will encounter a Safepoint that can enter the GC within a short period of time. But what about when the program "doesn't execute"? For example, if the thread is in the Sleep state or Blocked state, the thread cannot respond to the interrupt request of the JVM at this time, and "walks" to a safe point to interrupt and suspend, and the JVM is unlikely to wait for the thread to be awakened. In this case, a safe region (Safe Region) is needed to solve it.

The safe area means that in a piece of code, the reference relationship of the object will not change, and it is safe to start Gc anywhere in this area. We can also regard Safe Region as an extended Safepoint.

When actually executed:

  1. When the thread runs to the code of the Safe Region, it is first marked that it has entered the Safe Relgion. If GC occurs during this period, the JVM will ignore the thread marked as the Safe Region state
  2. When the thread is about to leave the Safe Region, it will check whether the JVM has completed the GC, and if it is completed, it will continue to run, otherwise the thread must wait until it receives a signal that it can safely leave the Safe Region;

12.6. References Revisited: Strong References

We hope to describe such a class of objects: when the memory space is sufficient, they can be kept in memory; if the memory space is still tight after garbage collection, these objects can be discarded.

After JDK1.2, Java expanded the concept of references and divided them into four types: Strong Reference, Soft Reference, Weak Reference, and Phantom Reference. The strength of the citations gradually weakened in turn.

Except for strong references, the other three references can be found in the java.lang.ref package. The figure below shows the classes corresponding to these three reference types, and developers can use them directly in the application.

Only the finalizer reference in the Reference subclass is visible in the package, and the other three reference types are public and can be used directly in the application

  • Strong Reference (StrongReference): The most traditional definition of "reference" refers to the reference assignment that commonly exists in program code, that is, a Object obj = new Object()reference relationship like " ". In any case, as long as the strong reference relationship still exists, the garbage collector will never recycle the referenced object.
  • Soft Reference (SoftReference): Before the system is about to overflow memory, these objects will be included in the scope of recycling for the second recycling. If there is not enough memory after this recycling, a memory outflow exception will be thrown.
  • Weak Reference (WeakReference): Objects associated with weak references can only survive until the next garbage collection. When the garbage collector works, regardless of whether there is enough memory space, the objects associated with weak references will be reclaimed.
  • Phantom Reference: Whether an object has a phantom reference will not affect its lifetime at all, and it is impossible to obtain an instance of an object through a phantom reference. The only purpose of setting a phantom reference association for an object is to receive a system notification when the object is reclaimed by the collector.

Strong Reference - not recycled

In Java programs, the most common type of reference is strong reference (over 99% of ordinary systems are strong references), which is our most common common object reference, and it is also the default reference type.

When using the new operator in the Java language to create a new object and assign it to a variable, the variable becomes a strong reference to the object.

Strongly referenced objects are reachable, and the garbage collector will never reclaim the referenced object.

For an ordinary object, if there is no other reference relationship, as long as it exceeds the scope of the reference or explicitly assigns the corresponding (strong) reference to nu11, it can be collected as garbage. Of course, the specific timing of recycling depends on the garbage collection strategy.

In contrast, objects of soft references, weak references, and phantom references are soft, weak, and virtual touchable, and can be recycled under certain conditions. Therefore, strong references are one of the main causes of Java memory leaks.

strong citation example

StringBuffer str = new StringBuffer("hello mogublog");

The local variable str points to the heap space where the StringBuffer instance is located, and the instance can be manipulated through str, then str is a strong reference to the StringBuffer instance

Corresponding memory structure

At this point, if another assignment statement is run

StringBuffer str1 = str;

Corresponding memory structure

The two references in this example are both strong references, which have the following characteristics:

  • A strong reference can directly access the target object.
  • The object pointed to by the strong reference will not be reclaimed by the system at any time. The virtual machine would rather throw an OOM exception than reclaim the object pointed to by the strong reference.
  • Strong references can lead to memory leaks.

12.8. References Revisited: Soft References

Soft Reference (Soft Reference) - Recycling when memory is insufficient

Soft references are used to describe objects that are useful but not required. Objects that are only associated with soft references will be included in the recovery range for the second recovery before the system is about to experience a memory overflow exception. If there is not enough memory for this recovery, a memory overflow will be thrown abnormal.

Soft references are often used to implement memory-sensitive caches. For example: caches use soft references. If there is still free memory, you can temporarily reserve the cache, and clean it up when the memory is insufficient, so as to ensure that the memory will not be exhausted while using the cache.

When the garbage collector decides to reclaim a soft-reachable object at a certain point, it will clean up the soft reference and optionally store the reference in a reference queue (Reference Queue).

Similar to weak references, except that the Java virtual machine tries to keep the soft references alive for a longer period of time, and cleans them up as a last resort.

The java.lang.ref.SoftReference class is provided after JDK1.2 to implement soft references

Object obj = new Object(); // 声明强引用
SoftReference<Object> sf = new SoftReference<>(obj);
obj = null; //销毁强引用

12.9. References Revisited: Weak References

Weak Reference (Weak Reference) - discovery is recycling

Weak references are also used to describe non-essential objects, and objects that are only associated with weak references can only survive until the next garbage collection occurs. During the system GC, as long as weak references are found, no matter whether the system heap space is sufficient or not, objects that are only associated with weak references will be recycled.

However, since the threads of the garbage collector usually have a very low priority, objects holding weak references may not be found quickly. In this case, weakly referenced objects can exist for a longer period of time.

Weak references are the same as soft references. When constructing weak references, you can also specify a reference queue. When the weak reference object is recycled, it will be added to the specified reference queue. Through this queue, the recovery of the object can be tracked.

Soft references and weak references are very suitable for storing dispensable cache data. If you do this, when the system memory is insufficient, the cached data will be recycled without causing memory overflow. And when the memory resources are sufficient, these cached data can exist for a long time, thereby speeding up the system.

The WeakReference class is provided after JDK1.2 to implement weak references

Object obj = new Object(); // 声明强引用
WeakReference<Object> sf = new WeakReference<>(obj);
obj = null; //销毁强引用

The biggest difference between a weak reference object and a soft reference object is that when the GC is recycling, it needs to check whether the soft reference object is recycled through an algorithm, but for the weak reference object, the GC always recycles. Weakly referenced objects are easier and faster to be reclaimed by GC.

Interview question: Have you ever used WeakHashMap in your development?

WeakHashMap is used to store image information, which can be recycled in time when the memory is insufficient, avoiding OOM

12.X. References Revisited: Phantom References

Phantom Reference - Object Recycling Tracking

Also known as "ghost reference" or "phantom reference", it is the weakest of all reference types.

Whether an object has virtual references does not determine the life cycle of the object at all. If an object only holds phantom references, then it is almost the same as having no references, and may be reclaimed by the garbage collector at any time.

It cannot be used alone, nor can it obtain the referenced object through phantom reference. Always null when trying to get an object through the get() method of a phantom reference

The only purpose of setting a phantom association for an object is to track the garbage collection process. For example: you can receive a system notification when this object is recycled by the collector.

Phantom references must be used with reference queues. A phantom reference must provide a reference queue as a parameter when it is created. When the garbage collector is about to reclaim an object, if it finds that it still has a virtual reference, it will add the virtual reference to the reference queue after recycling the object to notify the application of the recycling of the object.

Since phantom references can track the recovery time of objects, some resource release operations can also be executed and recorded in phantom references.

After the JDK1.2 version, the PhantomReference class is provided to realize the virtual reference.

Object obj = new Object(); // 声明强引用
ReferenceQueue phantomQueue = new ReferenceQueue();
PhantomReference<Object> sf = new PhantomReference<>(obj, phantomQueue);
obj = null;

12.11. Finalizer references

It is used to implement the finalize() method of an object, and can also be called a finalizer reference. No manual coding is required, it works internally with reference queues.

At GC time, finalizer references are enqueued. The finalizer thread finds the referenced object through the finalizer reference and calls its finalize() method, and the referenced object is recycled only at the second GC

13. Garbage Collector

13.1. GC classification and performance indicators

13.1.1. Garbage Collector Overview

The garbage collector is not specified too much in the specification, and it can be implemented by different vendors and different versions of the JVM.

Since the JDK version is in a high-speed iteration process, Java has developed many GC versions so far.

Analyzing garbage collectors from different angles, GC can be divided into different types.

13.1.2. Garbage Collector Classification

According to the number of threads, it can be divided into serial garbage collector and parallel garbage collector.

Serial collection means that only one CPU is allowed to perform garbage collection operations at the same time period, and the worker thread is suspended until the garbage collection work is completed.

  • In situations where the hardware platform is not particularly superior, such as a single CPU processor or a small application memory, the performance of the serial collector can exceed that of the parallel collector and the concurrent collector. Therefore, serial recycling is applied by default to the JVM in the Client mode of the client
  • On CPUs with relatively strong concurrency capabilities, the pause time generated by the parallel collector is shorter than that of the serial collector.

Contrary to serial collection, parallel collection can use multiple CPUs to perform garbage collection at the same time, thus improving the throughput of the application, but parallel collection is still the same as serial collection, adopting exclusive mode and using the "Stop-the-World" mechanism .

According to the working mode, it can be divided into concurrent garbage collector and exclusive garbage collector.

  • The concurrent garbage collector works alternately with application threads to minimize application pause times.
  • Once the exclusive garbage collector (Stop the world) runs, it stops all user threads in the application until the garbage collection process is completely over.

According to the fragmentation method, it can be divided into compression garbage collector and non-compression garbage collector.

  • The compact garbage collector will compress and organize the surviving objects after the collection is completed, and eliminate the fragments after collection.
  • Non-compacting garbage collectors do not perform this step.

According to the working memory interval, it can be divided into young generation garbage collector and old generation garbage collector.

13.1.3. Evaluating GC Performance Metrics

  • Throughput: the ratio of the time spent running user code to the total running time (total running time = program running time + memory recovery time)
  • Garbage Collection Overhead: The complement of throughput, the ratio of time spent in garbage collection to total runtime.
  • Pause time: The time during which the program's worker threads are suspended while performing garbage collection.
  • Collection Frequency: How often collection operations occur relative to the execution of the application.
  • Memory occupation: the memory size occupied by the Java heap area.
  • Fast: The time it takes for an object to be reclaimed from its birth.

Throughput, pause time, and memory usage together form an "impossible triangle". The overall performance of the three will get better and better as technology advances. An excellent collector usually satisfies at most two of them at the same time.

Among these three items, the importance of pause time is becoming increasingly prominent. Because with the development of hardware, more memory usage is more and more tolerable, and the improvement of hardware performance also helps to reduce the impact of the collector on the application when it is running, that is, to increase the throughput. The expansion of the memory has a negative effect on the delay.

To put it simply, we mainly grasp two points: throughput and pause time

throughput

Throughput is the ratio of the time the CPU spends on running user code to the total time consumed by the CPU, that is, throughput = running user code time/(running user code time + garbage collection time). For example, if the virtual machine runs for 100 minutes in total, and garbage collection takes 1 minute, then the throughput is 99%.

In this case, the application can tolerate higher pause times, so high-throughput applications have a longer time base, and fast response is not a concern

Throughput priority means that the STW time is the shortest per unit time: 0.2 + 0.2 = 0.4

pause time

"Pause time" refers to the state where the application thread is suspended for a period of time to allow the GC thread to execute.

For example, a pause time of 100 milliseconds during GC means that no application threads are active during those 100 milliseconds.

Pause time is prioritized, which means that the time for a single STW is the shortest possible: 0.1 + 0.1 + 0.1 + 0.1 + 0.1 = 0.5

Throughput vs Pause Time

High throughput is better because it gives the end user of the application the impression that only the application threads are doing "productive" work. Intuitively, the higher the throughput, the faster the program will run.

Low pause times (low latency) are better because it is always bad from an end user point of view to have an application hang regardless of GC or other reasons. Depending on the type of application, sometimes even a brief 200ms pause can interrupt the end user experience. Therefore, it is very important to have a low maximum pause time, especially for an interactive application.

Unfortunately "high throughput" and "low pause time" are a pair of competing goals (contradiction).

  • Because if you choose to prioritize throughput, you must reduce the execution frequency of memory recovery, but this will cause the GC to take a longer pause time to perform memory recovery.
  • On the contrary, if you choose the principle of low latency priority, then in order to reduce the pause time of each execution of memory recovery, you can only perform memory recovery frequently, but this in turn causes the reduction of young generation memory and leads to the reduction of program throughput. decline.

When designing (or using) a GC algorithm, we must determine our goals: a GC algorithm may only aim at one of two goals (i.e. only focus on maximum throughput or minimum pause time), or try to find a combination of both compromise.

Now Standard: Reduce Pause Time Where Maximum Throughput Prioritizes

13.2. Overview of the different garbage collectors

The garbage collection mechanism is a signature capability of Java, which greatly improves development efficiency. This is of course also a hot spot for interviews.

13.2.1. History of Garbage Collector Development

With a virtual machine, a garbage collection mechanism is definitely needed. This is Garbage Collection, and the corresponding product is called Garbage Collector.

  • In 1999, JDK1.3.1 came with serialGc, which was the first GC. The ParNew garbage collector is a multi-threaded version of the Serial collector
  • On February 26, 2002, Parallel GC and Concurrent Mark Sweep GC were released together with JDK1.4.2·
  • Parallel GC becomes the default GC of HotSpot after JDK6.
  • In 2012, in the JDK1.7u4 version, G1 is available.
  • In 2017, G1 became the default garbage collector in JDK9 to replace CMS.
  • In March 2018, parallel full garbage collection by the G1 garbage collector in JDK10, enabling parallelism to improve worst-case latency.
  • In September 2018, JDK11 was released. Introduced the Epsilon garbage collector, also known as the "No-Op (no operation)" collector. At the same time, introduce ZGC: scalable low-latency garbage collector (Experimental)
  • In March 2019, JDK12 was released. Enhanced G1 to automatically return unused heap memory to the operating system. At the same time, introduce Shenandoah GC: GC with low pause time (Experimental). ·
  • In September 2019, JDK13 was released. Enhance ZGC to automatically return unused heap memory to the operating system.
  • In March 2020, JDK14 was released. Remove CMS garbage collector. Extend the application of ZGC on macos and Windows

13.2.2. Seven Classic Garbage Collectors

  • Serial collector: Serial, Serial Old
  • Parallel collectors: ParNew, Parallel Scavenge, Parallel old
  • Concurrent collectors: CMS, G1

Official manual: https://www.oracle.com/technetwork/java/javase/tech/memorymanagement-whitepaper-1-150020.pdf

13.2.3. The relationship between seven classic collectors and garbage generation

  • New generation collectors: Serial, ParNew, Parallel Scavenge;
  • Old age collectors: Serial Old, Parallel Old, CMS;
  • Whole heap collector: G1;

13.2.4. Garbage collector composition

  1. There is a connection between the two collectors, indicating that they can be used together: Serial/Serial Old, Serial/CMS, ParNew/Serial Old, ParNew/CMS, Parallel Scavenge/Serial Old, Parallel Scavenge/Parallel Old, G1;
  2. Among them, Serial Old appears as Concurrent Mode Failurea backup plan for "" failure of CMS.
  3. (Red dotted line) Due to the cost of maintenance and compatibility testing, the two combinations of Serial+CMS and ParNew+Serial Old were declared obsolete in JDK 8 (JEP173), and the support of these combinations was completely canceled in JDK9 (JEP214 ), i.e. remove.
  4. (Green dotted line) JDK14: deprecate the combination of Parallel Scavenge and Serialold GC (JEP366)
  5. (Green dotted frame) JDK14: Delete the CMS garbage collector (JEP363)

13.2.5. Overview of the different garbage collectors

Why are there many collectors, one is not enough? Because Java has many usage scenarios, such as mobile terminals, servers, etc. Therefore, it is necessary to provide different garbage collectors for different scenarios to improve the performance of garbage collection.

Although we will compare various collectors, it is not to pick a best collector. There is no one-size-fits-all perfect collector that is applicable in any scenario, let alone a universal collector. So what we choose is only the most suitable collector for the specific application.

13.2.6. How to view the default garbage collector

-XX:+PrintCommandLineFlags: View command line related parameters (including the garbage collector used)

Use command line instructions:jinfo -flag 相关垃圾回收器参数 进程ID

13.3. Serial collector: serial collection

The Serial collector is the most basic and oldest garbage collector. Before JDK1.3, the only option to recycle the new generation.

The Serial collector is used as the default new generation garbage collector in the client mode in HotSpot.

The Serial collector performs memory reclamation using copy algorithms, serial reclamation, and "stop-the-world" mechanisms.

In addition to the young generation, the Serial collector also provides the Serial Old collector for performing old generation garbage collection. The Serial Old collector also uses the serial recycling and "Stop the World" mechanism, but the memory recycling algorithm uses the mark-compression algorithm.

  • Serial old is the default old generation garbage collector running in Client mode
  • Serial 0ld has two main uses in Server mode: ① It is used in conjunction with the new generation of Parallel scavenge ② It is used as a backup garbage collection scheme for the old generation CMS collector

This collector is a single-threaded collector, but its "single-threaded" meaning does not only mean that it will only use one CPU or one collection thread to complete garbage collection work, but more importantly, when it collects garbage , must suspend all other worker threads until it collects (Stop The World)

Advantages : Simple and efficient (compared to the single-threaded performance of other collectors). For an environment limited to a single CPU, the Serial collector has no thread interaction overhead, so concentrating on garbage collection can naturally obtain the highest single-threaded collection efficiency. A virtual machine running in Client mode is a good choice.

In the user's desktop application scenario, the available memory is generally not large (tens of MB to one or two hundred MB), and garbage collection can be completed in a relatively short period of time (tens of ms to more than a hundred ms). As long as it does not happen frequently, use A serial collector is acceptable.

In the HotSpot virtual machine, -XX:+UseSerialGCparameters can be used to specify that both the young generation and the old generation use the serial collector. It is equivalent to using Serial GC in the new generation, and using Serial Old GC in the old generation

Summarize

This kind of garbage collector is known to everyone, and serial ones are no longer used. And it can only be used on a limited single-core CPU. It's not single core anymore.

For highly interactive applications, this kind of garbage collector is unacceptable. Generally, serial garbage collectors are not used in Java web applications.

13.4. ParNew Collector: Parallel Collection

If Serial GC is a single-threaded garbage collector in the young generation, then the ParNew collector is a multi-threaded version of the Serial collector. Par is the abbreviation of Parallel, New: can only deal with the new generation

There is almost no difference between the two garbage collectors except that the ParNew collector performs memory collection in parallel. The ParNew collector also uses the replication algorithm and "Stop-the-World" mechanism in the young generation.

ParNew is the default garbage collector for the new generation of many JVMs running in Server mode.

  • 对于新生代,回收次数频繁,使用并行方式高效。
  • 对于老年代,回收次数少,使用串行方式节省资源。(CPU并行需要切换线程,串行可以省去切换线程的资源)

由于ParNew收集器是基于并行回收,那么是否可以断定ParNew收集器的回收效率在任何场景下都会比serial收集器更高效?

  • ParNew 收集器运行在多CPU的环境下,由于可以充分利用多CPU、多核心等物理硬件资源优势,可以更快速地完成垃圾收集,提升程序的吞吐量。
  • 但是在单个CPU的环境下,ParNew收集器不比Serial 收集器更高效。虽然Serial收集器是基于串行回收,但是由于CPU不需要频繁地做任务切换,因此可以有效避免多线程交互过程中产生的一些额外开销。

因为除Serial外,目前只有ParNew GC能与CMS收集器配合工作

在程序中,开发人员可以通过选项"-XX:+UseParNewGC"手动指定使用ParNew收集器执行内存回收任务。它表示年轻代使用并行收集器,不影响老年代。

-XX:ParallelGCThreads限制线程数量,默认开启和CPU数据相同的线程数。

13.5. Parallel回收器:吞吐量优先

HotSpot的年轻代中除了拥有ParNew收集器是基于并行回收的以外,Parallel Scavenge收集器同样也采用了复制算法、并行回收和"Stop the World"机制。

那么Parallel 收集器的出现是否多此一举?

  • 和ParNew收集器不同,ParallelScavenge收集器的目标则是达到一个可控制的吞吐量(Throughput),它也被称为吞吐量优先的垃圾收集器。
  • 自适应调节策略也是Parallel Scavenge与ParNew一个重要区别。

高吞吐量则可以高效率地利用CPU时间,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务。因此,常见在服务器环境中使用。例如,那些执行批量处理、订单处理、工资支付、科学计算的应用程序。

The Parallel collector provides a Parallel Old collector for garbage collection in the old age in JDK1.6, which is used to replace the Serial Old collector in the old age.

The Parallel Old collector uses a mark-compression algorithm, but it is also based on parallel collection and the "Stop-the-World" mechanism.

In application scenarios where program throughput is prioritized, the combination of the Parallel collector and the Parallel Old collector has good memory recovery performance in Server mode. In Java8, this garbage collector is the default.

parameter configuration

  • -XX:+UseParallelGCManually specify the young generation to use the Parallel parallel collector to perform memory recovery tasks.
  • -XX:+UseParallelOldGCManually specifying the old generation uses the parallel collection collector.
    • Applicable to the new generation and the old generation respectively. By default jdk8 is enabled.
    • For the above two parameters, one is enabled by default, and the other will also be enabled. (mutual activation)
  • -XX:ParallelGCThreadsSets the number of threads for the young generation parallel collector. In general, it is best to be equal to the number of CPUs to avoid excessive number of threads affecting garbage collection performance.
  • -XX:MaxGCPauseMillisSet the maximum pause time of the garbage collector (that is, the time of STw). The unit is milliseconds.
    • In order to control the pause time within MaxGCPauseMills as much as possible, the collector will adjust the Java heap size or some other parameters when working.
    • For users, the shorter the pause time, the better the experience. But on the server side, we focus on high concurrency and overall throughput. So the server side is suitable for Parallel, for control.
    • Use this parameter with caution.
  • -XX:GCTimeRatioThe ratio of garbage collection time to total time (=1/(N+1)). The size used to measure throughput.
    • Value range (0, 100). The default value is 99, which means that the garbage collection time does not exceed 1%.
    • -XX:MaxGCPauseMillisThere is a certain contradiction with the previous parameter. The longer the pause time, the easier it is for the Radio parameter to exceed the set ratio.
  • -XX:+UseAdaptivesizePolicySet the Parallel Scavenge collector to have an adaptive tuning strategy
    • In this mode, parameters such as the size of the young generation, the ratio of Eden to Survivor, and the age of objects promoted to the old generation will be automatically adjusted to reach a balance between heap size, throughput, and pause time.
    • In situations where manual tuning is difficult, you can directly use this adaptive method, only specifying the maximum heap of the virtual machine, the target throughput ( ) GCTimeRatioand the pause time ( MaxGCPauseMills), and let the virtual machine complete the tuning work by itself.

13.6. CMS Collector: Low Latency

During the JDK1.5 period, Hotspot launched a garbage collector that can be considered as an epoch-making significance in strong interactive applications: CMS (Concurrent-Mark-Sweep) collector, this collector is the first in the HotSpot virtual machine A true concurrent collector, it is the first time that the garbage collection thread and the user thread work at the same time.

The focus of the CMS collector is to minimize the pause time of user threads during garbage collection. The shorter the pause time (low latency), the more suitable for programs that interact with users, and good response speed can improve user experience.

  • At present, a large part of Java applications are concentrated on the server side of Internet websites or B/S systems. This type of application pays special attention to the response speed of services, hoping that the system pause time will be the shortest, so as to bring better experience to users. The CMS collector is very suitable for the needs of this type of application.

The garbage collection algorithm of CMS uses the mark-sweep algorithm, and will also "Stop-the-World"

Unfortunately, CMS, as the collector of the old generation, cannot work with the existing new generation collector Parallel Scavenge in JDK1.4.0, so when using CMS to collect the old generation in JDK1.5, the new generation can only Choose one of the ParNew or Serial collectors.

Before the advent of G1, CMS was still widely used. To this day, there are still many systems using CMS GC.

The whole process of CMS is more complicated than the previous collector. The whole process is divided into 4 main stages, namely, the initial marking phase, the concurrent marking phase, the re-marking phase and the concurrent clearing phase

  • Initial mark (Initial-Mark) stage: In this stage, all worker threads in the program will be temporarily suspended due to the "Stop-the-World" mechanism. The main task of this stage is only to mark that GCRoots can directly the associated object. All application threads that were suspended will be resumed once the marking is complete. Since the directly associated objects are relatively small, the speed here is very fast.
  • Concurrent -Mark stage: The process of traversing the entire object graph from the directly associated objects of GC Roots. This process takes a long time but does not need to pause user threads, and can run concurrently with garbage collection threads.
  • Remark (Remark) phase: Since in the concurrent marking phase, the worker thread of the program will run concurrently or cross-running with the garbage collection thread, so in order to correct the part of the object that is marked due to the continued operation of the user program during the concurrent marking period Mark records, the pause time of this phase is usually slightly longer than the initial marking phase, but also much shorter than the concurrent marking phase.
  • Concurrent -Sweep stage: This stage cleans up and deletes the dead objects judged by the marking stage, and releases memory space. Since there is no need to move surviving objects, this stage can also be concurrent with user threads

Although the CMS collector uses concurrent recycling (non-exclusive), it still needs to implement the "Stop-the-World" mechanism to suspend the worker threads in the program during the two stages of its initialization mark and mark again, but the pause time does not It won't be too long, so it can be said that all current garbage collectors can't do "stop-the-world" at all, but just shorten the pause time as much as possible.

Since the most time-consuming concurrent marking and concurrent sweeping phases do not require pausing work, the overall collection is low-pause.

In addition, since the user thread is not interrupted during the garbage collection phase, it should also ensure that the application user thread has enough memory available during the CMS recycling process. Therefore, the CMS collector cannot wait until the old generation is almost completely filled up like other collectors, but when the heap memory usage reaches a certain threshold, it starts to recycle to ensure that the application is in the process of CMS work. There is still enough space for the application to run. If the memory reserved during the running of the CMS cannot meet the needs of the program, a " Concurrent Mode Failure" failure will occur, and the virtual machine will start a backup plan: temporarily enable the Serial Old collector to re-collect the garbage in the old age, so that the pause time is very short long.

The garbage collection algorithm of the CMS collector uses the mark-and-sweep algorithm, which means that after each memory recovery is performed, the memory space occupied by the useless objects that are performed memory recovery is very likely to be some discontinuous memory blocks, which cannot Avoid some memory fragmentation. Then when CMS allocates memory space for new objects, it will not be able to use the pointer collision (Bump the Pointer) technology, but can only select the free list (Free List) to perform memory allocation.

Some people may think that since Mark Sweep will cause memory fragmentation, why not change the algorithm to Mark Compact?

The answer is actually very simple, because when cleaning concurrently, if you use Compact to organize the memory, how can the memory used by the original user thread be used? To ensure that the user thread can continue to execute, the premise is that the resources it runs on are not affected. Mark Compact is more suitable for use in the "Stop the World" scenario

13.6.1. Advantages of CMS

  • concurrent collection
  • low latency

13.6.2. Disadvantages of CMS

  • Memory fragmentation will occur, resulting in insufficient space available to user threads after concurrent clearing. In the case where large objects cannot be allocated, FullGC has to be triggered in advance.
  • The CMS collector is very sensitive to CPU resources. In the concurrency stage, although it will not cause users to pause, it will slow down the application and reduce the total throughput because it occupies a part of the thread.
  • The CMS collector cannot handle floating garbage. There may be " Concurrent Mode Failure"failures that lead to another Full GC. In the concurrent marking phase, since the program's worker threads and garbage collection threads are running at the same time or cross-running, if new garbage objects are generated during the concurrent marking phase, CMS will not be able to mark these garbage objects, which will eventually lead to these newly generated garbage objects. Garbage objects are not recovered in time, so the memory space that has not been recovered can only be released when the next GC is executed.

13.6.3. Parameters set

  • -XX:+UseConcMarkSweepGCManually specify to use the CMS collector to perform memory recovery tasks.
    After this parameter is turned on, it will be -xx:+UseParNewGCturned on automatically. That is: the combination of ParNew (for Young area) + CMS (for Old area) + Serial Old.
  • -XX:CMSInitiatingOccupanyFractionSets the threshold of heap memory usage, once the threshold is reached, the collection will start.
    • The default value of JDK5 and previous versions is 68, that is, when the space usage rate of the old generation reaches 68%, a CMS recycling will be performed. The default value of JDK6 and above is 92%
    • If the memory growth is slow, you can set a slightly larger value. A larger threshold can effectively reduce the trigger frequency of CMS, and reduce the number of old generation recycling can significantly improve application performance. Conversely, if the application's memory usage grows rapidly, this threshold should be lowered to avoid triggering the old serial collector frequently. Therefore, this option can effectively reduce the execution times of Ful1Gc.
  • -XX:+UseCMSCompactAtFullCollectionIt is used to specify that the memory space should be compressed and sorted after the Full GC is executed, so as to avoid memory fragmentation. However, since the memory compression and sorting process cannot be executed concurrently, the problem is that the pause time becomes longer.
  • -XX:CMSFullGCsBeforeCompactionSet how many times to perform Full GC to compress and organize the memory space.
  • -XX:ParallelcMSThreadsSet the number of threads for CMS.
    • The number of threads started by CMS by default is (ParallelGCThreads+3)/4, and ParallelGCThreads is the number of threads of the young generation parallel collector. When CPU resources are tight, the performance of the application during the garbage collection phase may be very poor due to the impact of the CMS collector thread.

summary

HotSpot has so many garbage collectors, so if someone asks, what is the difference between the three Gcs, Serial GC, Parallel GC, and Concurrent Mark Sweep GC?

Remember the following passwords:

  • If you want to minimize the use of memory and parallel overhead, please choose Serial GC;
  • If you want to maximize the throughput of the application, please choose Parallel GC;
  • If you want to minimize GC interruptions or pause times, choose CMS GC.

13.6.4. Changes to the CMS in subsequent versions of the JDK

New feature of JDK9: CMS is marked as Deprecate (JEP291)

  • -XX: +UseConcMarkSweepGCIf you use parameters to enable the CMS collector for HotSpot virtual machines of JDK9 and above , the user will receive a warning message that CMS will be deprecated in the future.

New feature of JDK14: remove CMS garbage collector (JEP363)

  • The CMS garbage collector is removed. If it is used in JDK14 -XX:+UseConcMarkSweepGC, the JVM will not report an error, but will only give a warning message, but will not exit. The JVM will automatically fall back to start the JVM in the default GC mode

13.7. The G1 Collector: Regionalized Generational

Since we already have the previous powerful GC, why release Garbage First (G1)?

The reason is that the business of the application program is becoming larger and more complex, and there are more and more users. Without GC, the application program cannot be guaranteed to run normally, and the GC of STW often cannot keep up with the actual demand, so it will continue to Try to optimize the GC. The G1 (Garbage-First) garbage collector is a new garbage collector introduced after Java7 update4, and is one of the most cutting-edge achievements in the development of today's collector technology.

At the same time, in order to adapt to the ever-expanding memory and the increasing number of processors, the pause time is further reduced, while taking into account good throughput.

The official goal set for G1 is to obtain the highest possible throughput while the delay is controllable, so it takes on the heavy responsibility and expectation of a "full-featured collector".

Why is it called Garbage First (G1)?

Because G1 is a parallel collector, it divides the heap memory into many irrelevant regions (Regions) (physically discontinuous). Use different Regions to represent Eden, Survivor 0, Survivor 1, Old Age, etc.

The G1 GC programmatically avoids region-wide garbage collection on the entire Java heap. G1 tracks the value of garbage accumulation in each Region (the size of the space obtained by recycling and the experience value of the time required for recycling), maintains a priority list in the background, and gives priority to recycling the Region with the highest value each time according to the allowed collection time.

Since this approach focuses on reclaiming the region with the largest amount of garbage (Region), we give G1 a name: Garbage First.

G1 (Garbage-First) is a garbage collector for server-side applications. It is mainly aimed at machines equipped with multi-core CPUs and large-capacity memory. It meets the GC pause time with a high probability and also has high-throughput performance characteristics. .

It was officially launched in JDK1.7 version, and the Experimenta1 logo was removed. It is the default garbage collector after JDK9, replacing the CMS collector and the combination of Parallel+Parallel Old. It is officially called a "full-featured garbage collector" by Oracle.

At the same time, CMS has been marked as deprecated in JDK9. It is not the default garbage collector in jdk8 and needs to be enabled -XX:+UseG1GC.

13.7.1. Features (advantages) of the G1 collector

Compared with other GC collectors, G1 uses a brand-new partition algorithm, and its characteristics are as follows:

Parallel and concurrent

  • Parallelism: During G1 recycling, multiple GC threads can work simultaneously to effectively utilize multi-core computing power. At this point the user thread STW
  • Concurrency: G1 has the ability to execute alternately with the application, and part of the work can be executed at the same time as the application. Therefore, generally speaking, the application will not be completely blocked during the entire recycling phase.

Generational collection

  • From the perspective of generation, G1 is still a generational garbage collector, which distinguishes the young generation from the old generation. The young generation still has Eden area and Survivor area. But from the perspective of the heap structure, it does not require the entire Eden area, the young generation or the old generation to be continuous, and it no longer insists on a fixed size and a fixed number.
  • Divide the heap space into several regions (Regions), which contain the logical young generation and old generation.
  • Unlike previous types of collectors, it takes care of both the young generation and the old generation at the same time. Compared with other collectors, either work in the young generation or work in the old generation;

spatial integration

  • CMS: "mark-clear" algorithm, memory fragmentation, defragmentation after several Gc
  • G1 divides memory into regions. The recovery of memory is based on the region as the basic unit. There is a copy algorithm between Regions, but it can actually be regarded as a mark-compression (Mark-Compact) algorithm as a whole. Both algorithms can avoid memory fragmentation. This feature is conducive to the long-running of the program. When allocating large objects, the next GC will not be triggered in advance because no continuous memory space can be found. Especially when the Java heap is very large, the advantages of G1 are more obvious.

Predictable pause time model (ie: soft real-time)

This is another major advantage of G1 over CMS. In addition to pursuing low pauses, G1 can also establish a predictable pause time model, allowing users to clearly specify that within a time segment of M milliseconds, the time spent in garbage collection The time on must not exceed N milliseconds.

  • Due to partitioning, G1 can only select some areas for memory recovery, which reduces the scope of recovery, so the occurrence of global pauses can also be better controlled.
  • G1 tracks the value of garbage accumulation in each Region (the size of the space obtained by recycling and the experience value of the time required for recycling), maintains a priority list in the background, and gives priority to recycling the Region with the highest value each time according to the allowed collection time. It ensures that the G1 collector can obtain the highest possible collection efficiency within a limited time.
  • Compared with CMSGC, G1 may not be able to achieve the delay pause of CMS in the best case, but the worst case is much better.

13.7.2. Disadvantages of the G1 Garbage Collector

Compared with CMS, G1 does not have all-round and overwhelming advantages. For example, during the running of user programs, G1 is higher than CMS in terms of the memory usage (Footprint) generated by garbage collection and the additional execution load (Overload) when the program is running.

From experience, the performance of CMS in small-memory applications is likely to be better than G1, and G1 will play its advantages in large-memory applications. The balance point is between 6-8GB.

13.7.3. Parameter setting of G1 collector

  • -XX:+UseG1GC: Manually specify to use the G1 garbage collector to perform memory recovery tasks
  • -XX:G1HeapRegionSizeSet the size of each Region. The value is a power of 2, the range is between 1MB and 32MB, and the goal is to divide about 2048 regions according to the minimum Java heap size. The default is 1/2000 of the heap memory.
  • -XX:MaxGCPauseMillisSet the expected maximum GC pause time indicator (JVM will try its best to achieve it, but it is not guaranteed to be achieved). The default value is 200ms (average human reaction speed)
  • -XX:+ParallelGCThreadSets the value for the number of STW worker threads. The maximum setting is 8 (the thread calculation formula of Parallel recycler mentioned above, when CPU_Count > 8, ParallelGCThreads will also be greater than 8)
  • -XX:ConcGCThreadsSets the number of threads for concurrent marking. Set n to about 1/4 of the number of parallel garbage collection threads (ParallelGCThreads).
  • -XX:InitiatingHeapOccupancyPercentSets the Java heap occupancy threshold that triggers concurrent GC cycles. If this value is exceeded, GC is triggered. The default value is 45.

13.7.4. Common operation steps of G1 collector

The design principle of G1 is to simplify JVM performance tuning. Developers only need three simple steps to complete the tuning:

  • Step 1: Turn on the G1 garbage collector
  • Step 2: Set the maximum memory of the heap
  • Step 3: Set the maximum pause time

G1 provides three garbage collection modes: Young GC, Mixed GC and Full GC, which are triggered under different conditions.

13.7.5. Applicable scenarios of G1 collector

For server-side applications, for machines with large memory and multiple processors. (not surprising performance on normal sized heaps)

The most important application is to provide solutions for applications that require low GC latency and have a large heap; for example, when the heap size is about 6GB or larger, the predictable pause time can be less than 0.5 seconds; (G1 passes only Incremental cleaning of some rather than all Regions to ensure that each GC pause time is not too long).

Used to replace the CMS collector in JDK1.5; in the following cases, using G1 may be better than CMS:

  • More than 50% of the Java heap is occupied by active data;
  • Object allocation frequency or age promotion frequency varies greatly;
  • GC pauses are too long (longer than 0.5 to 1 second)

In the HotSpot garbage collector, except G1, other garbage collectors use the built-in JVM thread to perform GC multi-threaded operations, while G1 GC can use the application thread to undertake the GC work running in the background, that is, when the JVM GC thread processing speed is slow , the system invokes application threads to help speed up the garbage collection process.

13.7.6. Partition Region: split into zero

When using the G1 collector, it divides the entire Java heap into about 2048 independent Region blocks of the same size. The size of each Region block depends on the actual size of the heap space, and the overall control is between 1MB and 32MB, and is 2 The N power of , namely 1MB, 2MB, 4MB, 8MB, 16MB, 32MB. Can be -XX:G1HeapRegionSizeset. All Regions are the same size and will not be changed during the JVM lifetime.

Although the concepts of the new generation and the old generation are still retained, the new generation and the old generation are no longer physically isolated. They are a collection of a part of Regions (does not need to be continuous). Logical continuity is achieved through the dynamic allocation of Regions.

A region may belong to Eden, Survivor or Old/Tenured memory area. But a region can only belong to one role. E in the figure indicates that the region belongs to the Eden memory area, S indicates that it belongs to the survivor memory area, and O indicates that it belongs to the Old memory area. Blank spaces in the figure represent unused memory space.

The G1 garbage collector also adds a new memory area called the Humongous memory area, such as the H block in the figure. It is mainly used to store large objects. If there are more than 1.5 regions, it will be placed in H.

The reason for setting H: For objects in the heap, they will be directly allocated to the old generation by default, but if it is a short-lived large object, it will have a negative impact on the garbage collector. In order to solve this problem, G1 divides a Humongous area, which is used to store large objects. If one H area cannot hold a large object, then G1 will look for a continuous H area to store it. In order to find continuous H areas, sometimes Full GC has to be started. Most of the behavior of G1 treats the H area as part of the old generation.

Each Region allocates space through pointer collision

13.7.7. G1 Garbage Collector Collection Process

The garbage collection process of G1GC mainly includes the following three links:

  • Young GC (Young GC)
  • Old generation concurrent marking process (Concurrent Marking)
  • Mixed GC (Mixed GC)
    (If necessary, the single-threaded, exclusive, high-intensity Full GC still exists. It provides a failure protection mechanism for GC evaluation failures, namely strong recycling.)

Clockwise, Young gc -> Young gc + Concurrent mark->Mixed GC sequence for garbage collection.

The application allocates memory, and starts the young generation collection process when the Eden area of ​​the young generation is exhausted; the young generation collection phase of G1 is a parallel exclusive collector. During the young generation collection period, G1GC suspends all application threads and starts multi-threads to perform young generation collection. Then move the surviving objects from the young generation interval to the Survivor interval or the old interval, or both intervals may be involved.

When the heap memory usage reaches a certain value (45% by default), the concurrent marking process of the old generation starts.

Once the marking is complete, the mixed recovery process begins immediately. For a mixed collection period, the G1 GC moves surviving objects from the old generation to free regions, which then become part of the old generation. Unlike the young generation, the old generation G1 collector is different from other GCs. The G1 old generation collector does not need to recycle the entire old generation. It only needs to scan/recycle a small part of the old generation Region at a time. At the same time, the old generation Region is recycled together with the young generation.

For example: a web server, the Java process has a maximum heap memory of 4G, responds to 1500 requests per minute, and allocates about 2G of memory every 45 seconds. G1 will recycle the young generation every 45 seconds, and the usage rate of the entire heap will reach 45% every 31 hours. It will start the concurrent marking process of the old generation, and start four to five mixed collections after the marking is completed.

13.7.8. Remembered Set

  • The problem that an object is referenced by different regions
  • A Region cannot be isolated. Objects in a Region may be referenced by objects in any other Region. When judging the survival of an object, does it need to scan the entire Java heap to ensure accuracy?
  • In other generational collectors, there is also such a problem (and G1 is more prominent). Recycling the new generation also has to scan the old generation at the same time?
  • This will reduce the efficiency of MinorGC;

Solution:

Regardless of G1 or other generational collectors, JVM uses Remembered Set to avoid global scanning:

Each Region has a corresponding Remembered Set;

Every time the Reference type data is written, a Write Barrier will be generated to temporarily interrupt the operation;

Then check whether the object pointed to by the reference to be written is in a different Region from the Reference type data (other collectors: check whether the old generation object refers to the new generation object);

If they are different, record the relevant reference information into the Remembered Set corresponding to the Region where the reference points to the object through CardTable;

When performing garbage collection, add the Remembered Set to the enumeration range of the GC root node; it can ensure that no global scanning is performed and there will be no omissions.

13.7.9. G1 recycling process 1: young generation GC

When the JVM starts, G1 prepares the Eden area first, and the program continuously creates objects to the Eden area during the running process. When the Eden space is exhausted, G1 will start a young generation garbage collection process.

Young generation garbage collection will only reclaim the Eden area and the Survivor area.

First, G1 stops the execution of the application program (Stop-The-World), and G1 creates a collection set (Collection Set). The collection set refers to the collection of memory segments that need to be reclaimed. The collection set of the young generation recovery process includes the young generation Eden area and all memory segments in the Survivor area. 

Then start the recycling process as follows:

  1. In the first stage, the root is scanned. The root refers to the object pointed to by the static variable, the local variable on the method call chain being executed, and so on. The root reference, together with the external references recorded by the RSet, are used as the entry point for scanning surviving objects.
  2. In the second stage, RSet is updated. Process the cards in the dirty card queue (see remarks) and update the RSet. After this phase is completed, RSet can accurately reflect the references of the old generation to the objects in the memory segment where it is located.
  3. In the third stage, RSet is processed. Identify the objects in Eden that are pointed to by the old generation objects. These objects in Eden that are pointed to are considered to be live objects.
  4. In the fourth stage, the object is copied. At this stage, the object tree is traversed, and the surviving objects in the memory segment of the Eden area will be copied to the empty memory segment in the Survivor area. If the age of the surviving objects in the memory segment of the Survivor area does not reach the threshold, the age will be increased by 1 to reach the threshold. It will be copied to the empty memory segment in the Old area. If the Survivor space is not enough, part of the data in the Eden space will be directly promoted to the old generation space.
  5. The fifth stage is to process references. Handles Soft, Weak, Phantom, Final, JNI Weak, etc. references. In the end, the data in the Eden space is empty, the GC stops working, and the objects in the target memory are stored continuously without fragmentation, so the copy process can achieve the effect of memory organization and reduce fragmentation.

13.7.10. G1 recycling process 2: concurrent marking process

  1. Initial marking phase: mark objects that are directly reachable from the root node. This phase is STW and triggers a young generation GC.
  2. Root Region Scanning (Root Region Scanning): G1 GC scans the objects in the old age region that are directly reachable in the Survivor region, and marks the referenced objects. This process must be completed before YoungGC.
  3. Concurrent Marking: Concurrent marking is performed on the entire heap (concurrent execution with the application), this process may be interrupted by YoungGC. During the concurrent marking phase, if all objects in the area object are found to be garbage, the area will be recycled immediately. At the same time, during the concurrent marking process, the object liveness (proportion of surviving objects in the region) is calculated for each region.
  4. Remark (Remark): As the application continues, it is necessary to revise the last mark result. It's from STW. G1 uses an initial snapshot algorithm faster than CMS: snapshot-at-the-beginning (SATB).
  5. Exclusive cleanup (cleanup, STW): Calculate the proportion of surviving objects and GC recovery in each area, and sort them to identify areas that can be mixed and recovered. Pave the way for the next stage. It's from STW. This phase does not actually do garbage collection
  6. Concurrent cleanup phase: completely free regions are identified and cleaned up.

13.7.11. G1 Recycling Process 3: Mixed Recycling

When more and more objects are promoted to the old generation o1d region, in order to avoid the exhaustion of heap memory, the virtual machine triggers a mixed garbage collector, that is, Mixed GC. This algorithm is not an Old GC, except for reclaiming the entire Young Region , and part of the Old Region will be recycled. It should be noted here: it is part of the old age, not all the old age. Which Old Regions can be selected for collection, so that the time-consuming time of garbage collection can be controlled. Also note that Mixed GC is not Full GC.

After the concurrent marking ends, the memory segments that are 100% garbage in the old generation are reclaimed, and the memory segments that are partially garbage are calculated. By default, these memory segments in the old age will be recycled 8 times (can be -XX:G1MixedGCCountTargetset)

The collection set of mixed recovery includes one-eighth of the memory segments of the old age, the memory segments of the Eden area, and the memory segments of the Survivor area. The mixed recycling algorithm is exactly the same as the young generation recycling algorithm, except that the old generation memory segments are added to the recycling collection. For details, please refer to the Young Generation Recycling Process above.

Since the memory segments in the old generation are recycled 8 times by default, G1 will give priority to recycling the memory segments with more garbage. The higher the ratio of garbage to the memory segment, the more it will be recycled first. And there is a threshold that will determine whether the memory segment is reclaimed, -XX:G1MixedGCLiveThresholdPercent, the default is 65%, which means that the garbage will be reclaimed only when the proportion of garbage in the memory segment reaches 65%. If the proportion of garbage is too low, it means that the proportion of surviving objects is high, and it will take more time to copy.

Mixed recovery does not have to be performed 8 times. There is a threshold -XX:G1HeapWastePercent, the default value is 10%, which means that 10% of the entire heap memory is allowed to be wasted, which means that if it is found that the proportion of garbage that can be recycled accounts for less than 10% of the heap memory, no mixed collection will be performed. Because GC takes a lot of time but reclaims very little memory.

 

13.7.12. G1 recovery optional process four: Full GC

The original intention of G1 is to avoid the occurrence of Full GC. However, if the above method does not work properly, G1 will stop the execution of the application (Stop-The-World), and use a single-threaded memory recovery algorithm for garbage collection. The performance will be very poor, and the application pause time will be very long.

To avoid the occurrence of Full GC, adjustments need to be made once it occurs. When will Full GC happen? For example, if the heap memory is too small, when G1 has no empty memory segments available when copying surviving objects, it will fall back to Full GC. This situation can be solved by increasing the memory.

There may be two reasons for G1 Full GC:

  • During Evacuation, there is not enough to-space to store promoted objects;
  • Space was exhausted before concurrent processing was complete.

13.7.13. Supplements

From the official information disclosed by Oracle, we can know that the recovery stage (Evacuation) was actually designed to be executed concurrently with the user program, but this is more complicated to do. Considering that G1 only returns a part of the Region, the pause time is the user It is controllable, so it is not urgent to implement it, but chooses to put this feature in the low-latency garbage collector (ie ZGC) that appears after G1. In addition, considering that G1 is not only for low latency, pausing user threads can greatly improve the efficiency of garbage collection. In order to ensure throughput, the implementation scheme of completely pausing user threads was chosen.

13.7.14. G1 Collector Optimization Suggestions

young generation size

  • Avoid explicitly setting the young generation size with related options such as -Xmnor-XX:NewRatio
  • Fixed young generation size overrides pause time goal

Don’t be too strict about your pause time goals

  • The throughput target for G1 GC is 90% application time and 10% garbage collection time
  • When evaluating the throughput of G1 GC, the pause time goal should not be too strict. Goals that are too strict mean that you are willing to incur more garbage collection overhead, which directly impacts throughput.

13.8. Garbage Collector Summary

13.8.1. Summary of 7 Classic Garbage Collectors

As of JDK1.8, there are 7 different garbage collectors. Each type of garbage collector has different characteristics. When using it, you need to choose a different garbage collector according to the specific situation.

garbage collector

Classification

Action position

use algorithm

features

Applicable scene

Serial

serial run

Act on the new generation

copy algorithm

Response speed priority

Applicable to the client mode in a single CPU environment

ParNew

run in parallel

Act on the new generation

copy algorithm

Response speed priority

Use with CMS in multi-CPU environment Server mode

Parallel

run in parallel

Act on the new generation

copy algorithm

Throughput priority

Suitable for background operations without much interaction

Serial Old

serial run

acting on the old generation

mark-compression algorithm

Response speed priority

Client mode suitable for single CPU environment

Parallel Old

run in parallel

acting on the old generation

mark-compression algorithm

Throughput priority

Suitable for background operations without much interaction

CMS

run concurrently

acting on the old generation

mark-sweep algorithm

Response speed priority

Applicable to Internet or B/S business

G1

run concurrently

Acting on the new generation and the old generation

mark-compression algorithm, replication algorithm

Response speed priority

For server-side applications

GC development stage: Serial => Parallel (parallel) => CMS (concurrent) => G1 => ZGC

13.8.2. Garbage collector pooling

Different manufacturers and different versions of virtual machines have a relatively large gap in implementation. All collectors and combinations of HotSpot virtual machine after JDK7/8 are shown in the figure below

  1. There is a connection between the two collectors, indicating that they can be used together: Serial/Serial Old, Serial/CMS, ParNew/Serial Old, ParNew/CMS, Parallel Scavenge/Serial Old, Parallel Scavenge/Parallel Old, G1;
  2. Among them, Serial Old is Concurrent Mode Failurethe backup plan for ""failure" in CMS.
  3. (Red dotted line) Due to the cost of maintenance and compatibility testing, the two combinations of Serial+CMS and ParNew+Serial old were declared as Deprecated (JEP 173) in JDK 8, and in JDK 9

Support for these combinations was completely removed (JEP214), ie: removed.

  1. (Green dotted line) JDK 14: Deprecate the combination of ParallelScavenge and SeriaOold GC (JEP 366)
  2. (Green dashed box) JDK 14: Remove the CMS garbage collector (JEP 363)

13.8.3. How to choose a garbage collector

The configuration of the Java garbage collector is a very important choice for JVM optimization. Choosing an appropriate garbage collector can greatly improve the performance of the JVM.

How to choose a garbage collector?

  1. Prioritize adjusting the size of the heap to allow the JVM to complete it adaptively.
  2. If the memory is less than 100M, use the serial collector
  3. If it is a single-core, stand-alone program, and there is no pause time requirement, the serial collector
  4. If it is multi-CPU, high throughput is required, and the pause time is allowed to exceed 1 second, choose parallel or JVM to choose
  5. If it is multi-CPU, pursues low pause time, and needs fast response (for example, the delay cannot exceed 1 second, such as Internet applications), use a concurrent collector
  6. The official recommendation is G1, which has high performance. Now Internet projects basically use G1.

Finally, one point needs to be clarified:

  1. There is no best collector, let alone a universal collector
  2. Tuning is always for specific scenarios and specific needs, there is no one-and-done collector

interview

For garbage collection, the interviewer can step by step from various angles of theory and practice, and it does not necessarily require the interviewer to understand everything. But if you understand the principle, it will definitely become a bonus item in the interview. The more general and basic parts here are as follows:

  • What are the algorithms for garbage collection? How to judge whether an object can be recycled?
  • The basic flow of garbage collector work.

In addition, you need to pay more attention to the various commonly used parameters in this chapter of the garbage collector

13.9. GC log analysis

By reading the Gc log, we can understand the memory allocation and recycling strategy of the Java virtual machine. List of parameters for memory allocation and garbage collection

  • -XX:+PrintGCOutput GC log. similar:-verbose:gc
  • -XX:+PrintGCDetailsOutput detailed GC logs
  • -XX:+PrintGCTimestampsOutput the GC timestamp (in the form of base time)
  • -XX:+PrintGCDatestampsOutput the timestamp of GcC (in the form of date, such as 2013-05-04T21:53:59.234+0800)
  • -XX:+PrintHeapAtGCPrint out heap information before and after GC
  • -Xloggc:../logs/gc.logThe output path of the log file

Open GC log

-verbose:gc

This will only show the total GC heap changes, as follows:

[GC (Allocation Failure) 80832K->19298K(227840K),0.0084018 secs]
[GC (Metadata GC Threshold) 109499K->21465K(228352K),0.0184066 secs]
[Full GC (Metadata GC Threshold) 21465K->16716K(201728K),0.0619261 secs]

Parameter analysis

GC、Full GC:GC的类型,GC只在新生代上进行,Full GC包括永生代,新生代,老年代。
Allocation Failure:GC发生的原因。
80832K->19298K:堆在GC前的大小和GC后的大小。
228840k:现在的堆大小。
0.0084018 secs:GC持续的时间。

Open GC log

-verbose:gc -XX:+PrintGCDetails

Enter the information as follows

[GC (Allocation Failure) [PSYoungGen:70640K->10116K(141312K)] 80541K->20017K(227328K),0.0172573 secs] [Times:user=0.03 sys=0.00,real=0.02 secs]
[GC (Metadata GC Threshold) [PSYoungGen:98859K->8154K(142336K)] 108760K->21261K(228352K),0.0151573 secs] [Times:user=0.00 sys=0.01,real=0.02 secs]
[Full GC (Metadata GC Threshold)[PSYoungGen:8154K->0K(142336K)]
[ParOldGen:13107K->16809K(62464K)] 21261K->16809K(204800K),[Metaspace:20599K->20599K(1067008K)],0.0639732 secs]
[Times:user=0.14 sys=0.00,real=0.06 secs]

Parameter analysis

GC,Full FC:同样是GC的类型
Allocation Failure:GC原因
PSYoungGen:使用了Parallel Scavenge并行垃圾收集器的新生代GC前后大小的变化
ParOldGen:使用了Parallel Old并行垃圾收集器的老年代GC前后大小的变化
Metaspace: 元数据区GC前后大小的变化,JDK1.8中引入了元数据区以替代永久代
xxx secs:指GC花费的时间
Times:user:指的是垃圾收集器花费的所有CPU时间,sys:花费在等待系统调用或系统事件的时间,real:GC从开始到结束的时间,包括其他进程占用时间片的实际时间。

Open GC log

-verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimestamps -XX:+PrintGCDatestamps

Enter the information as follows

2019-09-24T22:15:24.518+0800: 3.287: [GC (Allocation Failure) [PSYoungGen:136162K->5113K(136192K)] 141425K->17632K(222208K),0.0248249 secs] [Times:user=0.05 sys=0.00,real=0.03 secs]

2019-09-24T22:15:25.559+0800: 4.329: [GC (Metadata GC Threshold) [PSYoungGen:97578K->10068K(274944K)] 110096K->22658K(360960K),0.0094071 secs] [Times: user=0.00 sys=0.00,real=0.01 secs]

2019-09-24T22:15:25.569+0800: 4.338: [Full GC (Metadata GC Threshold) [PSYoungGen:10068K->0K(274944K)][ParoldGen:12590K->13564K(56320K)] 22658K->13564K(331264K),[Metaspace:20590K->20590K(1067008K)],0.0494875 secs] [Times: user=0.17 sys=0.02,real=0.05 secs]

Instructions: Bring Dates and Practice

If you want to save the GC log to a file, the following parameters are used:

-Xloggc:/path/to/gc.log

Supplementary notes to the log

  • " [GC" and " [Full GC"denote the pause type of this garbage collection. If there is "Full", it means that the GC has "Stop The World"
  • The name of the new generation using the Serial collector is Default New Generation, so the display is " [DefNew"
  • Using the ParNew collector, the name of the new generation will become " [ParNew", meaning "Parallel New Generation"
  • The name of the new generation using the Parallel scavenge collector is " [PSYoungGen"
  • The collection of the old generation is the same as that of the new generation, and the name is also determined by the collector
  • If the G1 collector is used, it will be displayed as "garbage-first heap"
  • Allocation Failure
    indicates that the reason for the GC this time is that there is not enough space in the young generation to store new data.
  • [PSYoungGen:5986K->696K(8704K) ]  5986K->704K(9216K)
  • In square brackets: the size of the young generation before GC recovery, the size after recovery, (total size of the young generation)
  • Outside the brackets: the size of the young generation and the old generation before GC recovery, the size after recovery, (the total size of the young generation and the old generation)
  • user represents the time-consuming recovery in user mode, the time-consuming recovery in sys kernel mode, and the actual time-consuming in rea. Due to multi-core, the total time may exceed the real time
Heap(堆)
PSYoungGen(Parallel Scavenge收集器新生代)total 9216K,used 6234K [0x00000000ff600000,0x0000000100000000,0x0000000100000000)
eden space(堆中的Eden区默认占比是8)8192K,768 used [0x00000000ff600000,0x00000000ffc16b08,0x00000000ffe00000)
from space(堆中的Survivor,这里是From Survivor区默认占比是1)1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
to space(堆中的Survivor,这里是to Survivor区默认占比是1,需要先了解一下堆的分配策略)1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
                                                                         
ParOldGen(老年代总大小和使用大小)total 10240K, used 7001K [0x00000000fec00000,0x00000000ff600000,0x00000000ff600000)
object space(显示个使用百分比)10240K,688 used [0x00000000fec00000,0x00000000ff2d6630,0x00000000ff600000)

PSPermGen(永久代总大小和使用大小)total 21504K, used 4949K [0x00000000f9a00000,0x00000000faf00000,0x00000000fec00000)
object space(显示个使用百分比,自己能算出来)21504K, 238 used [0x00000000f9a00000,0x00000000f9ed55e0,0x00000000faf00000)

Full GC log

example

private static final int _1MB = 1024 * 1024;

public static void testAllocation() {
    byte [] allocation1, allocation2, allocation3, allocation4;
    allocation1 = new byte[2 *_1MB];
    allocation2 = new byte[2 *_1MB];
    allocation3 = new byte[2 *_1MB];
    allocation4 = new byte[4 *_1MB];
}

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

Set JVM parameters

-Xms10m -Xmx10m -XX:+PrintGCDetails

You can use some tools to analyze these GC logs

Commonly used log analysis tools include: GCViewer, GCEasy, GCHisto, GCLogViewer, Hpjmeter, garbagecat, etc.

13.X. New Developments in the Garbage Collector

GC is still in rapid development. The current default option, G1 GC, is constantly being improved. Many shortcomings that we originally thought, such as serial Fu11GC and low efficiency of Card Table scanning, have been greatly improved. For example, JDK10 In the future, Fu11GC is already running in parallel, and in many scenarios, its performance is slightly better than ParallelGC's parallel Ful1GC implementation.

Even though Serial GC is relatively old, its simple design and implementation are not necessarily obsolete. Its own overhead, whether it is the overhead of GC-related data structures or the overhead of threads, is very small, so as cloud computing The rise of Serial GC has found a new stage in new application scenarios such as Serverless.

Unfortunately, CMSGC has been marked as obsolete in JDK9 and removed in JDK14 because of the theoretical defects of its algorithm and other reasons. Although there is still a very large user group

13.X.1. JDK11 new features

Epsilon: A No-Op GarbageCollector (Epsilon garbage collector, "No-Op (no operation)" collector) iava.net

ZGC: A Scalable Low-Latency Garbage Collector (Experimental) (ZGC: Scalable Low-Latency Garbage Collector, in the experimental stage) http://openidk.iava.net/jeps/333

13.X.2. Shenandoash GC of Open JDK12

Shenandoash GC for Open JDK12: GC with low pause times (experimental)

Shenandoah is undoubtedly the loneliest of many GCs. It is the first Hotspot garbage collector not led by the Oracle team. Inevitably subject to official exclusion. For example, Oracle, which claims that there is no difference between OpenJDK and OracleJDK, still refuses to support Shenandoah in OracleJDK12.

The Shenandoah garbage collector was originally implemented by Pauseless GC, a garbage collector research project conducted by RedHat, aiming at low pause requirements for memory recovery on the JVM. Contributed to OpenJDK in 2014.

The Red Hat R&D Shenandoah team claims that the pause time of the Shenandoah garbage collector has nothing to do with the heap size, which means that no matter whether the heap is set to 200MB or 200GB, 99.9% of the goals can limit the pause time of garbage collection to within ten milliseconds. However, actual usage performance will depend on the actual working heap size and workload

13.X.3. The Shocking, Revolutionary ZGC

Official address: Java Platform, Standard Edition HotSpot Virtual Machine Garbage Collection Tuning Guide, Release 12

13.X.4. Other Garbage Collectors: AliGC

AliGC is based on the G1 algorithm of the Alibaba JVM team and is oriented to large heap (Large Heap) application scenarios. Contrast in the specified scene.

Of course, other manufacturers also provide various unique GC implementations, such as the famous low-latency GC: Zing. If you are interested, you can refer to the provided link The Azul Garbage Collector

Guess you like

Origin blog.csdn.net/guaituo0129/article/details/121025617
Recommended