jvmHotSpot虚拟机对象探秘

  • 对象的实例化
  • 对象的内存布局
  • 对象的访问定位
  • 直接内存
  • 执行引擎
  • StringTable
  • 垃圾回收概述
  • 垃圾回收相关算法
  • 垃圾回收相关概念的概述
  • 垃圾回收器

对象的实例化

创建对象的方式new最常见的方式变形一:静态方法变形二:XXXBuilder、XXXFactory的静态方法Class的newInstance():反射方式,只能调用空参的构造器,权限必须publicConstruct的newInstance(Xxx):反射方式,可以调用空参或有参的构造器,权限没有要求clone():当前类实现Cloneable接口,实现clone()反序列化:从文件,从网络中获取一个对象的二进制流第三方库Objenesits:字节码技术创建对象的步骤判断对象对应的类是否加载、链接、初始化为对象分配内存如果内存规整指针碰撞如果内存不规整虚拟机需要维护一个列表空闲列表分配说明处理并发安全问题采用CAS配上失败重试保证更新的原子性每个线程分配一块TLAB(Thread Local Allocation Buffer)线程本地分配缓存区通过-XX:+/-UseTLAB参数来设定初始化分配的空间所有属性设置为默认值,保证对象实例字段在不赋值时可以直接使用对象实例化过程中给对象的的属性赋值的操作属性的默认初始化显示初始化代码块初始化构造器初始化设置对象头信息属性的显示、代码块、构造器初始化

对象的内存布局

对象头(Heeder)包含两部分运行时数据(Mark Word)哈希值GC分代年龄锁状态标志线程持有的锁偏向线程ID偏向时间戳类型指针指向类元数据InstanceKlass,确定该对象所属的类型说明:如果是数组,还要记录数组的长度实例数据(Instance Data)说明它是对象真正存储的有效信息,包括程序代码中定义的各种类型的字段(包括从父类继承下来的和本身拥有的字段)规则相同宽度的字段总是被分配在一起父类中定义的变量会出现在子类之前如果CompactFields参数为true(默认):子类的窄变量可能插入到父类的空隙1、这部分的存储顺序会受到虚拟机分配策略参数和字段在Java源码中定义顺序的影响。
2、分配策略参数-XX:FieldsAllocationStyle
3、HotSpot虚拟机默认的分配顺序为longs/doubles、ints、shorts/chars、bytes/booleans、oops(Ordinary Object Pointers)
4、从默认的分配策略中可以看出,相同宽度的字段总被分配到一起存放。
5、在满足这个前提条件的情况下,在父类中定义的变量会出现在子类之前。
6、如果HotSpot虚拟机的+XX:CompactFields参数值为true(默认也是true),那么子类中较窄的变量也允许插入父类变量的空隙之间,以节省一点点空间。对齐填充(padding)不是必须的,也灭特别含义,仅仅起到占位符的作用

对象的访问定位

【使用句柄】访问使用句柄,Java堆中将划出一块内存作为句柄池,reference中存储的就是对象的句柄地址,句柄包含对象实例数据与类型数据各自的具体信息。【直接指针】使用指针,reference中存储的直接就是对象地址,如果访问对象本身,不需要多一次的间接访问的开销。两种方式各有优势:使用句柄最大好处是reference中存放的是稳定句柄地址,在对象被移动(垃圾搜集时会产生)时只改变句柄中实例数据指针,reference本身不用改变。使用指针最大好处就是速度快,节省了一次指针定位的时间开销,由于对象访问在Java中非常频繁,所以积少成多也是一项可观的执行成本。HotSpot主要是用指针,进行对象访问(例外情况,如果使用Shenandoah收集器的话,也会有一次额外的转发)。

直接内存

概述:元空间使用的是直接内存不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域直接内存是在java堆外的,直接向系统申请的内存区间来源于NIO,通过存在堆中的DirectByteBuffer操作Native内存通常,访问直接内存的速度会优于Java堆,即读写性能高因此出于性能考虑,读写频繁的场合可能会考虑使用直接内存Java的NIO库允许Java程序使用直接内存,用于数据缓冲区IO   NIO对比IObyte[]/char[]面向流StreamNIO非阻塞IO阻塞式BufferChanner通道也可能导致OOM异常直接内存在堆外,所以大小不受限于-Xmx指定的最大堆大小但是系统内存是有限的,Java堆和直接内存的总和依然受限于操作系统能给出的最大内存缺点分配回收成本较高不受JVM内存回收管理直接内存大小可以通过MaxDirectMemorySize设置如果不指定,默认与堆的最大值-Xmx参数值一致

执行引擎

概述执行引擎是Java虚拟机核心的组成部分之一“虚拟机”是一个相对于“物理机的概念。这两种机器都有代码执行能力,区别是物理机的执行引擎是直接建立在处理器、缓存、指令集和操作系统层面上的,二虚拟机的执行引擎则是有软件自行实现的,因此可以不受屋里条件制约地定制指令集与执行引擎的结构体系,能够执行那些不被硬件直接支持的指令集格式。执行引擎的工作过程执行引擎在执行的过程中究竟需要执行什么样的字节码指令完全依赖PC寄存器每当执行完一项指令操作后,PC寄存器就会更新下一条需要被执行的指令地址当然方法在执行过程中,执行引擎有可能会通过存储在局部变量表中的对象引用准确定位到存储在Java堆区中的对象实例信息,以及通过对象头中的元数据指针定位到目标对象的类型信息。任务将字节码指令解释、编译为对应平台上的本地机器指令,简单地说,JVM中的执行引擎充当了将高级语言翻译为机器语言的译者。什么是解释器当Java虚拟机启动时会根据预定义的规范对字节码采用逐行解释的方式执行,将每条字节码文件中的内容“翻译”为对应平台的本地机器指令执行。工作机制解释器真正意义上所承担的角色就是一个运行时“翻译者”,将字节码文件中的内容“翻译”为对应平台的本地机器指令执行。当一条字节码指令被解释执行完成后,接着再根据PC寄存器中记录的下一条需要被执行的字节码执行解释操作。分类古老字节码解释器在执行时通过纯软件代码模拟字节码的执行,效率低下普遍使用的模板解释器将每一条字节码和一个模板函数相关联,模板函数中直接产生这条字节码执行时的机器码,从而很大程度上提高了解释器的性能。Hotspot的解释器构成Interpreter:实现解释器的核心功能Code:用于管理Hotspot VM在运行时生成的本地机器指令什么是JITJust In Time compile:就是虚拟机将源代码直接编译成和本地机器平台相关的机器语言。既然Hotspot vm中已经内置了JIT编译器了,为什么还需要在使用解释器来“拖累”程序的执行性能呢首先明确:当程序启动后,解释器可以马上发挥作用,省去编译时间,立即执行。编译器要想发挥作用,把代码编译成本地代码,需要一定的执行时间。但编译为本地代码后,效率高。子主题 2内嵌的两个JIT编译器-client:指定Java虚拟机运行在Client模式下,并使用C1编译器;C1编译器会对字节码进行简单和可靠的优化,耗时短。以达到更快的编译速度。优化策略方法内联:将引用的函数代码编译到引用各处,这样可以减少栈帧的生成,减少参数传递以及跳转过程。去虚拟化:队唯一的实现类进行内联冗余消除:在运行期间把一些不会执行的代码折叠掉-Server:使用C2编译器,C2进行耗时较长的优化,以及激进优化。但优化的代码执行效率更高。基于逃逸分析标量替换:用标量值代替聚合对象的属性值栈上分配:对于未分配的对象分配对象在栈而不是堆同步消除:清楚同步操作,通常指synchronized

StringTable

基本特性字符串,使用“”表示。String s1 = ""String s2=new String("")声明为final,不可被继承实现了serializable接口:字符串支持序列化。实现Comparable接口,表示string可以比较大小8以前内部定义了final char[] value用于储存字符串数据,9时改为byte[].jdk9更改动机char数组一个char占16bits,String是堆空间的主要部分,大部分是latin-1字符,,一个字节就够了,这样会有一半空间浪费中文等UTF-16 的用两个字节存储。StringBuffer,StringBuilder同样做了修改String代表不可变的字符序列 简称不可变性当字符串重新赋值,需要重写指定内存区域赋值,不能使用原有的value进行赋值当对现有的字符串进行连接操作时,也需要重新指定内存区域赋值,不能对使用原有的value进行赋值当调用String的replace方法修改指定字符或字符串时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。字符串常量池中不会存储相同的字符串的String的String pool是一个固定大小的HashTable,默认大小长度是1009,如果放进String Pool的String非常多,就会造成Hash冲突严重,从而导致链表会很长,而链表长了,直接影响就是调用String.intern时性能会大幅下降-XX:StringTableSize可设置StringTable的大小JDK6固定1009,jdk7中StringTable默认的长度是60013,JDK8时默认是60013,1009是可设置的最小值String的内存分配Java语言中有8种基本数据类型和一种比较特殊的类型String,这些类型为了使他们再运行过程中速度更快,更节省内存,都提供了一种常量池的概念String的常量池比较特殊,主要使用方法有两种直接使用双引号,声明出来的String对象会直接存储在常量池中如果不是双引号声明的String对象,可以使用String提供的intern()方法jdk6及之前,字符串常量池存在永久代jdk7中,字符串常量池调整到Java堆中调优时仅需调整堆大小就可以Jdk8中,元空间,字符串常量在堆为什么要调整?永久代默认情况下比较小,大量字符串容易导致OOM。永久代垃圾回收频率低,字符串拼接操作常量与常量的拼接结果在常量池,原理是编译期优化常量池中不会存在相同内容的常量只要其中有一个是变量,结果就在堆中。变量拼接的原理是StringBuilder字符串拼接操作不一定使用的是StringBuilder如果拼接符号左右两边都是字符串常量或常量引用,则仍然使用编译期优化,即非StringBuilder的方式建议:针对于final修饰类,方法,基本数据类型,引用类型的量的结构时,能用final尽量用如果拼接的结果调用intern()方法,则主动将常量池中还没有的字符串对象放入池中,并返回此对象地址。如何保证变量指向的是字符串常量池中的数据?字面量方式:String s="XXX"调用intern方法intern()方法面试题:new String(“ab")会创建几个对象?两个。看字节码。new关键字在堆空间创建。字符串常量池中的对象。字节码指令ldcnew String("a")+new String("b")?对象1,有拼接操作就newStringBuilder对象2,new一个String对象3,常量池a对象4,new String对象5,常量池b对象6,StringBuilder,toString方法会new String返回此时字符串常量池中没有ab

垃圾回收概述

什么是垃圾?垃圾是指在运行程序中没有任何指针指向的对象,这个对象就是需要被回收的如果不及时对内存中的垃圾进行清理,那么,这些垃圾对象所占的内存空间会一直保留到应用成功程序结束,被保留的空间无法被其他对象使用,可能导致内存溢出。为什么需要GC对于高级语言来说,一个基本认知是如果不进行垃圾回收,内存迟早都会被耗完。除了释放没用的对象,垃圾回收也可以清除内存里的记录碎片,碎片整理将所占用的堆内存移到堆的一端以便 JVM将整理出的内存分配给新的对象随着应用庞大,没有GC就不能保证应用程序的正常进行

垃圾回收相关算法

标记阶段:引用计数算法对象存活判断在堆里存放着几乎所有的对象实例,在GC执行回收之前,首先需要区分出内存中哪些是存活对象,哪些是已经死亡的对象。只有被标记为已经死亡的对象GC才会在执行垃圾回收时,释放掉其所占用的内存空间那么JVM如何标记一个死亡对象?简单地说,当一个对象已经不再被任何的存活的对象继续引用时,就可以宣判死亡。判断对象存活两种方式引用计数法对每个对象保存一个整型的引用计数器属性,用于记录对象被引用的情况引用+1,引用失效-1,计数器为0,可进行回收可达性分析算法优点实现简单,垃圾对象便于辨识‘判定效率高,回收没有延迟缺点需要独立的字段存储计数器,这样做法增加了存储空间的开销每次赋值都需要更新计数器,伴随着加减法的操作,增加了时间开销有一个严重问题:无法处理循环引用的情况,致命缺陷,导致在Java的垃圾回收器中没有使用这类算法。小结引用计数算法,是很多语言的资源回收选择,例如因人工智能而更加火热的Python,它更是同时支持引用计数和垃圾回收机制具体哪种最优是看场景,业界有大规模实践中仅保留引用计数机制,以提高吞吐量的尝试Java并没有选择引用计数,因为其致命缺陷-循环引用Python如何解决?手动解除:在合适时机,解除引用使用弱引用weakref,是Python提供的标准库,旨在解决循环引用标记阶段:可达性分析算法相对于引用而言,它不仅同样具备实现简单和效率高效等特点,更重要的是可以有效地解决在引用计数算法中循环引用的问题,防止内存内泄漏的发生Java、C#选择的。这种那个类型的垃圾收集也叫作追踪性垃圾收集基本思路GC Roots根集合就是一组必须活跃的引用以根对象集合为起始点,按照从上至下的方式搜索被根对象集合所连接的目标对象是否可达使用它后,内存中的存活对象都会被根对象集合直接或间接连接着,搜索所走过的路径被称为引用链如果目标对象没有任何引用链相连,则是不可达的,就意味着该对象已经死亡,可以标记为垃圾对象高频:在Java中,GC Roots包含以下几类元素虚拟机栈中引用的对象各个线程被调用的方法中使用到的参数、局部变量等本地方法栈内JNI引用的对象方法区中静态属性引用的对象类的引用类型静态变量方法区中常量引用的变量字符串常量池里的引用所有被同步锁sysnchronized持有的对象虚拟机内部的引用基本数据类型对应的Class对象,一些常驻的异常对象(NullPointerException、OutOfMemoryError)系统类加载器反映Java虚拟机内部情况的JMXBean,JVMTI中注册的回调,本地代码缓存小技巧由于Root采用栈方式存放变量和指针,所以如果一个指针,它保存了堆内存里面的对象,但是自己又不存放在堆内存里面,那么它就是一个Root对象的finalization机制除了固定的GC Roots集合之外,根据用户选择的垃圾收集器以及当前回收的内存区域不同,还可以有其他对象临时性的加入,共同构成完整GCRoots集合,比如分代收集和局部回收如果只针对Java堆中某一块内存区域进行垃圾回收,必须要考虑这个区域的对象可能被其他区域对象所引用,这是需要一并将关联的区域对象加入GC Roots集合中去考虑,才能保证可达性分析的准确性。Java语言提供了对象终止finaliztion机制来允许开发人员提供对象被销毁之前的自定义处理逻辑当垃圾回收器发现没有引用指向一个对象,即垃圾回收此对象之前,总会先调用这个对象的finalize()方法finalize()方法允许在子类中被重写,用于在对象被回收时进行资源释放,通常在这个方法中进行一些资源释放和清理的工作,比如关闭文件,套接字和数据库链接等定义虚拟机的对象可能的三种状态可触及的从根节点开始,可以到达这个对象可复活的对象的所有引用都被释放了,但是对象有可能在finalize()中复活不可触及的对象的finalize()被调用,并且没有复活,那么就会进入不可触及状态。不可触及的对象不可能被复活,因为finalize()只会被调用一次只有对象再不可触及时才可以被回收具体过程判断一个对象ObjA是否可以被回收,至少需要经历两次标记过程1、如果对象到GCRoots没有引用链,则进行第一次标记2、进行筛选,判断此对象是否有必要执行finalize()方法如果对象A没有重写finalize方法,或者finalize方法已经被虚拟机调用过,则虚拟机视为没有必要执行,对象A被判定为不可触及的如果对象A重写finalize()方法,且还未执行过,那么A会被插入到F-queue队列中,有一个虚拟机自动创建的,低优先级的Finalizer线程触发其finalize()方法执行finalize方法是对象逃脱死亡的最后机会,稍后GC会对F-queue队列中的对象进行第二次标记,如果A在finalize方法中与引用链上的任何一个对象建立了联系,那么在第二次标记时,A会被移除即将回收集合。之后,对象会再次出现没有引用存在的情况下,finalize方法不会再被调用,对象直接变为不可触及状态MAT与JProfiler的GC Roots硕源MAT是Memory Analyzer的简称,是一款功能强大的Java堆内存分析器。用于查找内存泄露以及查看内存消耗情况,基于Eclipse开发的一款免费性能分析工具清除阶段:标记清除算法执行过程当堆中的有效内存空间被耗尽的时候,就会停止整个程序(STW),然后进行两项工作,标记-清除。标记:Collector从根节点开始遍历,标记所有被引用的对象。一般是在对象的Header中记录为可达对象。清除:Collector对堆内存从头到尾进行线性的遍历,如果发现某个对象在其Header中没有标记为可达对象,则将其回收。缺点效率不算高在进行GC的时候,需要停止整个应用程序,导致用户体验差这种方式清理出来的空闲内存是不连续的,产生内存碎片。需要维护一个空闲列表何为清除?这里所谓的清除并不是真的置空,而是把需要清除的对象地址保存在空闲的地址列表里。下次有新对象需要加载时,判断垃圾的位置空间是否够,如果够,就存放。清除阶段:复制算法核心思想将活着的内存空间分为两块,每次只使用其中一块,在垃圾回收时将正在使用的内存中的存活对象复制到未被使用的内存块中,之后清除正在使用的内存块中的所有对象,交换两个内存角色,最后完成垃圾回收。优点没有标记和清除过程,运行高效复制过去以后保证空间的连续性,不会出现“碎片问题”缺点两倍的内存空间对于G1这种拆分成为大量region的GC,福祉而不是移动,意味着GC需要维护region之间对象的引用关系,不管是内存占用或者时间开销也不小特别地如果系统中的垃圾对象很多,复制算法不太理想。因为复制算法需要复制的存活对象数量并不会太大,或者说非常低才行。清除阶段:标记-压缩(整理)算法背景复制算法的高效性是建立在存活对象少、垃圾对象多的前提下。这种情况在新生代经常发生,但是在老年代,更常见的1情况是大部分对象都是存活对象。如果依然使用复制算法,由于存活对象较多,复制的成本也将很高。因此,基于老年代的垃圾回收的特性,需要使用其他的算法。标记-清除算法的确可以应用在老年代中,但是该算法不仅执行效率低下,而且在执行完内存回收后还会产生内存碎片,所有JVM的设计者在此基础上进行改进。标记-压缩由此诞生。执行过程第一阶段和标记-清除算法一样,从根节点开始标记所有被引用对象第二阶段将所有的存活对象压缩到内存的一段,按顺序排放。之后,清理边界外所有的空间优点清除了标记-清除算法当中,内存区域分散的的缺点,我们需要给新对象分配内存时,JVM只需要持有一个内存的起始地址即可。清除了复制算法中,内存减半的高额代价缺点从效率上说,标记-整理算法低于复制算法移动对象的同时,如果对象被其他对象引用,则还需要调整引用的地址移动过程中,需要全程暂停用户应用程序。SWT小结分代收集算法不同生命周期的对象可以采取不同的收集方式,以便提高回收效率spot中年轻代特点区域相对老年代小对象生命周期短对象存活率低回收频繁这种情况复制算法的回收整理,速度是最快的。复制算法的效率只和当前存活对象大小有关。因此很适合用于年轻代的回收。而复制算法内存利用率不高的问题,通过Hotspot中的两个survivor的设计得到缓解。老年代特点区域较大对象生命周期长存活率高回收不及年轻代频繁这种情况存在大量存活率高的对象,复制算法明显变得不合适。一般是由标记-清除或者是标记-清除与标记-整理混合实现Mark阶段的开销与存活对象的数量成正比Sweep阶段的开销与所管理区域的大小成正比compact阶段的开销与存活对象的数据成正比增量收集算法、分区算法增量收集算法思想每次垃圾收集线程只收集一小片区域的内存空间,接着切换到应用程序线程,依次反复,直到垃圾收集完成通过对线程间冲突的妥善管理,允许垃圾收集线程以分阶段的方式完成标记、清理或复制工作缺点线程和上下文切换导致系统吞吐量的下降

垃圾回收相关概念的概述

System.gc()的理解在默认情况下,通过System.gc()或者Runtime.getRuntime().gc()的调用,会显式出发full GC,同时对老年代he新生代进行回收,会尝试释放被丢弃对象占用的内存。然而System.gc()调用无法保证对垃圾收集器的调用JVM实现者可以通过System,gc()调用来决定JVM的GC行为。而一般情况下,垃圾回收应该是自动进行的,无需手动触发,否则则就太过于麻烦了。内存溢出与内存泄露OOMjava 虚拟机的堆内存设置不够代码创建大量大对象,并且长时间不能被垃圾收集器收集(存在被引用)内存泄露只有对象不再被程序用到了,但是GC又不能回收他们的情况,才叫内存泄露实际情况有一些疏忽导致对象的生命周期变的很长甚至OOM,宽泛意义上的内存泄露举例单例的生命周期和程序是一样长,如果单例程序中,持有对外部对象的引用的话,那么这个外部对象是不能被回收的,导致内存泄露一些提供close的资源未关闭导致内存泄露,如数据库链接,网络链接,和IOSTWStop the world,指的是GC事件发生过程中,会产生应用程序的停顿。停顿产生时整个应用程序线程都会被暂停,没有任何响应。可达性分析算法枚举根节点导致stw分析工作必须在一个能确保一致性的快照中进行一致性指整个分析期间真个执行系统看起来像被冻结在某个时间点如果出现分析过程中对象引用关系还在不断变化,则分析结果的准确性无法保证开发中不要用System.gc();会导致stw的发生垃圾回收中的并行和并发并发在操作系统中,是指一个时间段中有几个程序都处于以启动运行到运行完毕之间,这几个程序都在同一个处理器上并发并不是真正的“同时进行”,CPU把一个时间段划分成几个时间区间,然后再这几个时间区间来回切换,CPU处理非常快,只要时间间隔处理得当,用户就感觉是多个程序同时在进行并行当系统有一个以上CPU,当一个CPU执行一个进程时,另一个CPU可以执行另一个进程,两个进程互不抢占CPU资源,可以同时进行。决定并行的1因素并不是CPU的数量,而是CPU的核心数量,多核也可以并行VS执行时间并发,多个事情,在同一时间段同时发生并行,多个事情,在同一时间点同时发生资源并发多个任务之间互相抢占资源并行不抢只有在多CPU或一个CPU多核的情况下,才会发生并行安全点和安全区域安全点程序执行时并非在所有地方都能停顿下来开始GC,只有在特定的位置才能停顿开始GC。选择太少可能导致GC等待时间太长,如果太频繁可能导致运行时对的性能问题。选择一些执行时间较长的指令作为安全点,方法调用,玄幻跳转,异常跳转GC发生时,检查所有县城都跑到碎金的安全点停顿下来?抢先式中断没有虚拟机使用。首先中断所有线程,如果有线程不在安全点,让线程跑到安全点主动式中断设置一个中断标志,各个线程运行到安全点的之后主动轮询这个标志,如果中断标志为真,将自己中断挂起安全区域安全点机制保证程序在执行时,在不太长时间内就会遇到可进入GC的安全点。但是,程序“不执行”的时候呢?例如线程处于sleep或blocked状态,这时候线程无法响应JVM的中断请求,“走”到安全点去中断挂起,JVM也不太可能等待线程被唤醒。这时候就需要安全区域来解决安全区域是在一段代码片段中,对象的引用关系不会发生变化,在这个区域中的任何位置开始GC都是安全的。再谈引用四种引用强引用永不回收。Object o = new Object()这种关系,无论在任何情况下,只要强引用关系存在,垃圾收集器就永远不会回收被引用的对象强引用是造成java内存泄露的主要原因之一强引用可以直接访问目标对象软引用内存不足才回收。在系统将要发生内存溢出之前,将会把这些对象列入回收范围之中进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常弱引用发现即回收。在弱引用关联的对象只能生存到下一次垃圾收集之前。当垃圾收集器工作时,无论内存空间是否足够,都会回收掉被弱引用关联的对象可有可无的缓存数据虚引用一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来获得一个对象的实例。为一个对象设置虚引用关联的唯一目的是能在这个对象被收集器回收时收到一个系统通知可以跟踪对象的回收时间,可以将一些资源释放操作放置在虚引用中执行和记录面试题你开发中用过WeakHashMap吗内存溢出OOMjava 虚拟机的堆内存设置不够代码创建大量大对象,并且长时间不能被垃圾收集器收集(存在被引用)

垃圾回收器

GC分类与性能指标垃圾回收器分类按垃圾回收线程数可以分为串行垃圾回收器截图串行回收指同一个时间段内,只允许一个CPU用于执行垃圾回收操作,此时工作线程被暂停,直到垃圾收集工作结束在单CPU处理器或者较小应用内存等硬件平台不是特别优越的场合,串行回收器的性能表现可以超过并行回收器和并发回收器。所以串行回收默认被应用在客户端的client模式下的JVM中在并发能力比较强的CPU上,并行回收器产生的停顿时间要短于串行回收器并行垃圾回收器和串行相反,并行收集可以运用在多个CPU同时执行垃圾回收,因此提升了应用的吞吐量,不过并行回收仍然与串行回收一样,采用独占式,使用了STW机制按照工作模式分并发式垃圾回收器与应用程序交替工作,以尽可能减少应用程序的停顿时间独占式一旦运行,就停止应用程序中所有的用户线程,直到垃圾回收过程完全结束按照碎片处理方式压缩式非压缩式按个工作内存区间分年轻代老年代性能指标吞吐量运行用户代码的时间占总运行时间的比例总运行时间:程序的运行时间+内存回收的时间吞吐量优先,意味着单位时间内,STW的时间最短垃圾收集开销吞吐量的补数,垃圾收集所占用的时间与总运行时间的比例暂停时间执行垃圾收集时,程序的工作线程被暂停的时间暂停时间优先,意味着单次STW的时间最短,但是频率可能增加收集频率相对于应用程序的执行,收集操作发生的频率内存占用Java堆区所占的内存大小快速一个对象从诞生到被回收经历的时间不可能三角简单来说抓住两点,吞吐量和暂停时间高吞吐量与低暂停时间,是一对互相竞争的。因为如果高吞吐量优先,必然需要降低内存回收的执行频率,导致GC需要更长的暂停时间来执行内存回收。如果选择低延迟优先为原则,也只能频繁的执行内存回收,引起程序吞吐量的下降现在的标准,在最大吞吐量优先的情况下,降低停顿时间不同的垃圾回收器概述垃圾回收器的发展迭代史Serial GC1999年jdk1.3.1第一款GCParNew是SerialGC收集器的多线程版本Parallel GC和Concurrent Mark SweepGCjdk1.4.22002年2月26日ParallelGC在JDK1.6之后称为HotSpot默认GCG12012年jdk1.7u42017年JDK9中G1变成默认的垃圾收集器,以替代CMS2018年3月,JDK10中G1垃圾回收器的并行完整垃圾回收,实现并行性改善最坏情况下的延迟Epsilon 垃圾回收器、ZGC,可伸缩低延迟垃圾回收器2018年9月JDK11Shenandoah GC:低停顿时间的GC,实验版2019年3月JDK12增强ZGC2019年9月JDK13删除CMS垃圾回收器,扩展ZGC在macOS和Windows上的应用2020年3月JDK147款经典垃圾收集器和垃圾分代之间的关系截图垃圾收集器的组合关系jdk8之前,可以用虚线参考关系CMS下面的实线,是CMS回收失败的后备方案JDK8中取消了红线的组合,标记为废弃的。如果要用也可以用。JDK9中将红线做了removejdk14中弃用了绿线组合jdk14中删除了CMSGCJDK9默认G1JDK8默认Parallel Scavenge 和Parallel old Gc新生代用了Parallel Scavenge 则老年代自动触发用Parallel oldParallel底层与ParNew底层不同,所以不能和CMS组合如何查看默认的垃圾收集器-XX:+PrintCommandLineFlagsjinfo -flag 相关垃圾回收器参数 进程ID截图Serial回收器:串行回收Serial收集器采用复制算法,串行回收和STW机制的方式执行内存回收除了年轻代,还有用于执行老年代的Serial old收集器,同样采取了串行回收,但是用标记压缩算法截图使用一个CPU或者一条收集线程去完成垃圾收集工作,在进行垃圾收集时,必须暂停其他所有工作线程优势简单而高效,对于限定单个CPU的环境来说,由于没有线程交互的开销,可以获取最高的单线程收集效率HotSpot虚拟机中,使用-XX:+UseSerialGC指定年轻代和老年代使用串行收集器对于交互强的应用而言,不会采取串行垃圾收集器ParNew回收器:并行回收除了采用并行回收,其他方面和Serial之间几乎没有任何区别截图-XX:UseParNewGC手工指定ParNew收集器执行内存回收任务,它表示年轻代使用,不影响老年代-XX:ParallelGCThreads限制线程数量,默认开启和CPU数据相同的线程数Parallel回收器:吞吐量优先也是并行回收和ParNew不同,它的目标是达到一个可控制的吞吐量自适应调节策略也是Parallel 与ParNew的一个重要区别适合后台运算不需要太多交互的任务,例如执行批量处理,订单处理,工资支付,科学计算的应用程序Parallel old采取标记压缩算法,同样基于并行回收和STW机制参数配置-XX:+UseParallelGC手动指定年轻代使用此收集器执行内存回收任务-XX:+UseParallelOldGC手工指定老年代使用并行回收收集器,分别适用于新生代和老年代,默认jdk8是开启的与上面这两个参数关联,开启一个,默认开启另一个。-XX:ParallelGCThreads设置年轻代并行收集器的线程数,一般与CPU数量相同,如果CPU数量大于8个,则值=3+(5*N/8)-XX:MaxGCPauseMillis设置收集器最大停顿时间,单位毫秒改参数谨慎使用-XX:GCTimeRatio垃圾收集占总时间比,用于衡量吞吐量大小默认99,取值范围0-100,也就是垃圾回收时间不超过1%与上一个参数矛盾,暂停时间越长,Ratio参数就容易超过设定比例-XX:+UseAdaptiveSizePolicy开启自适应调节策略这种模式下,年轻代大小,Eden和Survivor的比例,晋升老年底对象年龄参数都会被自动调整为了达到堆大小,吞吐量和停顿时间之间的平衡点在手动调优比较困难的场景下,可以直接用自适应方式,仅指定虚拟机最大堆,目标吞吐量和停顿时间,让虚拟机自己完成调优工作CMS回收器:低延迟jdk1.5推出Concurrent Mark Sweep 并发的标记清除,第一次实现了让垃圾收集线程与用户线程同时工作截图初始标记:STW,仅仅只是标记处GC Roots能直接关联的对象,一旦标记完成后就会恢复之前被暂停的所有应用线程,由于直接关联对象比较小,所以这里速度非常快并发标记:从GCRoots的直接关联对象开始遍历整个对象图的过程,这个过程耗时较长,但是不需要停顿用户线程。可以与垃圾收集线程一起并发运行重新标记:为了修正并发标记期间,因用户程序继续运作导致标记产生变动的那一部分对象的标记记录并发清除:清理删除标记阶段判断的已经死亡的对象,释放内存空间。由于不需要移动存活对象,所以这个阶段也可以与用户线程同时并发初始标记和重新标记阶段仍然需要STW机制由于在垃圾收集阶段用户线程没有中断,所以在CMS回收过程中,还应该确保应用程序用户线程有足够的内存可用。因此CMS收集器不能像其他收集器那样等到老年代几乎填满再进行回收,而是当堆内存使用率达到某一阈值时,便开始进行回收。要是CMS运行期间预留的内存无法满足程序需要,就会出现一次Concurrent Mode Failure失败,这时虚拟机启用备用方案,临时启用Serial old 收集器来重新进行老年代的垃圾收集,这样停顿时间就长了。CMS采取标记清除算法,会产生内存碎片,只能够选择空闲列表执行内存分配为什么不采取标记压缩呢?因为并发清除时,如果用压缩整理内存,原来的用户线程使用的内存就无法使用了。标记压缩更适合STW场景下使用优点并发收集低延迟缺点会产生内存碎片对CPU资源非常敏感在并发阶段会占用一部分线程导致应用程序变慢无法处理浮动垃圾并发标记阶段是与工作线程同时运行,如果并发阶段产生垃圾对象,CMS无法进行标记,导致新产生的垃圾对象没有被及时回收,只能在下一次执行GC时释放空间参数-XX:+UseConcMarkSweepGC手工指定CMS收集器执行内存回收任务开启后,自动将-XX:UseParNewGC打开,即ParNew(Young区)+CMS(old区)+Serial GC组合-XX:CMSlnitiatingOccupanyFraction设置堆内存使用率的阈值一旦达到该阈值,则开始进行回收jdk5及之前默认68,即老年代的空间使用率达到68%时会执行一次CMS回收JDK6及以上默认值为92%如果内存增长缓慢,可以设置一个稍大的值,有效降低CMS的触发频率,减少老年代回收的次数如果应用程序内存使用率增加很快,则应该降低这个阈值,以避免频繁触发老年代串行收集器。-XX:+UseCMSCompactAtFullCollection用于执行完Full GC后对内存空间进行压缩整理不过内存压缩无法并发执行,会带来停顿时间更长的问题-XX:CMSFullGCsBeforeCompaction设置执行多少次FullGC后对内存空间进行压缩整理-XX:ParallelCMSThreads设置CMS的线程数量默认启动的线程数是(ParallelGCThreads+3)/4ParallelGCThreads是年轻代并行收集器的线程数小结如果想要最小化使用内存和并行开销,选择Serial GC如果最大化应用程序的吞吐量,选择ParallelGC如果想要最小化的GC的中断或停顿时间,选择CMS GCjdk9标记为废弃的,jdk14已经删除了G1回收器:区域化分代式官方给G1设定的目标就是在延迟可控的情况下,获得尽可能高的吞吐量,所以才担当起全功能收集器的重任和期望Garbage FirstG1是一个并行回收器,他把堆内存分割为很多不相关的区域(Region)(物理上不连续)使用不同的region表示Eden,s0,s1,老年代等G1跟踪各个region里面垃圾堆积的价值大小,在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的RegionJDK1.7版本正式启用,jdk9以后默认垃圾回收器JDK8还不是默认的,需要用-XX:+UseG1GC来启用优势并行与并发分代收集同时兼顾年轻代与老年代空间整合region之间用复制算法,整体可以看做是标记压缩算法。两种算法都避免内存碎片,有利于程序长时间运行,分配大对象不会因为无法找到连续空间提前触发下一次GC,尤其当Java堆非常大的时候,G1优势更加明显可预测的停顿时间模型能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不能超过N毫秒缺点相较于CMS,G1不具备全方位,压倒性优势。比如用户程序运行中,G1无论是为了垃圾收集产生的内存占用,还是程序运行时的额外执行负载都要比CMS要高经验上来说,小内存应用CMS表现大概率优于G1,在大内存上G1优势发挥更多,平衡点再6-8GB参数设置-XX:+UseG1GC-XX:G1HeapRegionSize设置每个Region大小,值是2的幂,范围是1MB到32MB之间,目标是根据最小的Java堆划分出约2048个区域,默认是堆内存的1/2000-XX:MaxGCPauseMillis设置期望达到的最大GC停顿时间指标,JVM尽力但不保证,默认200ms-XX:ParallelGCThread设置STW工作线程数的值,最多设置8-XX:ConcGCThreads设置并发标记的线程数,将N设置为并行垃圾回收线程数(parallelGCThreads)的1/4左右-XX:InitiatingHeapOccupancyPercent设置触发并发GC周期的Java堆占用率阈值,超过此值就触发GC,默认是45常见调优第一步开启G1垃圾收集器第二步,设置堆的最大内存第三步,设置最大的停顿时间G1提供了三种垃圾回收模式在不同的条件下触发YoungGCMixedGCFullGC适用场景面向服务器端应用,针对具有大内存,多处理器的机器最主要应用是需要低GC延迟如:在堆大小约6GB或更大,可预测的暂停时间可以低于0.5s,G1每次清理一部分region来保证每次GC停顿时间不会过长用来替换1.5中的CMS超过50%的Java堆被活动数据占用对象分配频率或年代提升频率变化很大GC停顿时间过长,长于0.5~1秒region所有region大小相同,且在JVM生命周期内不会改变截图region可以充当多个角色垃圾回收过程年轻代GC当年轻代eden区用尽时并行独占式收集器老年代并发标记过程当堆内存使用到一定值,默认45%混合回收标记完成马上开始混合回收G1老年代回收器不需要整个老年底都被回收,一次只需要扫描回收一小部分老年代的region就可以了。同时这个老年代回收是和年轻代一起被回收的。有可能fullGC记忆集每个region对应一个记忆集通过记忆集避免全局扫描每次引用类型数据写操作时,会产生一个写屏障暂时中断操作然后检查将要希尔的引用指向的对象是否和该引用对象类型数据在不同的region,如果不同就通过CardTable把相关的引用信息记录到引用指向对象所在的Region对应的记忆集中当进行垃圾收集时,在GC根节点枚举范围加入记忆集,就可以保证不进行全局扫描,也不会有遗漏G1回收过程一,年轻代GC1、扫描根根是指static变量指向的对象,正在执行的方法调用链上的局部变量等。根引用连同Rset记录的外部引用作为扫描存活对象的入口2、更新Rset处理dirty card queue中的card,更新Rset,此阶段完成后,Rset可以准确的反应老年代所在的内存分段中对象的引用3、处理Rset识别被老年代对象指向的Eden中的对象,这些被指向的Eden中的对象被认为是存活的对象4、复制对象对象树被遍历,Eden区内存段中存活的对象会被复制到Survivor去中空的内存分段,Survivor区内存段中存活的对象如果年龄未达阈值,会加一,达到阈值会被复制到old区中空的内存分段,如果Survivor区空间不够,Eden空间的部分数据会直接晋升到老年代空间5、处理引用处理强软弱虚,终结器引用,本地方法接口引用等,最红eden空间的数据为空,GC停止工作,而目标内存中的对象都是连续存储的,没有碎片,所以复制过程可以达到内存整理的效果,减少碎片。G1回收过程二、并发标记过程初始标记阶段STW标记从根节点直接可达的对象,并且触发一次年轻代GC根区域扫描阶段扫描Survivor区直接可达老年代区域对象,并标记被引用的对象,这个过程在youngGC之前完成并发标记和应用程序并发执行,并发标记阶段若发现区域对象中的所有对象都是垃圾,那这个区域会被立即回收。并发标记过程中,会计算每个区域的对象活性,存活对象的比例再次标记由于应用程序持续进行,需要修正上次标记结果,STW,G1采取比CMS更快的初始快照算法独占清理计算各个区域存活对象和GC回收比例,并进行排序,识别可以混合回收的区域。为下个阶段做铺垫,STW,这个阶段并不会实际上去做垃圾的收集并发清理阶段识别并清理完全空闲的区域G1回收过程三:混合回收当越来越多的对象晋升到老年代old region时,为了避免内存被耗尽,虚拟机会触发一次混合的垃圾收集器,该算法除了回收整个young region,还会回收一部分的old region。也要注意Mixed gc并不是fullgc并发标记结束后,老年代中百分百为垃圾的内存分段被回收了。部分为垃圾的内存分段被计算出来了,默认情况下,这些老年代的内存分段会分8次被回收-XX:G1MixedGCCountTarget设置混合回收的回收集包括八分之一的老年代,Eden区内存分段,Survivor区内存分段。由于老年代中内存分段默认分8次回收,G1会优先回收垃圾多的内存分段,并且有一个阈值会决定内存分段是否被回收。-XX:G1MixedGCLiveThresholdPercent,默认为65%。意思是垃圾占比达到65%才会被回收。如果垃圾占比比较低,意味存活对象较高,复制的时候花更多时间。混合回收不一定要进行8次,有一个阈值:-XX:G1HeapWastePercent默认值是10%,意思是允许整个堆内存中有10%的空间被浪费,意味着如果发现可以回收的垃圾占堆内存比例低于10%,则不再进行混合回收,因为GC花费更多的时间,但是回收到的内存却很少。G1可选过程四:fullGCG1初衷就是要避免FULLGC,如果上述方式不能正常工作,G1会停止应用程序的执行。使用单线程的内存回收算法进行垃圾回收,性能非常差。应用程序停顿时间长比如堆太小,当G1复制存活对象的时候没有空的内存分段可用,则会回退到FullGC导致FullGC原因可能有两个回收阶段的时候没有足够的to-space存放晋升的对象并发处理过程完成之前空间耗尽了。优化建议避免使用-Xmn或-XX:NewRatio等相关选项显式设置年轻代大小固定的年轻代大小会覆盖暂停时间目标暂停时间目标不要太苛刻,太苛刻会影响吞吐量垃圾回收器总结截图GC日志分析截图截图截图截图截图截图GC EASY垃圾回收器的新发展截图Shenandoah GC强项低延迟时间弱项高运行负担下的吞吐量下降ZGC在尽可能堆吞吐量影响不大的前提下,实现在任意堆内存大小下都可以把垃圾回收的停顿时间限制在10毫秒以内的低延迟并发标记,并发预备重分配,并发重分配,并发重映射除了初始标记是STW,其他地方几乎都是并发执行的垃圾回收器

jvm有这一篇就够了,深入理解相关推荐

  1. 读懂 JVM 内存管理这篇就够了

    读懂 JVM 内存管理这篇就够了 JVM 的内存结构 程序计数器 作用 概述 PC寄存器的常见问题 虚拟机栈 栈中可能出现的异常 栈的存储单位 栈运行原理 栈帧的内部结构 局部变量表 槽 Slot 操 ...

  2. 【JVM系列】读懂Java虚拟机(JVM)这一篇就够了!

    [本文篇幅较长,博主强烈建议收藏阅读(也可关注)]      JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过 ...

  3. 菜鸟教程JVM优化,看一篇就够了!

    yong gc 的触发时机 当要分配新对象,但新生代edent区不足 将edent区和suvivor区的存活对象拷贝到另外一个survivor区 1)内存充足则,没事 2)如果内存不足,则存活对象直接 ...

  4. 关于Jvm知识看这一篇就够了

    2016年左右的时候读了周志明<深入理解Java虚拟机:JVM高级特性与最佳实践>,读完之后受益匪浅,让我对Java虚拟机有了一个完整的认识,这是Jvm书籍中最好的读物之一. 后来结合实际 ...

  5. JVM常量池最全详解-常量池/运行时常量池/字符串常量池/基本类型常量池,看这一篇就够了

    JVM常量池最全详解-常量池/运行时常量池/字符串常量池/基本类型常量池,看这一篇就够了! 常量池详解 1. 字面量和符号引用 1.1 字面量 1.2 符号引用 2. 常量池vs运行时常量池 3. 常 ...

  6. 深入理解JVM看这篇就够了

    前言 想成为一名优秀的Java工程师必须懂得JVM的原理,这里主要从三方面讲解:JVM类加载.JVM堆内存分配以及GC(垃圾自动回收),这里主要的还是GC回收,所以深入了解GC是必不可少的.当然,这些 ...

  7. .NET Core实战项目之CMS 第二章 入门篇-快速入门ASP.NET Core看这篇就够了

    本来这篇只是想简单介绍下ASP.NET Core MVC项目的(毕竟要照顾到很多新手朋友),但是转念一想不如来点猛的(考虑到急性子的朋友),让你通过本文的学习就能快速的入门ASP.NET Core.既 ...

  8. [转]看懂 Serverless,这一篇就够了

    文章目录 1. 无服务器(Serverless)计算是什么 2. 理解Serverless技术---FaaS和BaaS 2.1 FaaS(Function as a Service,函数即服务) 2. ...

  9. 如何应对大数据分析工程师面试Spark考察,看这一篇就够了

    作者丨斌迪.HappyMint 来源丨大数据与人工智能(ID:ai-big-data) [导读]本篇文章为大家带来spark面试指南,文内会有两种题型,问答题和代码题,题目大部分来自于网络上,有小部分 ...

最新文章

  1. Flutter——设置appBar的高度
  2. 一个由跨平台产生的浮点数bug | 有你意想不到的结果
  3. MySql中当in或or参数过多时导致索引失效
  4. Redis和消息队列使用实战
  5. [译]星际争霸人工智能比赛——规则
  6. DATAGUARD 参数配置
  7. C++primer第二章2.4节对于const限定符相关内容进行详解
  8. gdb调试时,Program received signal SIGPIPE, Broken pipe.
  9. bat循环执行带参数_wxappUnpacker的bingo.bat脚本逐行解读
  10. linux搭建vsftp服务器_Linux配置VSFTP服务器的方法
  11. 比较Spring AOP和AspectJ
  12. java 静态代码块 作用域_java static关键字和代码块
  13. 计算机无法安装网卡驱动,win7网卡驱动安装不了怎么修复_WIN7网卡驱动装不上如何解决...
  14. 【天磊卫士安全预警】incaseformat蠕虫病毒预警
  15. js 实现新年倒计时 定时器使用
  16. 计算机运维机构管理制度,信息化机房运维管理制度
  17. web服务器性能瓶颈,Web服务器性能瓶颈因素
  18. vue 移动端进入页面自动弹出软键盘
  19. CentOS 7 虚拟机网卡失效问题:ens33:<NO-CARRIER,BROADCAST,MULTICAST,UP>mtu 1508 gdisc pf ifo_fast state DOWN
  20. 商业智慧:创造奇迹的信件

热门文章

  1. VS助手工具Visual Assist X 安装和卸载指导手册
  2. Python中的def函数。
  3. elementUI里switch的实现
  4. [Golang] kafka集群搭建和golang版生产者和消费者
  5. python语言中不属于组合数据类型的是_python的基本语法(组合数据类型),基础...
  6. JavaScript——关于JavaScript、在HTML中嵌入JS代码的三种方式、变量
  7. 基于web的房屋房源中介管理系统
  8. 管理类联考——英语二——技巧篇——写作——图表作文——万能句
  9. 广西南宁机器人比赛_南宁举行机器人、创客竞赛,2000多名中小学生赛场大比拼...
  10. 在docker中配置defects4j基准测试集