0 JVM和Java的关系

  • JDK = JRE + Java开发工具(java,javac,javadoc,javap…)
  • JRE = JVM + Java核心类库
    即: JDK = JVM + Java核心类库 + Java开发工具

1 JVM架构(JVM内存区域)

1-1 JVM位置在哪里?

  • 硬件——>OS——>JVM
  • 即:JVM在操作系统之上。

1-2 JVM架构

  • 分三大块:

    • Class Loader Subsystem 类加载子系统
    • Runtime Data Area 运行时数据区
    • Execution Engine 执行引擎
  • 画图:

1-3 类加载器

  • 位置:在类加载子系统。
  • 类加载过程:加载——>链接(验证、准备、解析)——>初始化
  • 双亲委派机制:保证类加载和程序的安全。
    • 1)类加载器收到类的请求
    • 2)将这个请求向上委托给父类,一直向上委托(此时在最老)。(加载器:APP->EXC->BOOT)
    • 3)启动加载器检查是否能加载当前类,能就结束,并使用当前加载器;不能就抛异常通知子加载器进行加载
    • 4)重复3
    • 总结:在继承关系中,最老的类优先选择。

1-4 沙箱安全机制

  • 作用:Java安全的核心,限制程序运行环境。

1-5 栈stack

  • 特点:先进后出,后进先出。
  • 作用:主管程序的运行,生命周期和线程同步。线程结束栈内存就释放,不存在垃圾回收问题。
  • 栈存什么?
    • 八大基本数据类型
    • 对象的引用
    • 实例的方法

1-6 方法区

  • 特点:被所有线程共享。
  • 方法区存什么?
    • 所有字段和方法字节码。
    • 静态变量,常量,类信息,即时编译器编译后放入的代码。

1-7 PC寄存器

  • 每个线程都有一个程序计数器,是线程私有的,就会一个指针(我们接触不到),指向方法区中的方法字节码,在执行引擎读取下,加一。
  • 它占用的内容空间几乎忽略不计。

1-8 堆heap(需要垃圾回收)

  • 分区:

    • 1.8之前:新生区(伊甸园区,幸存区),老年区
    • 1.8之后:
  • 堆中存什么?
    • 对象实例和数组。

2 JVM垃圾回收机制——针对 堆

2-1 如何判断对象是不是垃圾?

2-1-1 引用计数法

  • 用计数器记录对象的引用次数,为0的就是垃圾。
  • 缺点:难以解决对象之间相互循环引用问题,所以不用。
  • PS:现在基本不用了。

2-2-2 可达性算法

  • 将GC Roots对象作为起点,从这些节点向下搜索引用的对象,找到对象都标记为非垃圾对象,其余是垃圾对象。
  • GC Roots根节点:线程栈的本地变量,静态变量本地方法栈的变量等等。
  • 优点:解决了引用计数法的对象之间相互循环引用问题。

3 垃圾回收算法

3-1 复制算法

  • gc时把依然存活的对象复制到两个幸存区之一的to幸存区域(谁空谁是to)。当达到15次(默认)时,认为这个对象有很强的生命力,放到
  • 缺点:浪费一个幸存区空间。
  • 优点:不产生碎片。

3-2 标记清除算法

  • 直接回收垃圾对象,保留存活对象。
  • 位置不连续,产生碎片。

3-3 标记整理算法/标记压缩算法

  • 回收之后,整理空间。
  • 优点:不产生碎片。
  • 缺点:效率变低。

4 垃圾回收器(10种…)

5 JMM(Java内存模型)

  • JMM定义了线程工作内存与主内存(线程之间的共享变量)的抽象关系。并不真实存在,它描述的是一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式。
  • JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存(有些地方称为栈空间),用于存储线程私有的数据。Java内存模型中规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可以访问。
  • 首先要将变量从主内存拷贝的自己的工作内存空间,然后对变量进行操作,操作完成后再将变量写回主内存,不能直接操作主内存中的变量,工作内存中存储着主内存中的变量副本拷贝,前面说过,工作内存是每个线程的私有数据区域,因此不同的线程间无法访问对方的工作内存,线程间的通信(传值)必须通过主内存来完成。

  • JMM 与 Java内存区域 唯一相似点,都存在共享数据区域和私有数据区域:

    • 在JMM中主内存属于共享数据区域,从某个程度上讲应该包括了方法区;而工作内存数据线程私有数据区域,从某个程度上讲则应该包括程序计数器虚拟机栈以及本地方法栈

      • 此处为什么包括本地方法栈?本地方法栈中的本地方法是怎么被线程调用的?:Java虚拟机栈用于管理Java方法的调用,而本地方法栈用于管理本地方法的调用。本地方法栈,也是线程私有的。如果线程请求分配的栈容量超过本地方法栈允许的最大容量,Java虚拟机将会抛出一个 StackOverflowError 异常。如果本地方法栈可以动态扩展,并且在尝试扩展的时候无法申请到足够的内存,或者在创建新的线程时没有足够的内存去创建对应的本地方法栈,那么Java虚拟机将会抛出一个 OutOfMemoryError 异常。本地方法栈是线程私有就是因为 只有线程调用时,线程的本地方法栈才会登记本地方法。
      • 它的具体做法是Native Method Stack中登记native方法,在Execution Engine 执行时加载本地方法库。
      • 当某个线程调用一个本地方法时,它就进入了一个全新的并且不再受虚拟机限制的世界。它和虚拟机拥有同样的权限。
        • 本地方法可以通过本地方法接口来访问虚拟机内部的运行时数据区。
        • 它甚至可以直接使用本地处理器中的寄存器。
        • 直接从本地内存的堆中分配任意数量的内存。
    • 或许在某些地方,我们可能会看见主内存被描述为堆内存,工作内存被称为线程栈,实际上他们表达的都是同一个含义。
      个人理解:JMM只是用线程角度思考JVM;JVM架构(JVM内存区域)就是从物理角度。
  • 关于JMM中的主内存和工作内存说明如下:
    • 主内存 存什么?:主要存储的是Java所有线程创建的实例对象(不管这个实例对象是成员变量还是方法中的本地变量(也叫局部变量)),当然也包括了类信息、常量、静态变量。
    • 工作内存 存什么?:主要存储当前方法的所有本地变量信息(针对本线程说)(主内存中变量的拷贝副本),每个线程这能访问自己的工作内存(即线程中的本地变量对其他线程是不可见的,就算是两个线程执行同一段代码,他们也会各自在自己的工作内存中胡藏剑属于当前线程的本地变量,当然也包括了字节码行号指示器,相关Native方法的信息),由于工作内存是每个线程的私有数据,线程间无法相互访问工作内存,因此存储在工作内存的数据不存在线程安全问题
  • 作用:缓存一致性协议,用于定义数据读写的规则。
  • 主内存与工作内存的数据存储类型以及操作方式:
    • 对于一个实例对象中的成员方法而言,

      • 如果方法中包含本地变量(局部变量)是基本数据类型(boolean,byte,short,char,int,long,float,double),将直接存储在工作内存的帧栈结构中,但倘若本地变量(局部变量)是引用类型,那么该变量的引用会存储在功能内存的帧栈中,而对象实例(在这个方法中新建的对象实例)将存储在主内存(共享数据区域,堆)中。
    • 但对于实例对象的成员变量而言,
      • 不管它是基本数据类型或者包装类型(Integer、Double等)还是引用类型,都会被存储到堆区。
    • 至于static变量以及类本身相关信息将会存储在主内存中。
    • 需要注意的是,在主内存中的实例对象可以被多线程共享,倘若两个线程同时调用了同一个对象的同一个方法,那么两条线程会将要操作的数据拷贝一份到自己的工作内存中,执行完成操作后才刷新到主内存,简单示意图如下所示:

6 硬件内存架构与JMM

6-1 硬件内存架构


当一个CPU需要访问主存时,会先读取一部分主存数据到CPU缓存(当然如果CPU缓存中存在需要的数据就会直接从缓存获取),进而在读取CPU缓存到寄存器,当CPU需要写数据到主存时,同样会先刷新寄存器中的数据到CPU缓存,然后再把数据刷新到主内存中。

6-2 Java线程与硬件处理器

6-2-1 JVM中线程的实现原理

在Window系统和Linux系统上,Java线程的实现是基于一对一的线程模型,所谓的一对一模型,实际上就是通过语言级别层面程序去间接调用系统内核的线程模型(即我们在使用Java线程时,Java虚拟机内部是转而调用当前操作系统的内核线程来完成当前任务)。
这里需要了解一个术语,内核线程(Kernel-Level Thread,KLT),它是由操作系统内核(Kernel)支持的线程,这种线程是由操作系统内核来完成线程切换,内核通过操作调度器进而对线程执行调度,并将线程的任务映射到各个**处理器(CPU)**上。
每个内核线程可以视为内核的一个分身,这也就是操作系统可以同时处理多任务的原因。由于我们编写的多线程程序属于语言层面的,程序一般不会直接去调用内核线程,取而代之的是一种轻量级的进程(Light Weight Process),也是通常意义上的线程,由于每个轻量级进程都会映射到一个内核线程,因此我们可以通过轻量级进程调用内核线程,进而由操作系统内核将任务映射到各个处理器,这种轻量级进程与内核线程间1对1的关系就称为一对一的线程模型。如下:

如图所示,每个线程最终都会映射到CPU中进行处理,如果CPU存在多核,那么一个CPU将可以并行执行多个线程任务。

6-2-2 Java内存模型与硬件内存架构的关系

多线程的执行最终都会映射到硬件处理器上进行执行,但Java内存模型和硬件内存架构并不完全一致。
对于硬件内存来说只有寄存器、缓存内存、主内存的概念,并没有工作内存(线程私有数据区域)和主内存(堆内存)之分,也就是说Java内存模型对内存的划分对硬件内存并没有任何影响,因为JMM只是一种抽象的概念,是一组规则,并不实际存在。
不管是工作内存的数据还是主内存的数据,对于计算机硬件来说都会存储在计算机主内存中,当然也有可能存储到CPU缓存或者寄存器中,因此总体上来说,Java内存模型和计算机硬件内存架构是一个相互交叉的关系,是一种抽象概念划分与真实物理硬件的交叉。(注意对于Java内存区域划分也是同样的道理)

7 JMM存在的必要性

由于JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存(有些地方称为栈空间),用于存储线程私有的数据,线程与主内存中的变量操作必须通过工作内存间接完成,主要过程是将变量从主内存拷贝的每个线程各自的工作内存空间,然后对变量进行操作,操作完成后再将变量写回主内存。
如果存在两个线程同时对一个主内存中的实例对象的变量进行操作就有可能诱发线程安全问题。
为了解决这种问题,JVM定义了一组规则,通过这组规则来决定一个线程对共享变量的写入何时对另一个线程可见,这组规则也称为Java内存模型(即JMM)
JMM是围绕着程序执行的原子性、有序性、可见性展开的,下面我们看看这三个特性。

7-1 原子性:

原子性指的是一个操作是不可中断的,即使是在多线程环境下,一个操作一旦开始就不会被其他线程影响。比如对于一个静态变量int x,两条线程同时对他赋值,线程A赋值为1,而线程B赋值为2,不管线程如何运行,最终x的值要么是1,要么是2,线程A和线程B间的操作是没有干扰的,这就是原子性操作,不可被中断的特点。
有点要注意的是,对于32位系统的来说,long类型数据和double类型数据(对于基本数据类型,byte,short,int,float,boolean,char读写是原子操作),它们的读写并非原子性的,也就是说如果存在两条线程同时对long类型或者double类型的数据进行读写是存在相互干扰的,因为对于32位虚拟机来说,每次原子读写是32位的,而long和double则是64位的存储单元,这样会导致一个线程在写时,操作完前32位的原子操作后,轮到B线程读取时,恰好只读取到了后32位的数据,这样可能会读取到一个既非原值又不是线程修改值的变量,它可能是“半个变量”的数值,即64位数据被两个线程分成了两次读取。但也不必太担心,因为读取到“半个变量”的情况比较少见,至少在目前的商用的虚拟机中,几乎都把64位的数据的读写操作作为原子操作来执行,因此对于这个问题不必太在意,知道这么回事即可。

7-2 指令重排:

7-2-1 内容:

计算机在执行程序时,为了提高性能,编译器和处理器的常常会对指令做重排。

7-2-2 一般分以下3种

  • 编译器优化的重排(编译期重排)
    编译器在不改变线程程序的语义下,可以重新安排语义的执行顺序。
  • 指令并行的重排(处理器重排)
    现代处理器采用了指令级并行技术来将多条指令重叠执行。如果不存在数据依赖性(即后一个执行的语句无需依赖前面执行的语句的结果),处理器可以改变语句对应的机器指令的执行顺序。
  • 内存系统的重排(处理器重排)
    由于处理器使用缓存和读写缓存冲区,这使得加载(load)和存储(store)操作看上去可能是在乱序执行,因为三级缓存的存在,导致内存与缓存的数据同步存在时间差。

7-2-3 指令重排带来的问题

编译器重排

线程 1             线程 2
1: x2 = a ;       3: x1 = b ;
2: b = 1;         4: a = 2 ;

两个线程同时执行,分别有1、2、3、4四段执行代码,其中1、2属于线程1 , 3、4属于线程2 。从程序的执行顺序上看,似乎不太可能出现x1 = 1 和x2 = 2 的情况,但实际上这种情况是有可能发现的,因为如果编译器对这段程序代码执行重排优化后,可能出现下列情况:

线程 1              线程 2
2: b = 1;          4: a = 2 ;
1:x2 = a ;        3: x1 = b ;

这种执行顺序下就有可能出现x1 = 1 和x2 = 2 的情况,这也就说明在多线程环境下,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性是无法确定的。

处理器指令重排

处理器指令重排是对CPU的性能优化,从指令的执行角度来说一条指令可以分为多个步骤完成,如下:

  • 取址 IF
  • 译码和取寄存器操作数 ID
  • 执行或者有效地址计算 EX
  • 存储器访问 MEM
  • 写回 WB

CPU在工作时,需要将上述指令分为多个步骤依次执行(注意硬件不同有可能不一样)。由于每一个步会使用到不同的硬件操作,比如取指时会只有PC寄存器存储器,译码时会执行到指令寄存器组,执行时会执行ALU(算术逻辑单元)、写回时使用到寄存器组。为了提高硬件利用率,CPU指令是按流水线技术来执行的,如下:

虽然流水线技术可以大大提升CPU的性能,但不幸的是一旦出现流水中断,所有硬件设备将会进入一轮停顿期,当再次弥补中断点可能需要几个周期,这样性能损失也会很大,就好比工厂组装手机的流水线,一旦某个零件组装中断,那么该零件往后的工人都有可能进入一轮或者几轮等待组装零件的过程。因此我们需要尽量阻止指令中断的情况,指令重排就是其中一种优化中断的手段,我们通过一个例子来阐明指令重排是如何阻止流水线技术中断的:

a = b + c ;
d = e + f ;

  • LW指令 表示 load,其中LW R1,b表示把b的值加载到寄存器R1中
  • LW R2,c 表示把c的值加载到寄存器R2中
  • ADD 指令表示加法,把R1 、R2的值相加,并存入R3寄存器中。
  • SW 表示 store 即将 R3寄存器的值保持到变量a中
  • LW R4,e 表示把e的值加载到寄存器R4中
  • LW R5,f 表示把f的值加载到寄存器R5中
  • SUB 指令表示减法,把R4 、R5的值相减,并存入R6寄存器中。
  • SW d,R6 表示将R6寄存器的值保持到变量d中

上述便是汇编指令的执行过程,在某些指令上存在X的标志,X代表中断的含义,也就是只要有X的地方就会导致指令流水线技术停顿,同时也会影响后续指令的执行,可能需要经过1个或几个指令周期才可能恢复正常,那为什么停顿呢?
这是因为部分数据还没准备好,如执行ADD指令时,需要使用到前面指令的数据R1,R2,而此时R2的MEM操作没有完成,即未拷贝到存储器中,这样加法计算就无法进行,必须等到MEM操作完成后才能执行,也就因此而停顿了,其他指令也是类似的情况。
停顿会造成CPU性能下降,因此我们应该想办法消除这些停顿,这时就需要使用到指令重排了。如下图,既然ADD指令需要等待,那我们就利用等待的时间做些别的事情,如把LW R4,eLW R5,f移动到前面执行,毕竟LW R4,eLW R5,f执行并没有数据依赖关系,对他们有数据依赖关系的SUB R6,R5,R4指令在R4,R5加载完成后才执行的,没有影响,过程如下:

正如上图所示,所有的停顿都完美消除了,指令流水线也无需中断了,这样CPU的性能也能带来很好的提升,这就是处理器指令重排的作用。关于编译器重排以及指令重排(这两种重排我们后面统一称为指令重排)相关内容已阐述清晰了,我们必须意识到对于单线程而言指令重排几乎不会带来任何影响,毕竟重排的前提是保证串行语义执行的一致性,但对于多线程环境而已,指令重排就可能导致严重的程序轮序执行问题,如下:

class MixedOrder{int a = 0;boolean flag = false;public void writer(){a = 1;flag = true;}public void read(){if(flag){int i = a + 1;}}
}

如上述代码,同时存在线程A和线程B对该实例对象进行操作,其中A线程调用写入方法,而B线程调用读取方法,由于指令重排等原因,可能导致程序执行顺序变为如下:

 线程A                    线程Bwriter:                 read:1:flag = true;           1:flag = true;2:a = 1;                 2: a = 0 ; //误读3: i = 1 ;

由于指令重排的原因,线程A的flag置为true被提前执行了,而a赋值为1的程序还未执行完,此时线程B,恰好读取flag的值为true,直接获取a的值(此时B线程并不知道a为0)并执行i赋值操作,结果i的值为1,而不是预期的2,这就是多线程环境下,指令重排导致的程序乱序执行的结果。
因此,请记住,指令重排只会保证单线程中串行语义的执行的一致性,但并不会关心多线程间的语义一致性。

7-3 可见性:

理解了指令重排现象后,可见性容易了,可见性指的是当一个线程修改了某个共享变量的值,其他线程是否能够马上得知这个修改的值。对于串行程序来说,可见性是不存在的,因为我们在任何一个操作中修改了某个变量的值,后续的操作中都能读取这个变量值,并且是修改过的新值。
但在多线程环境中可就不一定了,前面我们分析过,由于线程对共享变量的操作都是线程拷贝到各自的工作内存进行操作后才写回到主内存中的,这就可能存在一个线程A修改了共享变量x的值,还未写回主内存时,另外一个线程B又对主内存中同一个共享变量x进行操作,但此时A线程工作内存中共享变量x对线程B来说并不可见,这种工作内存与主内存同步延迟现象就造成了可见性问题,另外指令重排以及编译器优化也可能导致可见性问题,通过前面的分析,我们知道无论是编译器优化还是处理器优化的重排现象,在多线程环境下,确实会导致程序轮序执行的问题,从而也就导致可见性问题。

7-4 有序性:

有序性是指对于单线程的执行代码,我们总是认为代码的执行是按顺序依次执行的,这样的理解并没有毛病,毕竟对于单线程而言确实如此。
但对于多线程环境,则可能出现乱序现象,因为程序编译成机器码指令后可能会出现指令重排现象,重排后的指令与原指令的顺序未必一致,要明白的是,在Java程序中,倘若在本线程内,所有操作都视为有序行为,如果是多线程环境下,一个线程中观察另外一个线程,所有操作都是无序的,前半句指的是单线程内保证串行语义执行的一致性,后半句则指指令重排现象和工作内存与主内存同步延迟现象。
JMM提供的解决方案

7-5 JMM提供的解决方案:

在理解了原子性,可见性以及有序性问题后,看看JMM是如何保证的,在Java内存模型中都提供一套解决方案供Java工程师在开发过程使用。

  • 原子性问题
    除了JVM自身提供的对基本数据类型读写操作的原子性外,对于方法级别或者代码块级别的原子性操作,可以使用synchronized关键字或者重入锁(ReentrantLock)保证程序执行的原子性
  • 可见性问题
    而工作内存与主内存同步延迟现象导致的可见性问题,可以使用synchronized关键字或者volatile关键字解决,它们都可以使一个线程修改后的变量立即对其他线程可见。
    对于指令重排导致的可见性问题和有序性问题,则可以利用volatile关键字解决,因为volatile的另外一个作用就是禁止重排序优化。除了靠sychronized和volatile关键字来保证原子性、可见性以及有序性外,JMM内部还定义一套happens-before 原则来保证多线程环境下两个操作间的原子性、可见性以及有序性。
  • 程序顺序原则,即在一个线程内必须保证语义串行性,也就是说按照代码顺序执行。
  • 锁规则 解锁(unlock)操作必然发生在后续的同一个锁的加锁(lock)之前,也就是说,如果对于一个锁解锁后,再加锁,那么加锁的动作必须在解锁动作之后(同一个锁)。
  • volatile规则 volatile变量的写,先发生于读,这保证了volatile变量的可见性,简单的理解就是,volatile变量在每次被线程访问时,都强迫从主内存中读该变量的值,而当该变量发生变化时,又会强迫将最新的值刷新到主内存,任何时刻,不同的线程总是能够看到该变量的最新值。
  • 线程启动规则 线程的start()方法先于它的每一个动作,即如果线程A在执行线程B的start方法之前修改了共享变量的值,那么当线程B执行start方法时,线程A对共享变量的修改对线程B可见。
  • 传递性 A先于B ,B先于C 那么A必然先于C。
  • 线程终止规则 线程的所有操作先于线程的终结,Thread.join()方法的作用是等待当前执行的线程终止。假设在线程B终止之前,修改了共享变量,线程A从线程B的join方法成功返回后,线程B对共享变量的修改将对线程A可见。
  • 线程中断规则 对线程 interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过Thread.interrupted()方法检测线程是否中断。
  • 对象终结规则 对象的构造函数执行,结束先于finalize()方法。
    上述8条原则无需手动添加任何同步手段(synchronized|volatile)即可达到效果,下面我们结合前面的案例演示这8条原则如何判断线程是否安全,如下:
class MixedOrder{int a = 0;boolean flag = false;public void writer(){a = 1;flag = true;}public void read(){if(flag){int i = a + 1;}}
}

存在两条线程A和B,线程A调用实例对象的writer()方法,而线程B调用实例对象的read()方法。线程A先启动而线程B后启动,那么线程B读取到的i值是多少呢?现在依据8条原则,由于存在两条线程同时调用,因此程序次序原则不合适。writer()方法和read()方法都没有使用同步手段,锁规则也不合适。没有使用volatile关键字,volatile变量原则不适应。线程启动规则、线程终止规则、线程中断规则、对象终结规则、传递性和本次测试案例也不合适。
线程A和线程B的启动时间虽然有先后,但线程B执行结果却是不确定,也是说上述代码没有适合8条原则中的任意一条,也没有使用任何同步手段,所以上述的操作是线程不安全的。
因此线程B读取的值自然也是不确定的。修复这个问题的方式很简单,要么给writer()方法和read()方法添加同步手段,如synchronized或者给变量flag添加volatile关键字,确保线程A修改的值对线程B总是可见。

7-6 理解JMM中的 happens-before 原则:

倘若在程序开发中,仅靠sychronized和volatile关键字来保证原子性、可见性以及有序性,那么编写并发程序可能会显得十分麻烦,幸运的是,在Java内存模型中,还提供了happens-before 原则来辅助保证程序执行的原子性、可见性以及有序性的问题。它是判断数据是否存在竞争、线程是否安全的依据,happens-before 原则内容如下:

提问:

1 JVM最开始各个区域的内存量是固定的吗?

是固定的。

public class Demo1 {public static void main(String[] args) {//返回JVM试图使用的最大内存Long max = Runtime.getRuntime().maxMemory(); // 字节 1024 * 1024//返回JVM的总内存Long total = Runtime.getRuntime().totalMemory();System.out.println("max = "  + max + "字节\t\t" + (max/(double)1024/1024) + "MB\t" + (max/(double)1024/1024/1024) + "G");System.out.println("total = "  + total + "字节\t" + (total/(double)1024/1024) + "MB\t\t" + (total/(double)1024/1024/1024) + "G");}
}
输出:
max = 1866465280字节     1780.0MB    1.73828125G
total = 126877696字节    121.0MB     0.1181640625G
  • 结论:
    默认情况下,分配的JVM总内存,max = 电脑内存的1/4
    初始化的内存,total = 1/64

开心一刻:

在日常英语中,stack和heap二者都指堆积(动词)和一堆(名词),但是

  • heap 通常指杂乱的、呈小山状的一堆东西,如:Now, the house is a heap of rubble(现在,房子成了一堆瓦砾)。
  • stack通常是整齐的一叠,指扁平物体叠放起来,如:a neat stack of dishes(整齐的一叠盘子)。

参考文章:

  • CSDN:

    • JMM : https://blog.csdn.net/belongtocode/article/details/113918720
    • 本地方法栈:https://blog.csdn.net/weixin_43893397/article/details/121899679

参考视频:

  • B站:

    • JVM : https://www.bilibili.com/video/BV1iJ411d7jS?p=14&spm_id_from=333.880.my_history.page.click
    • JVM : https://www.bilibili.com/video/BV1sp4y1Y7ap?p=4&spm_id_from=333.880.my_history.page.click

JVM架构、JVM垃圾回收机制、垃圾回收算法、垃圾回收器、JMM(内存模型)相关推荐

  1. 垃圾回收机制之复制算法

    我再说一下什么是磁盘碎片化,磁盘碎片化表示删不干净,比如我格式化磁盘,肯定效率高一点,而且删的干净一点,如果一个一个的删除的话,容易产生碎片,有一部分没有被删除掉,你们可以下去自己做一个例子,有的时候 ...

  2. 深入理解并发内存模型||JMM与内存屏障||多核并发缓存架构 ||JMM内存模型||volatile 关键字的作用 ||JMM 数据原子操作||JMM缓存不一致的问题

    深入理解并发内存模型||JMM与内存屏障 多核并发缓存架构 JMM内存模型 volatile 关键字的作用 JMM 数据原子操作 JMM缓存不一致的问题

  3. jvm垃圾回收机制和常见算法

    这是朋友给的面试题里边的,具体地址已经找不到,只能对原作者说声抱歉了: 理论上来讲sun公司只定义了垃圾回收机制规则,而步局限于其实现算法,因此不同厂商生产的虚拟机采用的算法也不尽相同. GC(Gar ...

  4. JVM 垃圾回收机制和常见算法

    理论上来讲 Sun 公司只定义了垃圾回收机制规则而不局限于其实现算法,因此不同厂商生产的虚拟机采用的算法也不尽相同. GC(Garbage Collector)在回收对象前首先必须发现那些无用的对象, ...

  5. 【JVM进阶之路】垃圾回收机制和GC算法之三色标记(三)

    JVM往期文章 [JVM进阶之路]内存结构(一) [JVM进阶之路]玩转JVM中的对象(二) 上篇文章中讲到JVM中的对象以及判断对象的存活,那么对于"已死"的对象应该如何处理,怎 ...

  6. JVM虚拟机(四):JVM 垃圾回收机制概念及其算法

    垃圾回收概念和其算法 谈到垃圾回收(Garbage Collection)GC,需要先澄清什么是垃圾,类比日常生活中的垃圾,我们会把他们丢入垃圾箱,然后倒掉.GC中的垃圾,特指存于内存中.不会再被使用 ...

  7. java垃圾回收机制_笔记 | Java垃圾回收机制

    本文经授权转载自程序员杂货铺(ID:speakFramework) 垃圾回收 最近上海的小伙伴是不是要被强垃圾分类搞疯了???哈哈哈哈 上海是个走在前列的城市啊,不光骑自行车闯红灯要被罚钱,垃圾不分类 ...

  8. 【Java 虚拟机原理】垃圾回收算法 ( Java 虚拟机内存分区 | 垃圾回收机制 | 引用计数器算法 | 引用计数循环引用弊端 )

    文章目录 一.Java 虚拟机内存分区 二.垃圾回收机制 三.引用计数器算法 ( 无法解决循环引用问题 ) 一.Java 虚拟机内存分区 Java 虚拟机内存分区 : 所有线程共有的内存区域 : 堆 ...

  9. java垃圾回收机制串行_Java垃圾回收机制

    Java语言是一门自动内存管理的语言,不再需要的对象可以通过垃圾回收自动进行内存释放. Java运行时内存区域划分 JVM将Java程序运行时内存区域划分成以下几个部分: 程序计数器(Program ...

  10. java gc回收机制种类_JAVA的垃圾回收机制(GC)

    1.什么是垃圾回收? 垃圾回收(Garbage Collection)是Java虚拟机(JVM)垃圾回收器提供的一种用于在空闲时间不定时回收无任何对象引用的对象占据的内存空间的一种机制. 2.什么时候 ...

最新文章

  1. 构建之法第十,十一,十二章阅读
  2. ip integrator_使用Oracle Data Integrator(和Kafka / MapR流)完善Lambda体系结构
  3. 面试官系统精讲Java源码及大厂真题 - 11 HashSet、TreeSet 源码解析
  4. Git如何从众多提交中保留个别提交
  5. mysql 自增长改动_优化了MYSQL大量写入问题,老板奖励了1000块给我
  6. 转载:常用CSS缩写语法总结
  7. python中读取和保存图片的方法对比
  8. 用计算机计算三角函数角度,角度计算器
  9. 全网最全的AItium Designer 16下载资源与安装步骤
  10. Android基础学习(十五)—— 序列化与反序列化(包括 JSON、GSON))
  11. 数据分析初试:“梧桐杯”中国移动大数据应用创新大赛 - 智慧城市赛道
  12. 谷歌浏览器 Google Chrome v74.0.3729.131 正式版
  13. Auto.js实例找色
  14. 单片机开发:不得不了解的看门狗电路
  15. 非线性方程求根方法总结附代码(从二分法、试位法到牛顿迭代、二次插值等)
  16. Spring boot、Spring cloud深度技术集锦
  17. 深度学习——YOLO模型的原理与实战
  18. nacos服务端报 failed to update serviceName: localhost
  19. C语言模拟图书馆管理系统
  20. RecyclerView自定义动画,item飞入,渐隐渐出-[Android_YangKe]

热门文章

  1. 注入修改代码,白嫖按键精灵ios手机版
  2. 科维的时间管理法—《可以量化的管…
  3. 【BackEnd--SSM 框架详解】Mybatis+Spring+SpringMVC学习笔记(完整详细版)
  4. Html-照片的逐步出现 、心形动画制作、3d立方体魔方、鼠标划过box阴影练习
  5. 零点城市社交电商2.1.7.4 VUE全端+全开源+全插件+独立版
  6. 苹果放弃Intel基带应是大概率事件
  7. 全国各地电台FM.ini汇总
  8. python实现截取excel中的表格,生成图片
  9. python语言程序设计习题
  10. ESP8266 alios things 自带 linkkitapp OTA 更新失败