JVM 面试深入理解内存模型和垃圾回收(二)

2023-11-04

JVM 面试深入理解内存模型和垃圾回收(二)

文章目录

今天我们主要分享下面试中常被问到的 JVM 的相关知识:

  • 内存模型
  • 垃圾回收
  • 垃圾收集器

1. 运行时数据区域

这里写图片描述

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.

https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.5 >>>

Java 虚拟机定义了在程序执行期间使用的各种运行时数据区域。其中一些数据区域是在 Java 虚拟机启动时创建的,只有在 Java 虚拟机退出时才会销毁。其他数据区域为每个线程独自使用的,每个线程的数据区域是在创建线程时创建的,并在线程退出时销毁。

1.8同1.7比,最大的差别就是:元数据区取代了永久代。元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元数据空间并不在虚拟机中,而是使用本地内存

1.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 虚拟机可以同时支持多个执行线程(jls17)。每个 Java 虚拟机线程都有自己的 pc (程序计数器)寄存器。在任何时候,每个 Java 虚拟机线程都在执行单个方法的代码,即该线程的当前方法(2.6)。如果这个方法不是本机的,那么 pc 寄存器就会包含当前正在执行的 Java 虚拟指令的地址。如果当前由线程执行的方法是本地方法,那么 Java 虚拟机的 pc 寄存器的值是未定义的(null)。Java 虚拟机的 pc 寄存器足够宽,可以在特定平台上保存 returnAddress 或本机指针。

简而言之,每个线程独自占用一个寄存器,指向当前线程正在执行的字节码代码的行号。如果当前线程执行的是native方法,则其值为null。

我们都知道一个JVM进程中有多个线程在执行,而线程中的内容是否能够拥有执行权,是根据 CPU 调度来的。假如线程A正在执行到某个地方,突然失去了CPU的执行权,切换到线程B了,然后当线程A再获得CPU执行权的时候,怎么能继续执行呢?这就是需要在线程中维护一个变量,记录线程执行到的位置,也就是 PC 程序计数器的作用。

1.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.

每个 Java 虚拟机线程都有一个专用的 Java 虚拟机堆栈,与线程同时创建。Java 虚拟机堆栈存储帧(2.6)。Java 虚拟机堆栈类似于传统语言(如 c)的堆栈: 它保存局部变量和部分结果,并在方法调用和返回中发挥作用。因为 Java 虚拟机栈从来不会被直接操作,除了推送和弹出帧,帧可能会被分配到堆中。Java 虚拟机堆栈的内存不需要是连续的。

In the First Edition of The Java® Virtual Machine Specification, the Java Virtual Machine stack was known as the Java stack.

在 Java 虚拟机规范的第一版中,Java 虚拟机堆栈被称为 Java 堆栈。

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.

该规范允许 Java 虚拟机堆栈具有固定的大小,或者根据计算的需要动态扩展和收缩。如果 Java 虚拟机堆栈的大小是固定的,那么在创建该堆栈时可以独立地选择每个 Java 虚拟机堆栈的大小。

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.

一个 Java 虚拟机实现可以为程序员或用户控制 Java 虚拟机堆栈初始大小,以及,在动态扩展或收缩 Java 虚拟机堆栈的情况下,控制最大和最小值。

The following exceptional conditions are associated with Java Virtual Machine stacks:

下列异常情况与 Java 虚拟机堆栈相关:

  • If the computation in a thread requires a larger Java Virtual Machine stack than is permitted, the Java Virtual Machine throws a StackOverflowError.

    如果线程中的计算需要比允许的更大的 Java 虚拟机堆栈,则 Java 虚拟机抛出 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 虚拟机抛出 OutOfMemoryError。

stack

线程私有,每个线程对应一个Java虚拟机栈,其生命周期与线程同进同退。每个Java方法在被调用的时候都会创建一个栈帧,并入栈。一旦完成调用,则出栈。所有的的栈帧都出栈后,线程也就完成了使命。

图解栈和栈帧

void a(){b();}
void b(){c(); }
void c(){ }

image-20201011011202515

1.2.1 Frames

A frame is used to store data and partial results, as well as to perform dynamic linking, return values for methods, and dispatch exceptions.

帧用于存储数据和部分结果,以及执行动态链接、方法返回值和分派异常。

A new frame is created each time a method is invoked. A frame is destroyed when its method invocation completes, whether that completion is normal or abrupt (it throws an uncaught exception). Frames are allocated from the Java Virtual Machine stack (§2.5.2) of the thread creating the frame. Each frame has its own array of local variables (§2.6.1), its own operand stack (§2.6.2), and a reference to the run-time constant pool (§2.5.5) of the class of the current method.

每次调用方法时都会创建一个新的帧。当方法调用完成时,帧将被销毁,不管该完成是正常的还是突然的(它将引发未捕获的异常)。帧是从创建帧的线程的 Java 虚拟机栈(2.5.2)中分配的。每个帧都有自己的局部变量数组(2.6.1)、自己的操作数堆栈(2.6.2) ,以及对当前方法类的运行时常量池(2.5.5)的引用。

A frame may be extended with additional implementation-specific information, such as debugging information.

可以使用额外的特定于实现的信息(如调试信息)扩展帧。

The sizes of the local variable array and the operand stack are determined at compile-time and are supplied along with the code for the method associated with the frame (§4.7.3). Thus the size of the frame data structure depends only on the implementation of the Java Virtual Machine, and the memory for these structures can be allocated simultaneously on method invocation.

局部变量数组和操作数堆栈的大小在编译时确定,并与与帧(4.7.3)关联的方法的代码一起提供。因此,帧数据结构的大小仅取决于 Java 虚拟机的实现,并且这些结构的内存可以在方法调用时同时分配。

Only one frame, the frame for the executing method, is active at any point in a given thread of control. This frame is referred to as the current frame, and its method is known as the current method. The class in which the current method is defined is the current class. Operations on local variables and the operand stack are typically with reference to the current frame.

在给定的控制线程中,只有一个帧(执行方法的帧)处于活动状态。这个帧称为当前帧,它的方法称为当前方法。定义当前方法的类是当前类。对局部变量和操作数堆栈的操作通常与当前帧有关。

A frame ceases to be current if its method invokes another method or if its method completes. When a method is invoked, a new frame is created and becomes current when control transfers to the new method. On method return, the current frame passes back the result of its method invocation, if any, to the previous frame. The current frame is then discarded as the previous frame becomes the current one.

如果一个帧的方法调用了另一个方法或者它的方法完成了,那么这个帧就不再是当前的。当方法被调用时,将创建一个新的帧,并在控制转移到新方法时成为当前帧。在方法返回时,当前帧将其方法调用的结果(如果有的话)返回给前一个帧。然后,当前帧变成当前帧时,当前帧将被丢弃。

Note that a frame created by a thread is local to that thread and cannot be referenced by any other thread.

请注意,由线程创建的帧是该线程的本地帧,不能由任何其他线程引用。

官网 :https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.6

  • 栈帧:每个栈帧对应一个被调用的方法,可以理解为一个方法的运行空间。
    每个栈帧中包括局部变量表(Local Variables)、操作数栈(Operand Stack)、指向运行时常量池的引用(A reference to the run-time constant pool)、方法返回地址(Return Address)和附加信息。
    • 局部变量表: 方法中定义的局部变量以及方法的参数存放在这张表中 局部变量表中的变量不可直接使用,如需要使用的话,必须通过相关指令将其加载至操作数栈中作为操作数使用。
    • 操作数栈: 以压栈和出栈的方式存储操作数的
    • 动态链接: 每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接(Dynamic Linking)。
    • 方法返回地址: 当一个方法开始执行后,只有两种方式可以退出,一种是遇到方法返回的字节码指令;一种是遇 见异常,并且这个异常没有在方法体内得到处理。
image-20201011011231592

1.2.2 结合字节码指令理解栈帧

javap -c Person.class > Person.txt

《JVM 面试基础准备篇(一)》我们输出过一个简单类的字节码文件:

public static int calc(int, int);
    descriptor: (II)I
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=4, args_size=2
         0: iconst_3
         1: istore_0
         2: iload_0
         3: iload_1
         4: iadd
         5: istore_2
         6: new           #10                 // class java/lang/Object
         9: dup
        10: invokespecial #1                  // Method java/lang/Object."<init>":()V
        13: astore_3
        14: iload_2
        15: ireturn
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      16     0   op1   I
            0      16     1   op2   I
            6      10     2 result   I
           14       2     3   obj   Ljava/lang/Object;

简单分析下执行流程:

0: iconst_3	  //将int类型常量3压入[操作数栈]
1: istore_0	  //将int类型值存入[局部变量0]
2: iload_0 	  //从[局部变量0]中装载int类型值入栈
3: iload_1    //从[局部变量1]中装载int类型值入栈
4: iadd			  //将栈顶元素弹出栈,执行int类型的加法,结果入栈
5: istore_2	  //将栈顶int类型值保存到[局部变量2]中
6: iload_2	  //从[局部变量2]中装载int类型值入栈
7: ireturn		//从方法中返回int类型的数据

[字节码行号]:[字节码指令]_下标(从0开始)

On class method invocation, any parameters are passed in consecutive local
variables starting from local variable 0. On instance method invocation, local
variable 0 is always used to pass a reference to the object on which the
instance method is being invoked (this in the Java programming language). Any
parameters are subsequently passed in consecutive local variables starting from
local variable 1.
在类方法调用时,任何参数都从局部变量0开始以连续的局部变量传递。对于实例方法调用,总是使用局部变量0来传递对实例方法被调用的对象的引用(在 Java 编程语言中是这样的)。任何参数随后从局部变量1开始在连续的局部变量中传递。

image-20201011011301309

1.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.

Java 虚拟机有一个在所有 Java 虚拟机线程之间共享的堆。堆是运行时数据区域,从中分配所有类实例和数组的内存。

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.

堆是在虚拟机启动时创建的。对象的堆存储由自动存储管理系统(称为垃圾收集器)回收; 对象永远不会显式释放。Java 虚拟机没有特定类型的自动存储管理系统,可以根据实现者的系统需求选择存储管理技术。堆的大小可以是固定的,也可以根据计算的需要进行扩展,如果不需要更大的堆,还可以进行收缩。堆的内存不需要是连续的。

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.

一个 Java 虚拟机实现可以提供程序员或用户对堆的初始大小的控制,以及,如果堆可以动态扩展或收缩,对堆的最大和最小大小的控制。

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 虚拟机将抛出 OutOfMemoryError 错误。

heap

堆是JVM内存占用最大,管理最复杂的一个区域。其唯一的用途就是存放对象实例:几乎所有的对象实例及数组都在对上进行分配。1.7后,字符串常量池从永久代中剥离出来,存放在堆中。堆有自己进一步的内存分块划分,按照GC分代收集角度的划分请参见上图。

1.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.

Java 虚拟机有一个方法区域,该区域在所有 Java 虚拟机线程之间共享。方法区域类似于常规语言或操作系统进程中的“文本”段的编译代码的存储区域。它存储每个类的结构,如运行时常量池、字段和方法数据,以及方法和构造函数的代码,包括用于类和实例初始化和接口初始化的特殊方法(2.9)。

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.

Java 虚拟机实现可以为程序员或用户提供对方法区域初始大小的控制,以及在变大小方法区域的情况下对最大和最小方法区域大小的控制。

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 虚拟机抛出 OutOfMemoryError。

值得说明的:JVM方法区是一种规范,真正的实现:
在 JDK 8 中就是 Metaspace,在 JDK6 或 7 中就是Perm Space。

1.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.

运行时常量池是类文件(4.4)中常量 _ 池表的每类或每接口运行时表示形式。它包含几种常量,从编译时已知的数值文本到必须在运行时解析的方法和字段引用。运行时常量池提供的功能类似于传统编程语言的符号表,尽管它包含的数据范围比典型的符号表更广。

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.

每个运行时常量池都是从 Java 虚拟机的方法区域(2.5.4)中分配的。类或接口的运行时常量池是在 Java 虚拟机创建类或接口时构造的(5.3)。

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.

在创建类或接口时,如果运行时常量池的构造需要比 Java 虚拟机的方法区域可用的内存更多,则 Java 虚拟机抛出 OutOfMemoryError。

See §5 (Loading, Linking, and Initializing) for information about the construction of the run-time constant pool.

有关构造运行时常量池的信息,请参见5(加载、链接和初始化)。

1.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.

Java 虚拟机的实现可以使用传统的栈(通常称为“ c 栈”)来支持本机方法(用 Java 编程语言以外的语言编写的方法)。 本机方法堆栈也可以用于 Java 虚拟机指令集的解释器的实现,如 c 语言的 Java 虚拟机实现,不能加载本机方法,也不依赖于传统的堆栈,不需要提供本机方法堆栈。 如果提供,通常在创建每个线程时为每个线程分配本机方法堆栈。

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.

Java 虚拟机实现可以为程序员或用户提供对本机方法堆栈初始大小的控制,以及对于不同大小的本机方法堆栈,对最大和最小方法堆栈大小的控制。

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.

    如果线程中的计算需要比允许的更大的本机方法堆栈,那么 Java 虚拟机抛出一个 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 虚拟机将抛出 OutOfMemoryError。

如果当前线程执行的方法是Native类型的,这些方法就会在本地方法栈中执行。那如果在Java方法执行的时候调用native的方法呢?

image-20201009200205123

2. 内存模型

2.1 引用

image-20201009201317056

方法区指向堆

方法区中会存放静态变量,常量等数据。如果是下面这种情况,就是典型的方法区中元素指向堆中的对象。

private static Object obj = new Object();

image-20201009230812209

堆指向方法区

注意,方法区中会包含类的信息,堆中会有对象,那怎么知道对象是哪个类创建的呢?

image-20201009231015607

2.2 Java对象内存模型

一个对象怎么知道它是由哪个类创建出来的?怎么记录?这就需要了解一个Java对象的具体信息。一个Java对象在内存中包括3个部分:对象头、实例数据和对齐填充。

image-20201009231158556

上面对运行时数据区描述了很多,其实重点存储数据的是堆和方法区(非堆),所以内存的设计也着重从这两方面展开(注意这两块区域都是线程共享的)。对于虚拟机栈,本地方法栈,程序计数器都是线程私有的。可以这样理解,JVM运行时数据区是一种规范,而JVM内存模式是对该规范的实现。

image-20201009231453878

2.2.1 对象创建的过程

一般情况下,新创建的对象都会被分配到Eden区,一些特殊的大的对象会直接分配到Old区。

我是一个普通的Java对象,我出生在Eden区,在Eden区我还看到和我长的很像的小兄弟,我们在Eden区中玩了挺长时间。有一天Eden区中的人实在是太多了,我就被迫去了Survivor区的“From”区,自从去了Survivor 区,我就开始漂了,有时候在Survivor的“From”区,有时候在Survivor的“To”区,居无定所。直到我18岁的时候,爸爸说我成人了,该去社会上闯闯了。于是我就去了年老代那边,年老代里,人很多,并且年龄都挺大的。

image-20201009231607260

2.2.2 常见问题

  • 如何理解Minor/Major/FullGC
MinorGC:新生代
MajorGC:老年代
FullGC:新生代+老年代
  • 为什么需要 Survivor 区?只有 Eden 不行吗?
如果没有Survivor,Eden区每进行一次MinorGC,存活的对象就会被送到老年代。
这样一来,老年代很快被填满,触发MajorGC(因为MajorGC一般伴随着MinorGC,也可以看做触发了FullGC)。
老年代的内存空间远大于新生代,进行一次FullGC消耗的时间比MinorGC长得多。
执行时间长有什么坏处?频发的FullGC消耗的时间很长,会影响大型程序的执行和响应速度。

可能你会说,那就对老年代的空间进行增加或者较少咯。
假如增加老年代空间,更多存活对象才能填满老年代。虽然降低FullGC频率,但是随着老年代空间加大,一旦发生FullGC,执行所需要的时间更长。
假如减少老年代空间,虽然FullGC所需时间减少,但是老年代很快被存活对象填满,FullGC频率增加。

所以Survivor的存在意义,就是减少被送到老年代的对象,进而减少FullGC的发生,Survivor的预筛选保证,只有经历16次MinorGC还能在新生代中存活的对象,才会被送到老年代。
  • 为什么需要两个 Survivor 区?
最大的好处就是解决了碎片化。也就是说为什么一个Survivor区不行?第一部分中,我们知道了必须设置
Survivor区。假设现在只有一个Survivor区,我们来模拟一下流程:
刚刚新建的对象在Eden中,一旦Eden满了,触发一次MinorGC,Eden中的存活对象就会被移动到Survivor 区。这样继续循环下去,下一次Eden满了的时候,问题来了,此时进行MinorGC,Eden和Survivor各有一些存活对象,如果此时把Eden区的存活对象硬放到Survivor区,很明显这两部分对象所占有的内存是不连续的,也就导致了内存碎片化。
永远有一个Survivorspace是空的,另一个非空的Survivorspace无碎片。
  • 新生代中Eden:S1:S2为什么是8:1:1?
新生代中的可用内存:复制算法用来担保的内存为9:1 
可用内存中Eden:S1区为8:1 
即新生代中Eden:S1:S2=8:1:1 
现代的商业虚拟机都采用这种收集算法来回收新生代,IBM公司的专门研究表明,新生代中的对象大概98%是“朝生夕死”的
  • 堆内存中都是线程共享的区域吗?
JVM默认为每个线程在Eden上开辟一个buffer区域,用来加速对象的分配,称之为TLAB,全称:Thread LocalAllocationBuffer。
对象优先会在TLAB上分配,但是TLAB空间通常会比较小,如果对象比较大,那么还是在共享区域分配。

2.2.3 参数设置

  • Heap 堆内存大小设置: -Xmx20M -Xms20M
  • Metaspace 元空间大小设置: -XX:MetaspaceSize=50M -XX:MaxMetaspaceSize=50M
  • Stack Space 每个线程的堆栈大小: -Xss128k:设置每个线程的堆栈大小。
    • JDK5 以后每个线程堆栈大小为1M,以前每个线程堆栈大小为 256K。
    • 根据应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在 3000~5000左右。
    • 线程栈的大小是个双刃剑,如果设置过小,可能会出现栈溢出,特别是在该线程内有递归、大的循环时出现溢出的可能性更大,如果该值设置过大,就有影响到创建栈的数量,如果是多线程的应用,就会出现内存溢出的错误。
    • Stack Space用来做方法的递归调用时压入 Stack Frame(栈帧)。所以当递归调用太深的时候,就有可能耗尽Stack Space,爆出 StackOverflow 的错误。

3. 垃圾回收

之前说堆内存中有垃圾回收,比如Young区的MinorGC,Old区的MajorGC,Young区和Old区的FullGC。但是对于一个对象而言,怎么确定它是垃圾?是否需要被回收?怎样对它进行回收?等等这些问题我们还需要详细探索。
因为Java是自动做内存管理和垃圾回收的,如果不了解垃圾回收的各方面知识,一旦出现问题我们很难进行排查和解决,自动垃圾回收机制就是寻找Java堆中的对象,并对对象进行分类判别,寻找出正在使用的对象和已经不会使用的对象,然后把那些不会使用的对象从堆上清除。

3.1 如何确定一个对象是垃圾?

要想进行垃圾回收,得先知道什么样的对象是垃圾。

3.1.1 引用计数法

对于某个对象而言,只要应用程序中持有该对象的引用,就说明该对象不是垃圾,如果一个对象没有任何指针对其
引用,它就是垃圾。弊端: 如果AB相互持有引用,导致永远不能被回收。

3.1.2 可达性分析

通过GCRoot的对象,开始向下寻找,看某个对象是否可达。

image-20201009233200905

能作为GCRoot的有:

类加载器、Thread、虚拟机栈的本地变量表、static成员、常量引用、本地方法栈的变量等。

虚拟机栈(栈帧中的本地变量表)中引用的对象。
方法区中类静态属性引用的对象。
方法区中常量引用的对象。
本地方法栈中JNI(即一般说的Native方法)引用的对象。

3.2 什么时候会垃圾回收?

GC是由JVM自动完成的,根据JVM系统环境而定,所以时机是不确定的。
当然,我们可以手动进行垃圾回收,比如调用System.gc()方法通知JVM进行一次垃圾回收,但是具体什么时刻运行也无法控制。也就是说System.gc()只是通知要回收,什么时候回收由JVM决定。但是不建议手动调用该方法,因为GC消耗的资源比较大。

(1)当Eden区或者S区不够用了
(2)老年代空间不够用了
(3)方法区空间不够用了
(4)System.gc()

3.3 垃圾收集算法

已经能够确定一个对象为垃圾之后,接下来要考虑的就是回收,怎么回收呢?得要有对应的算法,下面介绍常见的垃圾回收算法。

3.3.1 标记-清除(Mark-Sweep)

  • 标记

找出内存中需要回收的对象,并且把它们标记出来

此时堆中所有的对象都会被扫描一遍,从而才能确定需要回收的对象,比较耗时

image-20201009233748188

  • 清除

清除掉被标记需要回收的对象,释放出对应的内存空间

image-20201009233842912

弊端:

标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程
序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
(1)标记和清除两个过程都比较耗时,效率不高
(2)会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。

3.3.2 标记-复制(Mark-Copying)

将内存划分为两块相等的区域,每次只使用其中一块,如下图所示:

image-20201009233954400

当其中一块内存使用完了,就将还存活的对象复制到另外一块上面,然后把已经使用过的内存空间一次清除掉。

image-20201009234022894

弊端:

缺点:空间利用率降低。

3.3.3 标记-整理(Mark-Compact)

复制收集算法在对象存活率较高时就要进行较多的复制操作,效率将会变低。更关键的是,如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都有100%存活的极端情况,所以老年代一般不能直接选用这种算法。

标记过程仍然与"标记-清除"算法一样,但是后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。

其实上述过程相对"复制算法"来讲,少了一个"保留区"

image-20201010084600618

让所有存活的对象都向一端移动,清理掉边界意外的内存。

image-20201010084651076

3.4 分代收集算法

既然上面介绍了3中垃圾收集算法,那么在堆内存中到底用哪一个呢?

  • Young区:复制算法(对象在被分配之后,可能生命周期比较短,Young区复制效率比较高)
  • Old区:标记清除或标记整理(Old区对象存活时间比较长,复制来复制去没必要,不如做个标记再清理)

3.5 垃圾收集器

如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。

image-20201010084907244

3.5.1 Serial

Serial收集器是最基本、发展历史最悠久的收集器,曾经(在JDK1.3.1之前)是虚拟机新生代收集的唯一选择。

它是一种单线程收集器,不仅仅意味着它只会使用一个CPU或者一条收集线程去完成垃圾收集工作,更重要的是其在进行垃圾收集的时候需要暂停其他线程。

  • 优点:简单高效,拥有很高的单线程收集效率缺点:收集过程需要暂停所有线程
  • 算法:复制算法
  • 适用范围:新生代
  • 应用:Client模式下的默认新生代收集器

image-20201010085146815

3.5.2 SerialOld

SerialOld收集器是Serial收集器的老年代版本,也是一个单线程收集器,不同的是采用"标记-整理算法",运行过程和Serial收集器一样。

image-20201010085247022

3.5.3 ParNew

可以把这个收集器理解为Serial收集器的多线程版本。

  • 优点:在多CPU时,比Serial效率高。
  • 缺点:收集过程暂停所有应用程序线程,单CPU时比Serial效率差。
  • 算法:复制算法
  • 适用范围:新生代
  • 应用:运行在Server模式下的虚拟机中首选的新生代收集器

image-20201010085417195

3.5.4 Parallel Scavenge

发音:['perə.lel] ['skævəndʒ]

Parallel Scavenge 收集器是一个新生代收集器,它也是使用复制算法的收集器,又是并行的多线程收集器,看上去和ParNew一样,但是 Parallel Scanvenge 更关注系统的吞吐量。

吞吐量=运行用户代码的时间/(运行用户代码的时间+垃圾收集时间)。比如虚拟机总共运行了100分钟,垃圾收集时间用了1分钟,吞吐量=(100-1)/100=99%。若吞吐量越大,意味着垃圾收集的时间越短,则用户代码可以充分利用CPU资源,尽快完成程序的运算任务。

-XX:MaxGCPauseMillis控制最大的垃圾收集停顿时间,-XX:GCRatio直接设置吞吐量的大小。

3.5.5 ParallelOld

ParallelOld收集器是ParallelScavenge收集器的老年代版本,使用多线程和标记-整理算法进行垃圾回收,也是更加关注系统的吞吐量。

3.5.6 CMS

官网:https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/cms.html#concurrent_mark_sweep_cms_collector>>>

CMS(ConcurrentMarkSweep)收集器是一种以获取最短回收停顿时间为目标的收集器。采用的是"标记-清除算法",整个过程分为4步

  • 初始标记CMS initial mark 标记GCRoots直接关联对象,不用Tracing,速度很快
  • 并发标记CMS concurrent mark 进行GCRootsTracing
  • 重新标记CMS remark 修改并发标记因用户程序变动的内容
  • 并发清除CMS concurrent sweep 清除不可达对象回收空间,同时有新垃圾产生,留着下次清理称为浮动垃圾

由于整个过程中,并发标记和并发清除,收集器线程可以与用户线程一起工作,所以总体上来说,CMS收集器的内存回收过程是与用户线程一起并发地执行的。

image-20201010090421565

  • 优点:并发收集、低停顿
  • 缺点:产生大量空间碎片、并发阶段会降低吞吐量

3.5.7 G1(Garbage-First)

官网:https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/g1_gc.html#garbage_first_garbage_collection

使用G1收集器时,Java堆的内存布局与就与其他收集器有很大差别。

  • 它将整个Java堆划分为多个大小相等的独立区域(Region)。

  • 虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分Region(不需要连续)的集合。

  • 每个 Region 大小都是一样的,可以是1M到32M之间的数值,但是必须保证是2的n次幂如果对象太大,一个Region 放不下[超过Region大小的50%],那么就会直接放到 H 中

  • 设置 Region 大小:-XX:G1HeapRegionSize=M

  • 所谓 Garbage-Frist,其实就是优先回收垃圾最多的 Region 区域

  • 分代收集(仍然保留了分代的概念)
  • 空间整合(整体上属于“标记-整理”算法,不会导致空间碎片)
  • 可预测的停顿(比CMS更先进的地方在于能让使用者明确指定一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒)

image-20201010090736472

工作过程可以分为如下几步:

  • 初始标记(Initial Marking): 标记 GC Roots 能够关联的对象,并且修改 TAMS 的值,需要暂停用户线程
  • 并发标记(Concurrent Marking): 从GC Roots进行可达性分析,找出存活的对象,与用户线程并发执行
  • 最终标记(Final Marking): 修正在并发标记阶段因为用户程序的并发执行导致变动的数据,需暂停用户线程**
  • 筛选回收(Live Data Counting and Evacuation): 对各个 Region 的回收价值和成本进行排序,根据用户所期望的 GC 停顿时间制定回收计划

1602318174773

3.5.8 ZGC

官网 : https://docs.oracle.com/en/java/javase/11/gctuning/z-garbage-collector1.html#GUID-A5A42691-095E-47BA-B6DC-FB4E5FAA43D0

JDK11 引入的ZGC收集器,不管是物理上还是逻辑上,ZGC中已经不存在新老年代的概念了。会分为一个个 page,当进行 GC 操作时会对 page 进行压缩,因此没有碎片问题只能在64位的linux上使用,目前用得还比较少。

  • 可以达到10ms以内的停顿时间要求
  • 支持TB级别的内存
  • 堆内存变大后停顿时间还是在10ms以内

1602322566132

3.6 垃圾收集器分类

3.6.1 串行收集器

Serial 和 SerialOld

只能有一个垃圾回收线程执行,用户线程暂停。适用于内存比较小的嵌入式设备。

3.6.2 并行收集器[吞吐量优先]

Parallel Scanvenge、ParallelOld

多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。适用于科学计算、后台处理等若交互场景。

3.6.3 并发收集器[停顿时间优先]

CMS、G1

用户线程和垃圾收集线程同时执行(但并不一定是并行的,可能是交替执行的),垃圾收集线程在执行的时候不会停顿用户线程的运行。适用于相对时间有要求的场景,比如 Web。

3.7 常见问题

3.7.1 吞吐量和停顿时间

  • 停顿时间:垃圾收集器进行垃圾回收终端应用执行响应的时间
  • 吞吐量:运行用户代码时间/(运行用户代码时间+垃圾收集时间)

停顿时间越短就越适合需要和用户交互的程序,良好的响应速度能提升用户体验;高吞吐量则可以高效地利用CPU时间,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务。

小结:这两个指标也是评价垃圾回收器好处的标准。

3.7.2 如何选择合适的垃圾收集器

官网建议:https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/collectors.html#sthref28

  • 优先调整堆的大小让服务器自己来选择
  • 如果内存小于100M,使用串行收集器
  • 如果是单核,并且没有停顿时间要求,使用串行或JVM自己选
  • 如果允许停顿时间超过1秒,选择并行或JVM自己选
  • 如果响应时间最重要,并且不能超过1秒,使用并发收集器

3.7.3 G1收集

  • JDK7开始使用,JDK8非常成熟,JDK9默认的垃圾收集器,适用于新老生代。
  • 是否使用G1收集器?
    • 50%以上的堆被存活对象占用
    • 对象分配和晋升的速度变化非常大
    • 垃圾回收时间比较长
  • G1中的RSet:全称RememberedSet,记录维护Region中对象的引用关系
    • 试想,在G1垃圾收集器进行新生代的垃圾收集时,也就是 MinorGC,假如该对象被老年代的 Region 中所引用,这时候新生代的该对象就不能被回收,怎么记录呢?
    • 不妨这样,用一个类似于 hash 的结构,key 记录 region 的地址,value 表示引用该对象的集合,这样就能知道该对象被哪些老年代的对象所引用,从而不能回收。

3.7.4 如何开启需要的垃圾收集器

  • 串行

    -XX:+UseSerialGC
    -XX:+UseSerialOldGC

  • 并行(吞吐量优先)

    -XX:+UseParallelGC
    -XX:+UseParallelOldGC

  • 并发收集器(响应时间优先)

    -XX:+UseConcMarkSweepGC

    -XX:+UseG1GC

image-20201011010826995

小结

本文主要介绍了 JVM 的运行时数据区域的组成、内存模型、常见垃圾回收算法以及垃圾收集器的选择。

REFERENCES


image-20200927235342666

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

JVM 面试深入理解内存模型和垃圾回收(二) 的相关文章

  • Scala 中的多个类型下限

    我注意到tuple productIterator总是返回一个Iterator Any 想知道是否无法设置多个下限 因此它可能是最低公共超类型的迭代器 我尝试并搜索了一下 但只发现this https stackoverflow com q
  • JVM内存段分配

    好吧 我有一个关于 JVM 内存段的问题 我知道每个 JVM 都会选择稍微不同地实现这一点 但这是一个总体概念 在所有 JVM 中应该保持相同 一个在运行时不使用虚拟机执行的标准C C 程序在运行时有四个内存段 代码 堆栈 堆 数据 所有这
  • Java 中清除嵌套 Map 的好方法

    public class MyCache AbstractMap
  • Java 语言中不可用的字节码功能

    当前 Java 6 是否有一些事情可以在 Java 字节码中完成而在 Java 语言中无法完成 我知道两者都是图灵完备的 所以将 可以做 理解为 可以做得更快 更好 或者只是以不同的方式 我正在考虑额外的字节码 例如invokedynami
  • Kotlin 未解决的参考:CLI 上 gradle 的 println

    放一个printlnkotlin 函数返回之前的语句会崩溃 堆栈跟踪 thufir dur NetBeansProjects kotlin thufir dur NetBeansProjects kotlin gradle clean bu
  • Scala 为了在 JVM 上运行做出了哪些妥协?

    Scala 是一种很棒的语言 但我想知道如果它有自己的运行时 如何改进 IE 由于 JVM 的选择 做出了哪些设计选择 我所知道的两个最重要的妥协是 类型擦除 http java sun com docs books tutorial ja
  • JVM:是否可以操作帧堆栈?

    假设我需要执行N同一线程中的任务 这些任务有时可能需要来自外部存储的一些值 我事先不知道哪个任务可能需要这样的值以及何时 获取速度要快得多M价值观是一次性的而不是相同的M值在M查询外部存储 注意我不能指望任务本身进行合作 它们只不过是 ja
  • 无法为对象堆保留足够的空间

    每次尝试运行该程序时 我都会重复出现以下异常 VM初始化期间发生错误 无法为对象堆保留足够的空间 无法创建Java虚拟机 我尝试增加虚拟内存 页面大小 和 RAM 大小 但无济于事 我怎样才能消除这个错误 运行 JVM XX MaxHeap
  • 调整 Java 类以提高 CPU 缓存友好性

    在设计java类时 对于实现CPU缓存友好性有哪些建议 到目前为止我学到的是应该尽可能多地使用 POD 即 int 而不是整数 这样 在分配包含对象时 数据将被连续分配 例如 class Local private int data0 pr
  • 使用适用于 API v2 的 Dropbox Java SDK 时出现 SSLHandshakeException

    In a XPages我想使用的应用程序适用于 API v2 的 Dropbox Java SDK 2 1 2 获取有关我的 Dropbox 帐户的信息 以下代码用于检索相应的帐户对象 String atoken DbxRequestCon
  • JVM锯齿状空闲进程

    我目前正在进行一项涉及 JVM 及其内存使用工作原理的研究 我不明白的是 JVM在空闲时用什么填充它的内存 只是为了在堆几乎达到时释放它 为什么使用的内存不只有一条平线 顺便说一句 这个 java 应用程序托管在 glassfish 上 但
  • Dart/Flutter 如何编译到 Android?

    我找不到任何具体的资源 Dart 是否被编译到 JVM 或者 Google 的团队是否编译了 Dart VM 以在 JVM 上运行 然后在 JVM 内的 Dart VM 中运行 Dart 前者更有意义 并且符合 无桥 的口号 但后者似乎更符
  • 是否可以强制 JVM 在堆中而不是堆中创建对象?

    我读过一些文章 有时JVM会识别一些对象并尝试在堆栈中而不是堆中创建它 因为堆栈上的内存分配比堆中的内存分配便宜 堆栈上的释放是免费的 并且堆栈由以下方式有效管理 运行时 那么 堆栈中的对象分配是如何工作的 有什么方法可以强制 JVM 执行
  • Java 加载类时如何管理内存?

    想象一下 我有一个包含 10 个方法的类 我需要从该类中实例化 10 个对象 问题是 JVM 会在对象创建时为 10 个实例分配 10 个不同的内存空间 我的意思是在我调用构造函数时 即 new MyClass 吗 或者它会在内存中加载一次
  • Java 比 Xmx 参数消耗更多内存

    我有一个非常简单的 Web 服务器类 基于 Java SEHttpServer class 当我使用此命令启动编译的类来限制内存使用时 java Xmx5m Xss5m Xrs Xint Xbatch Test 现在如果我使用检查内存top
  • java内存不足然后退出

    我有一个必须分析大文件的软件 限制输入或提供无限内存都不是一个选择 所以我必须忍受飞行的 OOME 因为 OOME 只杀死线程 所以我的软件运行在一些糟糕的状态 从外面看一切都很好 因为进程正在运行 但在内部却是脑死亡 我想拔掉它的插头 但
  • 有没有一种独立的JAVA可以在PC上运行而无需任何操作系统

    据我所知 java 程序可以在任何操作系统上运行 任何类型的机器都有 JVM 我需要一个在我的 PC 上独立运行的 JVM 而不是在我的操作系统 Windows 或任何其他操作系统 上运行 我的意思是 JVM 的作用类似于启动 而不是操作系
  • Eclipse 找不到 javaw.exe

    当我启动 eclipse 时 我遇到这个问题 javaw exe在我的电脑中的位置是C Program Files Java jre7 bin 我尝试更改路径环境变量 然后出现不同的错误 例如 JRE 更新到 jre1 8 0 111 后
  • 在 Android 上运行 Java 字节码 - DalvikVM 之上的 Sun JVM

    由于 java 实现 OpenJDK 和Android的虚拟机DalvikVM是开源的 因此必须可以在Google的DalvikVM之上实现Sun的JavaVM 这将使运行基于 JVM 的应用程序和语言成为可能 Clojure Jython
  • 从不同 JVM 中的 Java 桌面应用程序中执行 Java main 方法

    我有一个桌面应用程序 当有人按下按钮时 我希望它启动另一个执行类的 main 方法的 JVM 我的桌面应用程序已经依赖于包含具有我想要执行的 main 方法的类的 jar 目前我有以下代码 但是 我希望它们是一种更优雅的方法 Runtime

随机推荐

  • QT布局

    布局管理 以下是Qt手册中的 布局管理 的译文 在一个Widget中 Qt布局管理系统提供了一个简单而有效的方式来自动组织子widget 以保证他们能够很好地利用可用空间 介绍 Qt包含一个布局管理类的集合 它们被用来描述widgets如何
  • JSON的编写规则

    JSON的规则很简单 对象是一个无序的 名称 值 对 集合 一个对象以 左括号 开始 右括号 结束 每个 名称 后跟一个 冒号 名称 值 对 之间使用 逗号 分隔 规则如下 1 映射用冒号 表示 名称 值 2 并列的数据之间用逗号 分隔 名
  • 敏感性、特异度、α、β、阳性预测值(PPV)、阴性预测值(NPV)等指标及置信区间计算(附R语言代码)

    这个虽然简单但老是被绕进去 所以整理一下方便查阅 首先画一个2 2的混淆矩阵confusion matrix TP True positive 真阳性 FP False positive 假阳性 FN False negative 假阴性
  • 微信小程序存在的风险_浅谈微信小程序会中病毒事件

    小程序其实是云端数据 不在软件里 所以小程序没有进入的端口 所以华为的管家也检测不出来 但是你按照腾讯的方法收索的话 就等于默认开通小程序 把你的数据放到云端 同时你拥有的账号就绑定了一大堆应用程序 这些程序你永远也别想删除掉 除非不用那个
  • C语言学习(四)——字符串处理函数

    字符串处理函数 17个 1 gets include
  • Jmeter(十五) - 从入门到精通 - JMeter导入自定义的Jar包(详解教程)

    1 简介 原计划这一篇是介绍前置处理器的基础知识的 结果由于许多小伙伴或者童鞋们在微信和博客园的短消息中留言问如何引入自己定义的Jar包呢 我一一回复告诉他们和引入插件的Jar包一样的道理 一通百通 但是感觉他们还是很迷糊很迷惘 因此在这里
  • powerdesigner 创建物理结构视图

    PowerDesigner系列产品提供了一个完整的建模解决方案 业务或系统分析人员 设计人员 数据库管理员DBA和开发人员可以对其裁剪以满足他们的特定的需要 本系列将简单介绍PowerDesigner入门使用操作 若有不足或需要补充之处 欢
  • 深度学习环境配置9——Ubuntu下的tensorflow-gpu==2.4.0环境配置

    深度学习环境配置9 Ubuntu下的tensorflow gpu 2 4 0环境配置 注意事项 一 2022 09 04更新 学习前言 各个版本tensorflow2的配置教程 环境内容 环境配置 一 Anaconda安装 1 Anacon
  • 股票获取最大利润

    1 题目背景 给定一个数组 prices 它的第i个元素prices i 表示一支给定股票第i天的价格 你只能选择某一天买入这只股票 并选择在未来的某一个不同的日子卖出该股票 设计一个算法来计算你所能获取的最大利润 返回你可以从这笔交易中获
  • 操作中 "Chinese_PRC_CI_AS" 和 "Chinese_PRC_CI_AI" 之间的排序规则冲突 的解决办法

    最主要就一句话 在条件中不同排序规则的列后面加上 collate Chinese PRC CI AS 即可解决 有个需求 要求数据库系统自动同步两个不同数据库中的人员信息 首先想到写一个存储过程然后由系统任务来自动处理 尝试性的写了下面的查
  • H5实现高德地图的uni.chooseLocation

    在uniapp中获取当前定位和选择当前位置都是做了兼容 在各个平台都可以使用 这篇文章讲解如何定位当前位置 搜索位置 点击进行定位在H5中实现uni chooseLocation 如下图所示 左侧微信小程序的选择位置 右侧为高德地图在H5中
  • 微信小程序 修改三方组件中的自带样式

    众所周知 微信小程序会引用诸如vant weiui等三方组件 当我们想要对组件自带样式进行修改的时候该怎么做呢 1 在调试器中找到想要修改样式的组件的class类名 在对应的wxss中添加样式 此时样式未生效 2 官方文档中提到可以在ext
  • python写的串口助手并连接腾讯云服务器数据库

    结合上一期的基于pyqt5开发的图书管理系统UI 带登录页面 文章做一个此章节的补充 因为老师说需要结合数据库实现登录系统 于是我就想起了自己在腾讯云上买的一个服务器 因此经过百度查询大量的资料 功夫不负有心人 在这个Pyqt5实现的可视化
  • three.js展示obj模型

    利用three js展示obj模型 环境 必须是在web服务器中 下载objShow js
  • 【UART】Verilog实现UART接收和发送模块

    目录 写在前面 UART 工作原理 UART 接收部分 UART RX 模块图 UART RX 时序图 Verilog 实现 UART RX 模块 UART 发送部分 UART TX 模块图 UART TX 时序图 Verilog 实现 U
  • linux如何同时执行两个命令,如何同时运行两个或者多个终端命令

    选项一 分号 运算符 分号 运算符允许你连续执行多个命令 而不管前面的每个命令是否成功 例如 打开终端窗口 在Ubuntu和Linux Mint中 Ctrl Alt T 然后 在一行中键入以下三个命令 用分号分隔 然后按Enter 这会列出
  • 09、用户、组(一):基本分类

    1 用户类别 管理员 普通用户 系统用户 登录用户 2 用户标识 UserID UID 16bits二进制数字 0 65535 管理员 0 普通用户 1 65635 系统用户 1 499 CentOS6 1 999 CentOS7 登录用户
  • springboot2.0 redis使用lettuce连接包实现分布式锁关键词setnx

    springboot升级到2 0之后 关联的spring data redis默认使用的连接包也从原本的jedis改为了性能更好 且线程安全的使用netty实现的lettuce连接包 鉴于spring data默认只提供了setnx不带过期
  • Unable to negotiate with XXXX port 22: no matching host key type found. Their offer: ssh-rsa,ssh-dss

    问题描述 代码仓库已经添加了ssh公钥之后 克隆代码到本地时就报了这个问题 执行命令 git clone git xxxxxxxxxxxxx git 不能正常clone代码 报错信息如下 Unable to negotiate with x
  • JVM 面试深入理解内存模型和垃圾回收(二)

    JVM 面试深入理解内存模型和垃圾回收 二 文章目录 JVM 面试深入理解内存模型和垃圾回收 二 1 运行时数据区域 1 1 The PC Register 1 2 Java Virtual Machine Stacks 1 2 1 Fra