运行时数据区

·hotspot虚拟机
在这里插入图片描述
在这里插入图片描述

Java虚拟机定义了若干种程序运行期间会使用到的运行时数据区,其中有一些会随着虚拟机启动而创建,随着虚拟机退出而销毁。另外一些则是与线程一一对应的,这些与线程对应的数据区域会随着线程开始和结束而创建和销毁。灰色的为单独线程私有的,红色的为多个线程共享的。即:

  • 每个线程:独立包括程序计数器、栈、本地栈。
  • 线程间共享:堆、堆外内存(永久代或元空间、代码缓存)

JDK1.8运行时数据区结构图:
在这里插入图片描述
每个JVM只有一个Runtime实例。即为运行时环境,相当于内存结构的中间的那个框框:运行时环境。

1、线程

  • 线程是一个程序里的运行单元。JVM允许一个应用有多个线程并行的执行。
  • 在Hotspot JVM里,每个线程都与操作系统的本地线程直接映射。
    当一个Java线程准备好执行以后,此时一个操作系统的本地线程也同时创建。Java线程执行终止后,本地线程也会回收。
  • 操作系统负责所有线程的安排调度到任何一个可用的CPU上。一旦本地线程初始化成功,它就会调用Java线程中的run()方法。

JVM系统线程:
1、虚拟机线程:这种线程的操作是需要JVM达到安全点才会出现。这些操作必须在不同的线程中发生的原因是他们都需要JVM达到安全点,这样堆才不会变化。这种线程的执行类型包括”stop-the-world"的垃圾收集,线程栈收集,线程挂起以及偏向锁撤销。
2、周期任务线程:这种线程是时间周期事件的体现(比如中断),他们一-般用于周期性操作的调度执行。
3、GC线程:这种线程对在JVM里不同种类的垃圾收集行为提供了支持。
4、编译线程:这种线程在运行时会将字节码编译成到本地代码。
5、信号调度线程:这种线程接收信号并发送给JVM,在它内部通过调用适当的方法进行外理。

2、程序计数器(PC寄存器)

  • 它是一块很小的内存空间,几乎可以忽略不记。也是运行速度最快的存储区域。.
  • 在JVM规范中, 每个线程都有它自己的程序计数器,是线程私有的,生命周期与线程的生命周期保持一致。
  • 任何时间一个线程都只有一 一个方法在执行,也就是所谓的当前方法。程序计数器会存储当前线程正在执行的Java方法的JVM指令地址;或者,如果是在执行native方法,则是未指定值(undefned)。

在这里插入图片描述
作用: PC寄存器用来存储指向下一条指令的地址,也即将要执行的指令代码。由执行引擎读取下一条指令
在这里插入图片描述
面试问题:
1、使用PC寄存器存储字节码指令地址有什么用呢?(为什么使用PC寄存器记录当前线程的执行地址呢?)
答:因为CPU需要不停的切换各个线程,这时候切换回来以后,就得知道接着从哪开始继续执行。
在这里插入图片描述
2、PC寄存器为什么会被设定为线程私有

答:为了能够准确地记录各个线程正在执行的当前字节码指令地址,最好的办法自然是为每-一个线程都分配一个PC寄存器,这样一来各个线程之间便可以进行独立计算

3、虚拟机栈

3.1虚拟机栈出现的背景

由于跨平台性的设计,Java的指令都是根据栈来设计的。不同平台CPU架构不同,所以不能设计为基于寄存器的。
优点是跨平台,指令集小,编译器容易实现,缺点是性能下降,实现同样的功能需要更多的指令。

3.2内存中的栈和堆

栈是运行时的单位,而堆是存储的单位
即:栈解决程序的运行问题,即程序如何执行,或者说如何处理数据。堆解决的是数据存储的问题,即数据怎么放、放在哪儿。

3.3虚拟机栈基本内容

3.3.1Java虛拟机栈是什么?

Java虚拟机栈(Java Virtual Machine Stack) ,早期也叫Java栈。每个线程在创建时都会创建一个虚拟机栈,其内部保存一个个的栈帧(Stack Frame) ,对应着一次次的Java方法调用。是线程私有的。

3.3.2生命周期

生命周期和线程一致。

3.3.3作用

主管Java程序的运行,它保存方法的局部变量(8种基本数据类型、对象的引用地址)、部分结果,并参与方法的调用和返回。

3.3.4栈的特点(优点)

  • 栈是一种快速有效的分配存储方式,访问速度仅次于程序计数器。
  • JVM直接对Java栈的操作只有两个:
    每个方法执行,伴随着进栈(入栈、压栈)
    执行结束后的出栈工作
  • 对于栈来说不存在垃圾回收问题存在溢出问题
    在这里插入图片描述
3.3.5栈中可能出现的异常

Java 虚拟机规范允许Java栈的大小是动态的或者是固定不变的。
1、如果采用固定大小的Java虚拟机栈,那每一个线程的Java虚拟机栈容量可以在线程创建的时候独立选定。如果线程请求分配的栈容量超过Java虚拟机栈允许的最大容量,Java虚拟机将 会抛出一个StackOverflowError异常。
2、如果Java虚拟机栈可以动态扩展,并且在尝试扩展的时候无法申请到足够的内存,或者在创建新的线程时没有足够的内存去创建对应的虚拟机栈,那Java虚拟机将会抛出一个OutOfMemoryError 异常。

3.3.6设置栈内存大小

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

3.4栈的存储单位

3.4.1栈中存储什么?
  • 每个线程都有自己的栈,栈中的数据都是以栈帧(Stack Frame)的格式存在。
  • 在这个线程上正在执行的每个方法都各自对应一个栈帧(Stack Frame) 。
  • 栈帧是一个内存区块,是一个数据集,维系着方法执行过程中的各种数据信息。

3.5栈的运行原理

  • JVM直接对Java栈的操作只有两个,就是对栈帧的压栈和出栈,遵循“先进后出”/“后进先出”原则。
  • 在一条活动线程中,一个时间点上,只会有一个活动的栈帧。即只有当前正在执行的方法的栈帧(栈项栈帧)是有效的,这个栈帧被称为当前栈帧(Current Frame) ,与当前栈帧相对应的方法就是当前方法(Current(Method),定义这个方法的类就是当前类(Current Class)。
  • 执行引擎运行的所有字节码指令只针对当前栈帧进行操作。
  • 如果在该方法中调用了其他方法,对应的新的栈帧会被创建出来,放在栈的顶端,成为新的当前帧。
  • 不同线程中所包含的栈帧是不允许存在相互引用的,即不可能在一个栈帧之中引用另外一个线程的栈帧。
  • 如果当前方法调用了其他方法,方法返回之际,当前栈帧会传回此方法的执行结果给前一个栈帧,接着,虚拟机会丢弃当前栈帧,使得前一个栈帧重新成为当前栈帧。
  • Java方法有两种返回函数的方式,一种是正常的函数返回,使用return指令;另外一种是抛出异常。不管使用哪种方式,都会导致栈帧被弹出。

在这里插入图片描述

3.6栈帧的内部结构

每个栈帧中存储着:

  • 局部变量表(Local variables)
  • 操作数栈(operand Stack) ( 或表达式栈)
  • 动态链接(Dynamic Linking) ( 或指向运行时常量池的方法引用)
  • 方法返回地址(Return Address) ( 或方法正常退出或者异常退出的定义)
  • 一些附加信息
    在这里插入图片描述
3.6.1 局部变量表
  • 局部变量表也被称之为局部变量数组或本地变量表

  • 定义为一个数字数组,主要用于存储方法参数和定义在方法体内的局部变量,这些数据类型包括各类基本数据类型、对象引用(reference) ,以及returnAddress类型。

  • 由于局部变量表是建立在线程的栈上,是线程的私有数据,因此不存在数据安全问题

  • 局部变量表所需的容量大小是在编译期确定下来的,并保存在方法的Code属性的maximum local variables数据项中。在方法运行期间是不会改变局部变量表的大小的。

  • 对一个函数而言,它的参数和局部变量越多,使得局部变量表膨胀,它的栈帧就越大,以满足方法调用所需传递的信息增大的需求。进而函数调用就会占用更多的栈空间,导致其嵌套调用次数就会减少。

  • 局部变量表中的变量只在当前方法调用中有效。在方法执行时,虚拟机通过使用局部变量表完成参数值到参数变量列表的传递过程。当方法调用结束后,随着方法栈帧的销毁,局部变量表也会随之销毁。

关于Slot的理解:

  • 参数值的存放总是在局部变量数组的index0开始,到数组长度-1的索引结束。

  • 局部变量表, 最基本的存储单元是Slot (变量槽)

  • 局部变量表中 存放编译期可知的各种基本数据类型(8种),引用类型(reference),returnAddress类型的变量。

  • 在局部变量表里,32位以内的类型只占用一个slot (包括:returnAddress类型),64位的类型(long和double) 占用两个slot。
    byte、short 、char在存储前被转换为int,boolean 也被转换为int,0表示false,非0表示true。
    long和double 则占据两个Slot。

在这里插入图片描述

  • JVM会为局部变量表中的每-一个Slot都分配-一个访问索引,通过这个素引即可成功访问到局部变量表中指定的局部变量值;
  • 如果当前帧是由构造方法或者实例方法创建的,那么该对象引用this将会存放在index为0的slot处,其余的参数按照参数表顺序继续排列。
  • 如果需要访问局部变量表中一个64bit的局部变量值时,只需要使用前一个索引即可。(比如:访问Long或double类型变量)
  • 栈帧中的局部变量表中的槽位是可以重用的,如果一个局部变量过了其作用域,那么在其作用域之后申明的新的局部变量就很有可能会复用过期局部变量的槽位,从而达到节省资源的目的。

3.6.2、操作数栈(用数组实现)

  • 每一个独立的栈帧中除了包含局部变量表以外,还包含一个后进先出(Last-In-First-Out)的操作数栈,也可以称之为表达式栈(Expression Stack)
  • 操作数栈,在方法执行过程中,根据字节码指令,往栈中写入数据或提取数据,即入栈(push) /出栈(pop)。
  • 操作数栈,主要用于保存计算过程的中间结果,同时作为计算过程中变量临时的存储空间。
  • 操作数栈就是JVM执行引擎的一个工作区,当一个方法刚开始执行的时候,一个新的栈帧也会随之被创建出来,这个方法的操作数栈是空的。
  • 每一个操作数栈都会拥有一个明确的栈深度用于存储数值,其所需的最大深度在编译期就定义好了,保存在方法的Code属性中,为max stack的值。
  • 栈中的任何一个元素都是可以任意的Java数据类型。
    32bit的类型占用一个栈单位深度
    64bit的类型占用两个栈单位深度
  • 操作数栈并非采用访问索引的方式来进行数据访问的,而是只能通过标准的入栈(push) 和出栈(pop) 操作来完成一次数据访问。 .
  • 如果被调用的方法带有返回值的话,其返回值将会被压入当前栈帧的操作数栈中,并更新PC寄存器中下一条需要执行的字节码指令。
  • 操作数栈中元素的数据类型必须与字节码指令的序列严格匹配,这由编译器在编译器期间进行验证,同时在类加载过程中的类检验阶段的数据流分析阶段要再次验证。
  • 另外,我们说Java虚拟机的解释引擎是基于栈的执行引擎,其中的栈指的就是操作数栈。

栈顶缓存技术

基于栈式架构的虛拟机所使用的零地址指令更加紧凑,但完成一项操作的时候必然需要使用更多的入栈和出栈指令。
由于操作数是存储在内存中的,因此频繁地执行内存读/写操作必然会影响执行速度。为了解决这个问题,HotSpot JVM的设计者们提出了栈顶,缓存(ToS,Top-of-Stack Cashing)技术,将栈顶元素全部缓存在物理CPU的寄存器中,以此降低对内存的读/写次数,提升执行引擎的执行效率。

3.6.3动态链接(或指向运行时常量池的方法引用)

  • 每一个栈帧内部都包含一个指向运行时常量池中该栈帧所属方法的引用。包含这个引用的目的就是为了支持当前方法的代码能够实现动态链接(Dynamic Linking)。比如: invokedynamic指 令
  • 在Java源文件被编译到字节码文件中时,所有的变量和方法引用都作为符号引用(Symbolic Reference) 保存在class文件的常量池里。比如:描述一一个方法调用了另外的其他方法时,就是通过常量池中指向方法的符号引用来表示的,那么动态链接的作用就是为了将这些符号引用转换为调用方法的直接引用
    在这里插入图片描述

方法的调用
在JVM中,将符号引用转换为调用方法的直接引用与方法的绑定机制相关。

  • 静态链接:早期绑定
    当一个字节码文件被装载进JVM内部时,如果被调用的目标方法在编译期可知,且运行期保持不变时。这种情况下将调用方法的符号引用转换为直接引用的过程称之为静态链接。
  • 动态链接:晚期绑定
    如果被调用的方法在编译期无法被确定下来,也就是说,只能够在程序运行期将调用方法的符号引用转换为直接引用,由于这种引用转换过程具备动态性,因此也就被称之为动态链接。

非虚方法

  • 如果方法在编译期就确定了具体的调用版本,这个版本在运行时是不可变的。这样的方法称为非虚方法。
  • 静态方法、私有方法、final方法、实例构造器、父类方法都是非虚方法。
  • 其他方法称为虚方法。.
  • invokestatic指令和invokespecial指令调用的方法称为非虚方法

3.6.4方法返回地址

  • 存放调用该方法的pc寄存器的值。

  • 一个方法的结束,有两种方式:
    正常执行完成
    出现未处理的异常,非正常退出

  • 无论通过哪种方式退出,在方法退出后都返回到该方法被调用的位置。方法正常退出时,调用者的pc计数器的值作为返回地址,即调用该方法的指令的下一条指令的地址。而通过异常退出的,返回地址是要通过异常表来确定,栈帧中一般不会保存这部分信息。

  • 本质上,方法的退出就是当前栈帧出栈的过程。此时,需要恢复上层方法的局部变量表、操作数栈、将返回值压入调用者栈帧的操作数栈、设置PC寄存器值等,让调用者方法继续执行下去。

  • 正常完成出口和异常完成出口的区别在于:通过异常完成出口退出的不会给他的上层调用者产生任何的返回值。

4、堆

  • 一个JVM实例只存在一个堆内存,堆也是Java内存管理的核心区域。

  • Java堆区在JVM启动的时候即被创建,其空间大小也就确定了。是JVM管理的最大一块内存空间。堆内存的大小是可以调节的(-Xms -Xmx)。

  • 《Java虛拟机规范》规定,堆可以处于物理上不连续的内存空间中,但在逻辑上它应该被视为连续的。

  • 所有的线程共享Java堆,在这里还可以划分线程私有的缓冲区(Thread Local Allocation Buffer, TLAB) 。

  • 《Java虚拟机规范》中对Java堆的描述是:所有的对象实例以及数组都应当在运行时分配在堆上。(The heap is the run-time data area fromwhich memory for all class instances and arrays is al located )
    我要说的是:“几乎”所有的对象实例都在这里分配内存。——从实际使用角度看的。

  • 数组和对象可能永远不会存储在栈上,因为栈帧中保存引用,这个引用指向对象或者数组在堆中的位置。

  • 在方法结束后,堆中的对象不会马上被移除,仅仅在垃圾收集的时候才会被移除。

  • 堆,是GC ( Garbage Collection, 垃圾收集器)执行垃圾回收的重点区域

  • -Xms用来设置堆空间(年轻代+老年代)的初始内存大小
    -X是jvm的运行参数 ,ms是memory start

  • -Xmx用来设置堆空间(年轻代+老年代)的最大内存大小

  • 查看设置的参数:方式一: jps / jstat -gc 进程id
    方式二: -XX:+PrintGCDetails

  • OutOfMemoryError 堆内存空间不够时异常

4.1堆的内存细分

在这里插入图片描述
现代垃圾收集器大部分都基于分代收集理论设计,堆空间细分为:
在这里插入图片描述

4.1年轻代与老年代

  • Java堆区进一步细分的话, 可以划分为年轻代(YoungGen) 和老年代(OldGen)
  • 其中年轻代又可以划分为Eden空间、Survivor0空间和Survivor1空间(有时也叫做from区、to区)。

在这里插入图片描述

  • 配置新生代与老年代在堆结构的占比。
    默认-XX:NewRatio=2,表示新生代占1,老年代占2,新生代占整个堆的1/3
    可以修改-XX:NewRatio=4,表示新生代占1,老年代占4,新生代占整个堆的1/5
  • 在HotSpot中,Eden空间和另外两个Survivor空间缺省所占的比例是8:1:1(实际为6 :1:1)
  • 当然开发人员可以通过选项“-XX:SurvivorRatio"调整这个空间比例。比如-XX :SurvivorRatio=8
  • 几乎所有的Java对象都是在Eden区被new出来的。
  • 绝大部分的Java对象的销毁都在新生代进行了

4.2对象分配过程

为新对象分配内存是一件非 常严谨和复杂的任务,JVM的设计 者们不仅需要考虑内存如何分配、在哪里分配等问题,并且由于内存分配算法与内存回收算法密切相关,所以还需要考虑GC执行完内存回收后是否会在内存空间中产生内存碎片。

  1. new的对象先放伊甸园区。此区有大小限制。
  2. 当伊甸园的空间填满时,程序又需要创建对象,JVM的垃圾回收器将对伊甸园区进行垃圾回收(Minor GC/YGC), 将伊甸园区中的不再被其他对象所引用的对象进行销毁。再加载新的对象放到伊甸园区
  3. 然后将伊甸园中的剩余对象移动到幸存者0区,此对象的年龄加1,此时幸存者1区为空。
  4. 如果再次触发垃圾回收,此时上次幸存下来的放到幸存者0区的,如果没有回收,就会
    放到幸存者1区,年龄再加1,此时幸存者0区为空。
  5. 如果再次经历垃圾回收,此时会重新放回幸存者0区,接着再去幸存者1区。
  6. 啥时候能去养老区呢?可以设置次数(年龄)。默认是15次。
    可以设置参数: -XX:MaxTenuringThreshold= 值(年龄) 进行设置。

在这里插入图片描述
针对幸存者s0,s1区的总结:复制之后有交换,谁空谁是to.
关于垃圾回收:频繁在新生区收集,很少在养老区收集,几乎不在永久区/元空间收集。

特殊情况
在这里插入图片描述

4.3 Minor GC、Major GC、Full GC

JVM在进行GC时,并非每次都对上面三个内存(新生代、老年代;方法区)区域一 起回收的,大部分时候回收的都是指新生代。
针对HotSpot VM的实现,它里面的GC按照回收区域又分为两大种类型:一种是部分收集(Partial GC),一 种是整堆收集(Full GC )

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

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

老年代GC (Major GC/Fu1l GC)触发机制:
7. 指发生在老年代的GC,老年代空间不足时发生触发。对象从老年代消失时,我们说“Major GC”或“Fu1llGC”发生了。
8. 出现了Major GC,经常会伴随至少一次的Minor GC (但非绝对的,在ParallelScavenge收集器的收集策略里就有直接进行Major GC的策略选择过程)。也就是在老年代空间不足时,会先尝试触发Minor GC。如果之后空间还不足,则触发Major GC
9. Major GC的速度一般会比Minor GC慢10倍以上,STW的时间更长。如果Maior GC后,内存还不足,就报0OM了。

Full GC触发机制:
触发Fu1l GC执行的情况有如下五种:
(1)调用System. gc()时,系统建议执行Fu11 GC,但是不必然执行
(2)老年代空间不足
(3)方法区空间不足
(4)通过Minor GC后进入老年代的平均大小大于老年代的可用内存
(5)由Eden区、survivor space0 (From Space) 区向survivor space1 (ToSpace) 区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小
说明: full gc是开发或调优中尽量要避免的。这样暂时时间会短一些(STW)。

堆空间分代思想

在这里插入图片描述
为什么需要把Java堆分代?不分代就不能正常工作了吗?
其实不分代完全可以,分代的唯一理由就是优化GC性能。如果把所有对象都放在一个区域,GC的时候要找到哪些对象是垃圾,这样就会对整个区域进行扫描。而很多对象都是朝生夕死的,如果分代的话,把新创建的对象放到某一个地方,GC的时候对这个地方进行扫描回收垃圾。

内存分配策略

针对不同年龄段的对象分配原则如下所示:

  • 优先分配到Eden
  • 大对象直接分配到老年代尽量避免程序中出现过多的大对象
  • 长期存活的对 象分配到老年代
  • 动态对象年龄判断
    如果Survivor 区中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代,无须等到MaxTenuringThreshold中要求的年龄。
  • 空间分配担保.
    -XX: HandlePromotionFailure
TLAB

为什么有TLAB ( Thread Local Allocation Buffer) ?

  • 堆区是线程共享区域,任何线程都可以访问到堆区中的共享数据
  • 由于对象实例的创建在JVM中非常频繁,因此在并发环境下从堆区中划分内存空间是线程不安全的
  • 为避免多个线程操作同一地址,需要使用加锁等机制,进而影响分配速度。

什么呢是TLAB?

  • 从内存模型而不是垃圾收集的角度,对Eden区域继续进行划分,JVM为每个线程分配了一个私有缓存区域,它包含在Eden空间内。
  • 多线程同时分配内存时,使用TLAB可以避免一系列的非线程安全问题,同时还能够提升内存分配的吞吐量,因此我们可以将这种内存分配方式称之为快速分配策略。
  • 据我所知所有OpenJDK衍生出来的JVM都提供了TLAB的设计。
  • 尽管不是所有的对象实例都能够在TLAB中成功分配内存,但JVM确实是将TLAB作为内存分配的首选
  • 在程序中,开发人员可以通过选项“-XX:UseTLAB”设置是否开启TLAB空间。
  • 默认情况下,TLAB空间的内存非常小,仅占有整个Eden空间的1%,当然我们可以通过选项“-XX :TLABWasteTargetPercent”设置TLAB空间所占用Eden空间的百分比大小。
  • 一旦对象在TLAB空间分配内存失败时,JVM就会尝试着通过使用加锁机制确保数据操
    作的原子性,从而直接在Eden空间中分配内存。
堆空间常用JVM参数

-XX:+PrintFlagsInitial :查看所有的参数的默认初始值
-XX:+PrintFlagsFinal : 查看所有的参数的最终值(可能会存在修改,不再是初始值)
查看某一进程的参数:如 SurvivorRatio
在这里插入图片描述
-Xms:初始堆空间内存(默认为物理内存的1/64)
-Xmx:最大堆空间内存(默认为物理内存的1/4)
-Xmn:设置新生代的大小。(初始值及最大值)
-XX:NewRatio:配置新生代与老年代在堆结构的占比
-XX::SurvivorRatio:设置新生代中Eden和Is0/S1空间的比例
-XX:MaxTenuringThreshold:设置新生代垃圾的最大年龄
-xXX::+PrintGCDetails:输出详细的GC处理日志
打印gc简要信息:①-XX: +PrintGC ② -verbose:gc
-XX:HandLePromotionFailure:是否设置空间分配担保

逃逸分析

堆是对象分配唯一的选择吗?
在Java虚拟机中,对象是在Java堆中分配内存的,这是一个普遍的常识。但是,有一种特殊情况,那就是如果经过逃逸分析(Escape Analysis) 后发现,一个对象并没有逃逸出方法的话,那么就可能被优化成栈上分配这样就无需在堆上分配内存,也无须进行垃圾回收了。这也是最常见的堆外存储技术。以此达到降低GC的回收频率和提升GC的回收效率的目的。

  • 通过逃逸分析,Java Hotspot编译器能够分析出一个新的对象的引用的使用范围从而决定是否要将这个对象分配到堆上。
  • 逃逸分析的基本行为就是分析对象动态作用域:
  1. 当一个对象在方法中被定义后,对象只在方法内部使用,则认为没有发生逃逸。(栈上分配)
  2. 当一个对象在方法中被定义后,它被外部方法所引用,则认为发生逃逸。例如作为调用参数传递到其他地方中。(堆中分配)

结论:开发中能使用局部变量的,就不要使用在方法外定义。

逃逸分析代码优化之栈上分配

JIT编译器在编译期间根据逃逸分析的结果,发现如果一个对象并没有逃逸出方法的话,就可能被优化成栈上分配。分配完成后,继续在调用栈内执行,最后线程结束,栈空间被回收,局部变量对象也被回收。这样就无须进行垃圾回收了(效率变高了)。

逃逸分析代码优化之同步省略

如果一个对象被发现只能从一个线程被访问到,那么对于这个对象的操作可以不考虑同步。

  • 在动态编译同步块的时候,JIT编译器可以借助逃逸分析来判断同步块所使用的锁对象是否只能够被一个线程访问而没有被发布到其他线程。如果没有,那么JIT编译器在编译这个同步块的时候就会取消对这部分代码的同步。这样就能大大提高并发性和性能。这个取消同步的过程就叫同步省略,也叫锁消除。
    如:
    在这里插入图片描述
    JIT优化:
    在这里插入图片描述
逃逸分析代码优化之分离对象或标量替换
  • 标量(Scalar)是指一个无法再分解成更小的数据的数据。Java中的原始数据类型就是标量。相对的,那-些还可以分解的数据叫做聚合量(Aggregate),Java中的对象就是聚合量,因为他可以分解成其他聚合量和标量。
  • 在JIT阶段,如果经过逃逸分析,发现一个对象不会被外界访问的话,那么经过JIT优化,就会把这个对象拆解成若干个其中包含的若干个成员变量来代替。这个过程就是标量替换。
    如:
    在这里插入图片描述
    JIT优化:
    在这里插入图片描述
    可以看到,Point这个聚合量经过逃逸分析后,发现他并没有逃逸,就被替换成两个聚合量了。那么标量替换有什么好处呢?就是可以大大减少堆内存的占用。因为一旦不需要创建对象了,那么就不再需要分配堆内存了。
    标量替换为栈上分配提供了很好的基础。
堆小结
  • 年轻代是对象的诞生、成长、消亡的区域,一个对象在这里产生、应用,最后被垃圾回收器收集、结束生命。
  • 老年代放置长生命周期的对象,通常都是从Survivor区域筛选拷贝过来的Java;对象。当然,也有特殊情况,我们知道普通的对象会被分配在TLAB上;如果对象较大,JVM会试图直接分配在Eden其他位置上;如果对象太大,完全无法在新生代找到足够长的连续空闲空间,JVM就会直接分配到老年代。
  • 当GC只发生在年轻代中,回收年轻代对象的行为被称为MinorGC。当GC发生在老年代时则被称为MajorGC或者FullGC。一般的,MinorGC的发生频率要比MajorGC高很多,即老年代中垃圾回收发生的频率将大大低于年轻代。

5、方法区

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值