JVM-02 内存模型及内存分配机制

1、JVM运行时数据区定义

初学JAVA时,对JVM中的堆、栈、方法区等甚是模糊,甚至很多老师也讲不清楚,一遍遍从概念学起太浪费功夫,今天就从官网上把JVM内存中的数据区挨个拎出来看。

1.1 运行时数据区概念

2.5. Run-Time Data Areas
The Java Virtual Machine defines various run-time data areas that are used during execution of a program. Some of these data areas are created on Java Virtual Machine start-up and are destroyed only when the Java Virtual Machine exits. Other data areas are per thread. Per-thread data areas are created when a thread is created and destroyed when the thread exits.
(Java 虚拟机定义了在程序执行期间使用的各种运行时数据区。 其中一些数据区是在 Java 虚拟机启动时创建的,只有在 Java 虚拟机退出时才会销毁。 其他数据区是每个线程。 每线程数据区在创建线程时创建,并在线程退出时销毁。)

1.2 PC寄存器

2.5.1. The pc Register
The Java Virtual Machine can support many threads of execution at once (JLS §17). Each Java Virtual Machine thread has its own pc (program counter) register. At any point, each Java Virtual Machine thread is executing the code of a single method, namely the current method (§2.6) for that thread. If that method is not native, the pc register contains the address of the Java Virtual Machine instruction currently being executed. If the method currently being executed by the thread is native, the value of the Java Virtual Machine’s pc register is undefined. The Java Virtual Machine’s pc register is wide enough to hold a returnAddress or a native pointer on the specific platform.
(Java 虚拟机可以同时支持多个执行线程(JLS §17)。 每个 Java 虚拟机线程都有自己的 pc(程序计数器)寄存器。 在任何时候,每个 Java 虚拟机线程都在执行单个方法的代码,即该线程的当前方法(第 2.6 节)。 如果该方法不是本机方法,则 pc 寄存器包含当前正在执行的 Java 虚拟机指令的地址。 如果线程当前正在执行的方法是本地方法,则 Java 虚拟机的 pc 寄存器的值是未定义的。 Java 虚拟机的 pc 寄存器足够宽,可以保存特定平台上的 returnAddress 或本机指针。)

信息解读:

  • Pc寄存器是每个线程私有的。
  • PC寄存器没有内存大小限制,不会进行垃圾清理。
  • PC寄存器的生命周期同线程的生命周期一致。
  • PC寄存器含有当前线程正在执行的方法的指令地址,遇到native方法,Pc寄存器存储的值为undefined
  • 当执行引擎从PC寄存器读取完下个指令地址后,PC寄存器的地址就会指向下一指令地址。(当执行引擎从PC寄存区读取到5后,PC寄存器的值就会指向6)。
    在这里插入图片描述

1.3 虚拟机栈

2.5.2. Java Virtual Machine Stacks
Each Java Virtual Machine thread has a private Java Virtual Machine stack, created at the same time as the thread. A Java Virtual Machine stack stores frames (§2.6). A Java Virtual Machine stack is analogous to the stack of a conventional language such as C: it holds local variables and partial results, and plays a part in method invocation and return. Because the Java Virtual Machine stack is never manipulated directly except to push and pop frames, frames may be heap allocated. The memory for a Java Virtual Machine stack does not need to be contiguous.
In the First Edition of The Java® Virtual Machine Specification, the Java Virtual Machine stack was known as the Java stack.
This specification permits Java Virtual Machine stacks either to be of a fixed size or to dynamically expand and contract as required by the computation. If the Java Virtual Machine stacks are of a fixed size, the size of each Java Virtual Machine stack may be chosen independently when that stack is created.
A Java Virtual Machine implementation may provide the programmer or the user control over the initial size of Java Virtual Machine stacks, as well as, in the case of dynamically expanding or contracting Java Virtual Machine stacks, control over the maximum and minimum sizes.
The following exceptional conditions are associated with Java Virtual Machine stacks:
If the computation in a thread requires a larger Java Virtual Machine stack than is permitted, the Java Virtual Machine throws a StackOverflowError.
If Java Virtual Machine stacks can be dynamically expanded, and expansion is attempted but insufficient memory can be made available to effect the expansion, or if insufficient memory can be made available to create the initial Java Virtual Machine stack for a new thread, the Java Virtual Machine throws an OutOfMemoryError.
(每个 Java 虚拟机线程都有一个私有的 Java 虚拟机堆栈,与线程同时创建。 Java 虚拟机堆栈存储帧(第 2.6 节)。 Java 虚拟机堆栈类似于 C 等传统语言的堆栈:它保存局部变量和部分结果,并在方法调用和返回中发挥作用。因为除了推送和弹出帧之外,Java 虚拟机堆栈从不直接操作,所以帧可能被分配到堆上。 Java 虚拟机堆栈的内存不需要是连续的。
在 Java® 虚拟机规范的第一版中,Java 虚拟机堆栈被称为 Java 堆栈。
该规范允许 Java 虚拟机堆栈具有固定大小或根据计算需要动态扩展和收缩。如果 Java 虚拟机堆栈的大小是固定的,那么在创建该堆栈时可以独立选择每个 Java 虚拟机堆栈的大小。
Java 虚拟机实现可以为程序员或用户提供对 Java 虚拟机堆栈初始大小的控制,以及在动态扩展或收缩 Java 虚拟机堆栈的情况下,控制最大和最小大小。
以下异常情况与 Java 虚拟机堆栈相关联:
如果线程中的计算需要比允许的更大的 Java 虚拟机堆栈,则 Java 虚拟机将抛出 StackOverflowError。
如果 Java 虚拟机堆栈可以动态扩展,并且尝试扩展但没有足够的内存来实现扩展,或者如果没有足够的内存可以为新线程创建初始 Java 虚拟机堆栈,则 Java 虚拟机机器抛出 OutOfMemoryError。)

信息解读:

  • 线程栈是每个线程私有的。
  • 栈的结构类似C语言的堆栈,每有方法调用后,会在当前栈中压入一个栈帧,用于存放局部变量和部分结果(操作数栈)。
  • 栈结构都是FILO,先进后出。
  • 每个线程栈有默认的大小限制,在jvm中可以使用-Xss(StackSize)配置,默认值1M(见下图)。当把Xss的值调小时,那么一个线程栈能分配的栈帧数就越少,但JVM整体能开启的线程数就更多。
  • 线程栈的大小可以设置为固定值,也可以根据需要动态扩展和收缩。
  • 当线程栈的大小固定,且线程出现超长递归或无限循环时,会出现因为超出栈内存大小的StackOverflowError报错。
  • 如果线程栈内存可以动态扩展,当遇到线程栈没有内存可用于扩展时,或新线程没有内存开辟栈空间时,就会出现OutOfMemoryError
    在这里插入图片描述

1.4 堆

2.5.3. Heap
The Java Virtual Machine has a heap that is shared among all Java Virtual Machine threads. The heap is the run-time data area from which memory for all class instances and arrays is allocated.
The heap is created on virtual machine start-up. Heap storage for objects is reclaimed by an automatic storage management system (known as a garbage collector); objects are never explicitly deallocated. The Java Virtual Machine assumes no particular type of automatic storage management system, and the storage management technique may be chosen according to the implementor’s system requirements. The heap may be of a fixed size or may be expanded as required by the computation and may be contracted if a larger heap becomes unnecessary. The memory for the heap does not need to be contiguous.
A Java Virtual Machine implementation may provide the programmer or the user control over the initial size of the heap, as well as, if the heap can be dynamically expanded or contracted, control over the maximum and minimum heap size.
The following exceptional condition is associated with the heap:
If a computation requires more heap than can be made available by the automatic storage management system, the Java Virtual Machine throws an OutOfMemoryError.
(Java 虚拟机有一个在所有 Java 虚拟机线程之间共享的堆。堆是运行时数据区,从中分配所有类实例和数组的内存。
堆是在虚拟机启动时创建的。对象的堆存储由自动存储管理系统(称为垃圾收集器)回收;对象永远不会被显式释放。 Java 虚拟机不假设任何特定类型的自动存储管理系统,存储管理技术可以根据实现者的系统需求来选择。堆可以是固定大小的,也可以根据计算的需要进行扩展,如果不需要更大的堆,则可以收缩。堆的内存不需要是连续的。
Java 虚拟机实现可以为程序员或用户提供对堆初始大小的控制,以及如果堆可以动态扩展或收缩,则可以控制最大和最小堆大小。
以下异常情况与堆相关联:
如果计算需要的堆多于自动存储管理系统所能提供的堆,Java 虚拟机将抛出 OutOfMemoryError。)

信息解读:

  • 堆内存是所有线程共享的。
  • 堆中是存放的是类实例和数组。
  • 堆内存的生命周期:在JVM启动时创建,在运行时由GC进行回收管理,在退出虚拟机时销毁。
  • 堆内存空间可以使固定的,也可以动态扩展和收缩。
  • 当内存空间不足时,OutOfMemoryError。

1.5 方法区(元空间)

2.5.4. Method Area
The Java Virtual Machine has a method area that is shared among all Java Virtual Machine threads. The method area is analogous to the storage area for compiled code of a conventional language or analogous to the “text” segment in an operating system process. It stores per-class structures such as the run-time constant pool, field and method data, and the code for methods and constructors, including the special methods (§2.9) used in class and instance initialization and interface initialization.
The method area is created on virtual machine start-up. Although the method area is logically part of the heap, simple implementations may choose not to either garbage collect or compact it. This specification does not mandate the location of the method area or the policies used to manage compiled code. The method area may be of a fixed size or may be expanded as required by the computation and may be contracted if a larger method area becomes unnecessary. The memory for the method area does not need to be contiguous.
A Java Virtual Machine implementation may provide the programmer or the user control over the initial size of the method area, as well as, in the case of a varying-size method area, control over the maximum and minimum method area size.
The following exceptional condition is associated with the method area:
If memory in the method area cannot be made available to satisfy an allocation request, the Java Virtual Machine throws an OutOfMemoryError.
(Java 虚拟机有一个在所有 Java 虚拟机线程之间共享的方法区。方法区类似于传统语言的编译代码存储区,或类似于操作系统进程中的“文本”段。它存储每个类的结构,例如运行时常量池、字段和方法数据,以及方法和构造函数的代码,包括在类和实例初始化和接口初始化中使用的特殊方法(第 2.9 节)。
方法区是在虚拟机启动时创建的。尽管方法区在逻辑上是堆的一部分,但简单的实现可能会选择不进行垃圾收集或压缩它。本规范不强制要求方法区的位置或用于管理编译代码的策略。方法区可以是固定大小,也可以根据计算的需要进行扩展,如果不需要较大的方法区,可以缩小。方法区的内存不需要是连续的。
Java 虚拟机实现可以为程序员或用户提供对方法区初始大小的控制,以及在可变大小方法区的情况下,对最大和最小方法区大小的控制。
以下异常条件与方法区相关联:
如果方法区中的内存不能用于满足分配请求,Java 虚拟机将抛出 OutOfMemoryError。)

  • 方法区,又称元空间,在JDK7之前又称永久代,是线程间共享的区域。
  • 方法区类似编译代码存储区,由字节码执行引擎完整加载后存入。
  • 方法区存放运行时常量池、字段、方法、构造函数的代码等。
  • 方法区可以固定大小和动态扩展、压缩。默认无初始值。
  • 当方法区的内存大小超过MetaspaceSize阈值时,会触发Full GC,此时如收集器释放大量空间则会缩小方法区内存,如果仅释放少量空间,则会适当提高方法区内存空间。为了减少Full GC回收,一般会将MetaspaceSize和MaxMetaspaceSize都设置为256m(针对8G内存机器)

1.6 运行时常量池

2.5.5. Run-Time Constant Pool
A run-time constant pool is a per-class or per-interface run-time representation of the constant_pool table in a class file (§4.4). It contains several kinds of constants, ranging from numeric literals known at compile-time to method and field references that must be resolved at run-time. The run-time constant pool serves a function similar to that of a symbol table for a conventional programming language, although it contains a wider range of data than a typical symbol table.
Each run-time constant pool is allocated from the Java Virtual Machine’s method area (§2.5.4). The run-time constant pool for a class or interface is constructed when the class or interface is created (§5.3) by the Java Virtual Machine.
The following exceptional condition is associated with the construction of the run-time constant pool for a class or interface:
When creating a class or interface, if the construction of the run-time constant pool requires more memory than can be made available in the method area of the Java Virtual Machine, the Java Virtual Machine throws an OutOfMemoryError.
See §5 (Loading, Linking, and Initializing) for information about the construction of the run-time constant pool.
(运行时常量池是类文件(第 4.4 节)中 constant_pool 表的每个类或每个接口的运行时表示。它包含多种常量,从编译时已知的数字文字到必须在运行时解析的方法和字段引用。运行时常量池的功能类似于传统编程语言的符号表,尽管它包含比典型符号表更广泛的数据。
每个运行时常量池都是从 Java 虚拟机的方法区(第 2.5.4 节)分配的。类或接口的运行时常量池是在 Java 虚拟机创建类或接口(第 5.3 节)时构造的。
以下异常条件与类或接口的运行时常量池的构造相关:
在创建类或接口时,如果构建运行时常量池需要的内存多于 Java 虚拟机的方法区中可用的内存,则 Java 虚拟机将抛出 OutOfMemoryError。
有关构建运行时常量池的信息,请参阅第 5 节(加载、链接和初始化)。)

1.7 本地方发栈

2.5.6. Native Method Stacks
An implementation of the Java Virtual Machine may use conventional stacks, colloquially called “C stacks,” to support native methods (methods written in a language other than the Java programming language). Native method stacks may also be used by the implementation of an interpreter for the Java Virtual Machine’s instruction set in a language such as C. Java Virtual Machine implementations that cannot load native methods and that do not themselves rely on conventional stacks need not supply native method stacks. If supplied, native method stacks are typically allocated per thread when each thread is created.
This specification permits native method stacks either to be of a fixed size or to dynamically expand and contract as required by the computation. If the native method stacks are of a fixed size, the size of each native method stack may be chosen independently when that stack is created.
A Java Virtual Machine implementation may provide the programmer or the user control over the initial size of the native method stacks, as well as, in the case of varying-size native method stacks, control over the maximum and minimum method stack sizes.
The following exceptional conditions are associated with native method stacks:
If the computation in a thread requires a larger native method stack than is permitted, the Java Virtual Machine throws a StackOverflowError.
If native method stacks can be dynamically expanded and native method stack expansion is attempted but insufficient memory can be made available, or if insufficient memory can be made available to create the initial native method stack for a new thread, the Java Virtual Machine throws an OutOfMemoryError.
(Java 虚拟机的实现可以使用传统的堆栈,通俗地称为“C 堆栈”,以支持本机方法(用 Java 编程语言以外的语言编写的方法)。本机方法堆栈也可以由 Java 虚拟机指令集的解释器实现使用,例如 C 语言。无法加载本机方法且本身不依赖常规堆栈的 Java 虚拟机实现不需要提供本机方法堆栈。如果提供,则通常在创建每个线程时为每个线程分配本机方法堆栈。
该规范允许本地方法堆栈具有固定大小或根据计算需要动态扩展和收缩。如果本机方法堆栈的大小是固定的,则在创建该堆栈时可以独立选择每个本机方法堆栈的大小。
Java 虚拟机实现可以让程序员或用户控制本地方法堆栈的初始大小,以及在可变大小的本地方法堆栈的情况下,控制最大和最小方法堆栈大小。
以下异常情况与本机方法堆栈相关联:
如果线程中的计算需要比允许的更大的本机方法堆栈,则 Java 虚拟机将抛出 StackOverflowError。
如果可以动态扩展本机方法堆栈并尝试扩展本机方法堆栈但可以提供足够的内存,或者如果可以提供足够的内存来为新线程创建初始本机方法堆栈,则 Java 虚拟机将抛出 OutOfMemoryError .)

信息解读:

  • java调用C语言实现的如native方法等,所开辟的空间。
  • 本地方发栈是线程私有的。

oracle官网的doc地址:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.5

2 内存模型及分配机制

在这里插入图片描述

2.1 内存回收实例动态分析:

public class HeapTest {
 	//设置100KB成员数组变量,扩大类对象在堆中的内存占用,让JVM的垃圾回收更明显
    byte[] a = new byte[1024 *100];

    public static void main(String[] args) throws InterruptedException {
    	//让main方法的成员变量成为线程栈的局部变量满足gc root要求,不能回收
        ArrayList<HeapTest> heapTests = new ArrayList<>();
        while (true){
        	//无限向线程栈的局部变量内添加对象,让gc时刻在触发
            heapTests.add(new HeapTest());
            Thread.sleep(10);
        }
    }
}

打开jdk8自带的jvisualvm.exe观察上述代码的运行结果:
在这里插入图片描述

此时,整体的运行进展与我们思考的一致。首先将对象创建到堆的Eden区,当Eden方放满时将对象放到s1区;随后eden再次放满后,将s1和eden的对象再次收集,将一部分对象放到s0,另一部分无法存放下的对象放到了老年代。
在这里插入图片描述
从程序即将OOM的图中,我们也可以看到,默认情况下,Eden区、s0、s1区的内存空间在不断的动态扩展和缩小。这最直接的显示就是图中,柱状的s区或三角状的eden区在动态的扩大或缩小。而老年代的空间由于无法回收,始终在慢慢增长,而元空间内类元信息在一开始就确定了,不会随时间变化。

检查环境中JVM的默认参数配置:

java -XX:+PrintFlagsFinal -version

2.2 方法区内存分配机制

1、JVM默认配置
首先看一下JDK8中JVM对方法区触发full GC阈值的默认设置,
在这里插入图片描述

  • 元空间默认触发full gc阈值,MetaspaceSize约为21M(gc后会动态扩展、缩小)
  • 另外MaxMetaspaceSize 最大元空间值,实际是2^64次方,换算成内存可以理解为无穷大
    2、调优
    为了减少由元空间大小触发阈值导致的full gc,从而造成的STW,所以要将元空间触发gc的默认值及元空间最大值手动设置。一般针对8G内存将上述值设置为256M。
    优化点1:
java  ‐XX:MetaspaceSize=256M ‐XX:MaxMetaspaceSize=256M

2.3 线程栈内存分配机制

我们最常见的线程栈问题,一般是StackOverflowError,含义即是栈溢出。
什么场景可能触发栈溢出?

  • 当线程在计算过程中需要分配比当前线程被允许拥有的最大栈空间,这是则会抛出StackOverflowError(官网解释)。
  • 当线程尚未超出被允许拥有的最大占空间,而此时内存不足了,则OOM
    在这里插入图片描述
  • 可以看到ThreadStackSize的init值是1024(注意这里的单位是K),也就是1M。
  • java ‐Xss512k(‐Xss256k)

个人理解分析
针对线程栈的调优需要根据实际情况,具体情况具体分析。
一般来说线程栈的默认大小是没有必要进行调优的,1M可以满足除非死循环和上千次递归调用深度的需求。
但追求极致时,可以对服务接口复杂度低。线程会执行的方法不多,方法内的临时变量不多,方法内的对象不多时。我们可以适当将线程栈的大小调低,这样相同环境可以由更多的线程同时运行。

实际案例分析:

public class JvmStackDemo {
    //对比设置线程栈大小-Xss128 和默认1M,线程栈中能递归的方法次数
    static int count = 0;
    static void Recursion(){
        System.out.println(count++);
        Thread.sleep(100);
        Recursion();
    }
    
    public static void main(String[] args) {
        try {
            Recursion();
        }catch (Exception e){
            e.printStackTrace();
            System.out.println(count);
        }
    }
}

我们发现:
默认使用1M线程栈空间时,线程内方法递归的次数达到9812次
当设置-Xss128k时,方法递归的次数是1033次
当然,这种测试只能在宏观上感受,1、线程栈的空间时有限的,2、线程栈的大小设置是可以配合需要设置的

2.4 堆内模型及分配机制

最坑爹的当然还得看堆内存,因为JVM一系列的垃圾回收机制多是为堆量身打造的。
在这里插入图片描述

2.4.1分配内存

由于类的元信息是固定的,那么类不管有多少属性信息,依次按照类成员类型就可以计算出类对象所需要占据的空间大小,此时,就要为对象在堆内划分空间。
需要划分对象的内存空间,此时以下两件事就提上日程了。

  • 1、如何划分空间?
    内存空间模型又可以分为2种,
    1.1 指针碰撞(Bump the Pointer)默认方式
    内存空间是规整有序的,碰撞指针像管理员一样,将空闲内存和已用内存分开,指针指向临界点,每次有新对象进入,指针向空闲区移动该对象的空间长度。
    1.2 空闲列表(Free List)
    如果内存空间是不规整的,那么对象又需要一块完整的区域来存放。此时,就需要虚拟机维护列表,查看哪里的空间可容纳下新的对象,塞进去。
  • 2、多线程并发划分空间怎么办?
    2.1 CAS(compare and swap)
    依赖CAS操作的原子性,将更新操作和物理内存划分的结果绑定,保证结果一致性。
    2.2 Thread Local Allocation Buffer(TLAB)本地线程分配缓冲
    简言之,堆中为每个线程预设一小块区域。当线程需要存储对象时,只要在该区域存储对象即可。
    通过­XX:+/­ UseTLAB参数来设定虚拟机是否使用TLAB(JVM会默认开启­XX:+UseTLAB),­XX:TLABSize 指定TLAB大小。
    由下图可见,JDK8是默认开启TLAB为线程开启2M的内存空间的。
    在这里插入图片描述
2.4.2 对象头

对象头包涵3个部分:

  • Mark Work(32位系统占4个字节、64位系统占8字节)。主要有锁状态、hashcode、4位的分代年龄等
  • Klass Point 。Klass Point在堆内存中实际存储的是类在元空间的类信息地址。所以32位系统使用4个字节完全足够存放该地址(32系统支持的最大内存为2^32 =4GB ),而64位系统的所能支持的内存是非常大的,但实际使用时一般16G、32G、64G完全可以使用34-36位表示,那么剩下的很大空间都是浪费的。所以JVM对这类地址进行压缩,从而得到32位信息,在寻址时再解压缩在得到原地址。
  • 数组长度
    在这里插入图片描述
 <dependency> 
 	<groupId>org.openjdk.jol</groupId> 
 	<artifactId>jol‐core</artifactId>
	<version>0.9</version> 
</dependency>

我们可以通过上述依赖包,查看对象头的实际信息。进而由开关压缩功能,展现压缩头、默认对象地址的压缩

‐XX:+UseCompressedOops 默认开启的压缩所有指针
‐XX:+UseCompressedClassPointers 默认开启的压缩对象头里的类型指针Klass Pointer

我们通过对空对象、数组、包含成员对象的类来看对象压缩前后的内存空间情况对比

public class JOLSample {
    public static void main(String[] args) {
        ClassLayout layout = ClassLayout.parseInstance(new Object());
        System.out.println(layout.toPrintable());

        System.out.println();
        ClassLayout layout1 = ClassLayout.parseInstance(new int[]{});
        System.out.println(layout1.toPrintable());
        System.out.println();

        ClassLayout layout2 = ClassLayout.parseInstance(new A());
        System.out.println(layout2.toPrintable());
    }

    public static class A {
        //8B mark word  //4B Klass Pointer 如果关闭压缩‐XX:‐UseCompressedClassPointers或‐XX:‐UseCompressedOops,则占用8B
        int id; //4B
        String name; //4B 如果关闭压缩‐XX:‐UseCompressedOops,则占用8B
        byte b; //1B
        Object o; //4B 如果关闭压缩‐XX:‐UseCompressedOops,则占用8B
    }
}

开启指针压缩

#object对象 8位Mark word + 4位klass point + 4位补位
java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

#数组[] 8位Mark word + 4位klass point +4位数组长度
[I object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           6d 01 00 f8 (01101101 00000001 00000000 11111000) (-134217363)
     12     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
     16     0    int [I.<elements>                             N/A
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total


#自定义类对象 8位Mark word + 4位klass ponit + int 4位 + string 4位 + byte1位(3位补位) +object 4位  +补位4位
com.huawei.heap.JOLSample$A object internals:
 OFFSET  SIZE               TYPE DESCRIPTION                               VALUE
      0     4                    (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4                    (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4                    (object header)                           61 cc 00 f8 (01100001 11001100 00000000 11111000) (-134165407)
     12     4                int A.id                                      0
     16     1               byte A.b                                       0
     17     3                    (alignment/padding gap)                  
     20     4   java.lang.String A.name                                    null
     24     4   java.lang.Object A.o                                       null
     28     4                    (loss due to the next object alignment)
Instance size: 32 bytes
Space losses: 3 bytes internal + 4 bytes external = 7 bytes total

如果关闭指针压缩,原有占有4位的对象都变成占8位,剩余补位。

2.5 常量池

在这里插入图片描述
在这里插入图片描述

3、对象分配过程(内存调优的重要依据)

在这里插入图片描述

3.1 对象进入Eden区

对象进入到Eden区,会提前判断。是否适合直接进入Eden,尽可能的节省空间,分配到栈上。或者把大对象直接分到老年代,节省年轻代的空间。剩余的才考虑放到Eden区什么位置,TLAB进入线程堆内存缓冲区,或者CAS直接分配

3.2 对象进入Survivor

当Eden区对象占满空间时,会触发Minor GC,此时将存活的对象放入S0区(任意)。下次Eden区再满了,再次触发Minor GC,将S0区对象和Eden区存活对象放到S1区。此时,如果因为S区剩余空间较小,触发动态年龄判断机制,将部分对象送到老年代(在进入老年代的对象里详述)。

3.3 对象进入老年代

3.3.1 大对象判断机制

当对象进入Eden区之前,通过阈值判断是否需要直接进入到老年代。

3.3.2 分代年龄机制进入老年代

当对象的Age超过阈值,一般是15岁(CMS默认6岁),将超出的对象送到老年代。

3.3.3 动态年龄判断机制

一般在Minor GC之后,S区剩余空间不多。通过计算S区Age1+Age2+Age3+AgeN 的对象空间之和 > S区总空间50%,此时提前将年龄大于N的对象送到老年代,以缓解年轻代区的压力。

3.3.4 老年代空间担保机制

每次Minor GC之前,计算老年代剩余可用空间。该空间 < 年轻代所有对象占用的空间(包括垃圾对象),说明老年代也没空间了,如果正常执行有可能会直接触发Full GC,但如果设置了老年代空间担保机制,就会计算每次进行Minor GC后放入老年代的空间是否比剩余空间大,如果平均值能放的下,那么就直接进入老年代。不设置或者放不下就FullGC。Full GC后还是没有空间存放Minor GC产生的对象,那么就OOM

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

旧梦昂志

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值