精华内容
下载资源
问答
  • 常见jvm优化
    2021-12-29 20:57:31

    1、-Xms 512m:初始化堆内存大小为 512m。

    2、-Xmx 512m:堆最大内存为 512m。

    3、-XX:NewRatio=4:设置老年代与年轻内存比例为 4:1。

    4、-XX:SurvivorRatio=8:设置新生代 Eden 和 Survivor 比例为 8:1。

    5、–XX:+UseParNewGC:指定使用 ParNew + Serial Old 垃圾回收器组合。

    6、-XX:+UseParallelOldGC:指定使用 ParNew + ParNew Old 垃圾回收器组合。

    7、-XX:+UseConcMarkSweepGC:指定使用 CMS + Serial Old 垃圾回收器组合。

    8、-XX:+PrintGC:开启打印 GC 信息。

    9、-XX:+PrintGCDetails:开启打印 GC 详细信息。

    更多相关内容
  • - 了解下我们为什么要学习JVM优化 - 掌握jvm的运行参数以及参数的设置 - 掌握jvm的内存模型(堆内存) - 掌握jamp命令的使用以及通过MAT工具进行分析 - 掌握定位分析内存溢出的方法 - 掌握jstack命令的使用 - 掌握...
  • JVM 优化经验总结

    千次阅读 2020-09-21 13:10:50
    一、JVM架构图 1. JVM总体概述 JVM总体上是由类装载子系统(ClassLoader)、运行时数据区、执行引擎、内存回收这四个部分组成。其中我们最为关注的运行时数据区,也就是JVM的内存部分则是由方法区(Method Area)...

    一、JVM架构图

    在这里插入图片描述

    1. JVM总体概述

    在这里插入图片描述

    JVM总体上是由类装载子系统(ClassLoader)、运行时数据区、执行引擎、内存回收这四个部分组成。其中我们最为关注的运行时数据区,也就是JVM的内存部分则是由方法区(Method Area)、JAVA堆(Heap)、虚拟机栈(Stack)、程序计数器、本地方法栈这几部分组成;除此以外,在概念中还有一个直接内存的概念,事实上这部分内存并不属于虚拟机规范中定义的内存区域,但是因为在JDK1.4+后新加的NIO类,以及JDK1.8+后的Metaspace的关系,所以在讨论JVM时也经常会被放到一起讨论。

    2 .JVM内存概述

    各内存部分的功能及具体组成部分,总结如下:

    在这里插入图片描述

    二、 详解

    1. 栈(线程私有)

    也是线程私有的。
    每个方法在执行的时候也会创建一个栈帧,存储了局部变量,操作数,动态链接,方法返回地址。
    每个方法从调用到执行完毕,对应一个栈帧在虚拟机栈中的入栈和出栈。
    通常所说的栈,一般是指在虚拟机栈中的局部变量部分。
    局部变量所需内存在编译期间完成分配,
    如果线程请求的栈深度大于虚拟机所允许的深度,则StackOverflowError。
    如果虚拟机栈可以动态扩展,扩展到无法申请足够的内存,则OutOfMemoryError。

    1.1 栈图

    在这里插入图片描述

    在这里插入图片描述

    1.2 栈中的名词解释:

    1.2.1.局部变量表:

    存放编译期可知的各种基本数据类型、对象引用类型和returnAddress类型(指向一条字节码指令的地址:函数返回地址)。
    long、double占用两个局部变量控件Slot。
    局部变量表所需的内存空间在编译期确定,当进入一个方法时,方法在栈帧中所需要分配的局部变量控件是完全确定的,不可动态改变大小。
    异常:线程请求的栈帧深度大于虚拟机所允许的深度—StackOverFlowError,如果虚拟机栈可以动态扩展(大部分虚拟机允许动态扩展,也可以设置固定大小的虚拟机栈),但是无法申请到足够的内存—OutOfMemorError。

    1.2.2 操作数栈:

    后进先出LIFO,最大深度由编译期确定。栈帧刚建立使,操作数栈为空,执行方法操作时,操作数栈用于存放JVM从局部变量表复制的常量或者变量,提供提取,及结果入栈,也用于存放调用方法需要的参数及接受方法返回的结果。
    操作数栈可以存放一个jvm中定义的任意数据类型的值。
    在任意时刻,操作数栈都一个固定的栈深度,基本类型除了long、double占用两个深度,其它占用一个深度

    1.2.3 动态连接:

    每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接。Class文件的常量池中存在有大量的符号引用,字节码中的方法调用指令就以常量池中指向方法的符号引用为参数。这些符号引用,一部分会在类加载阶段或第一次使用的时候转化为直接引用(如final、static域等),称为静态解析,另一部分将在每一次的运行期间转化为直接引用,这部分称为动态连接。

    1.2.4 方法返回地址:

    当一个方法被执行后,有两种方式退出该方法:执行引擎遇到了任意一个方法返回的字节码指令或遇到了异常,并且该异常没有在方法体内得到处理。无论采用何种退出方式,在方法退出之后,都需要返回到方法被调用的位置,程序才能继续执行。方法返回时可能需要在栈帧中保存一些信息,用来帮助恢复它的上层方法的执行状态。一般来说,方法正常退出时,调用者的PC计数器的值就可以作为返回地址,栈帧中很可能保存了这个计数器值,而方法异常退出时,返回地址是要通过异常处理器来确定的,栈帧中一般不会保存这部分信息。
    方法退出的过程实际上等同于把当前栈帧出栈,因此退出时可能执行的操作有:恢复上层方法的局部变量表和操作数栈,如果有返回值,则把它压入调用者栈帧的操作数栈中,调整PC计数器的值以指向方法调用指令后面的一条指令。

    2.方法区(元空间)(线程共享)

    被所有方法线程共享的一块内存区域。
    用于存储已经被虚拟机加载的类信息,常量,静态变量等。
    这个区域的内存回收目标主要针对常量池的回收和堆类型的卸载。

    2.1元数据区域

    元数据区域取代了1.7版本及以前的永久代。元数据和永久代本质上都时方法区的实现。方法区皴法虚拟机加载的类型西,静态变量,常量数据。
    参数设置:-XX:MetaspaceSize=18m
    -XX:MaxMetaspaceSize=60m

    3.本地方法栈(线程私有)

    和虚拟机栈类似,主要为虚拟机使用到的Native方法服务。也会抛出StackOverflowError 和OutOfMemoryError。

    4.程序计数器(线程私有)

    是当前线程锁执行字节码的行号治时期,每条线程都有一个独立的程序计数器,这类内存也称为“线程私有”的内存。正在执行java方法的话,计数器记录的是虚拟机字节码指令的地址(当前指令的地址)。如果是Natice方法,则为空。

    5. 堆(线程共享)

    被所有线程共享的一块内存区域,在虚拟机启动的时候创建,用于存放对象实例。
    对可以按照可扩展来实现(通过-Xmx 和-Xms 来控制)
    当队中没有内存可分配给实例,也无法再扩展时,则抛出OutOfMemoryError异常
    堆时JVM内存占用最大,管理最复杂的一个区域。唯一的途径就是存放对象实例:所有的对象实例以及数组都在堆上进行分配。jdk1.7以后,字符串常量从永久代中剥离出来,存放在堆中。堆具有进一步的内存划分。按照GC分代手机角度划分。
    在这里插入图片描述
    默认值:
    老年代:2/3的堆空间
    年轻代:1/3的堆空间
    eden区:8/10 的年轻代
    survivor0: 1/10 的年轻代
    survivor1:1/10的年轻代

    正确理解并发问题

    在了解了JVM结构,特别是内存结构后,我们再说说并发问题产生的原因。在上面的内容中我们分析了Java堆、Java栈,知道Java堆存储的是对象,而Java栈内存是方法执行时所需要的局部变量,其中就包括堆中对象的引用,如果多个线程同时修改堆中同一引用对象的数据,就可能会产生并发问题,导致多个线程中某些线程得到的数据值与实际值不符,造成脏数据。

    那么这种问题为什么会发生呢?

    实际上,线程操作堆中共享对象数据时并不是直接操作对象所在的那块内存,这里称之为主内存;而是将对象拷贝到线程私有的工作内存中进行更新,完成后再将最新的数据值同步回主内存,而多个线程在同一时刻将一个对象的值改得七七八八,然后再同时同步给对象所在的内存区域,那么以谁更新的为准就成了问题了。

    所以,为了防止这种情况出现,Java提供了同步机制,确保各个线程按照一定的机制同一时刻只能运行一个线程更新主内存的值。

    具体逻辑示意图如下:

    在这里插入图片描述
    注意,这里所讲的主内存、工作内存与Java内存区域中的Java堆、栈内存、方法区等并不是同一个层次的内存划分。如果勉强类比,从变量、主内存、工作内存的定义来看,主内存主要对应于Java堆中对象实例数据部分,而工作内存则对应于虚拟机栈中使用的部分内存区域;从更低层次类比,主内存就直接对应于物理硬件的内存,而为了获取更好的运行速度,虚拟机(甚至是硬件系统本身的优化措施)可能会让内存优先存储于寄存器和高速缓存中,因为程序运行时主要访问读写的是工作内存。

    而主内存与工作内存之间具体的交互协议,即一个变量如何从主内存拷贝到工作内存、如何从工作内存同步回主内存之间的实现细节,Java内存模型中定义了8种操作来完成。

    而且还规定在执行上述8种基本操作时必须满足如下规则:

    • 不允许read和load、store和write操作之一单独出现,即不允许一个变量从主内存读取了但工作内存不接受,或者从工作内存发起了回写了但主内存不接受的情况出现。

    • 不允许一个线程丢弃它的最近的assign操作,即变量在工作内存中改变了之后必须把该变化同步回主内存。

    • 不允许一个线程无原因地(没有发生任何assign操作)把数据从线程的工作内存同步回主内存中。

    • 一个新的变量只能在主内存中“诞生”,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量,换句话说,就是对一个变量实施use、store操作之前,必须先执行过了assign和load操作。

    • 一个变量在同一时刻只允许一条线程对其进行lock操作,但lock操作可以被同一条线程重复执行多次,多次执行lock后,只有执行相同次数的unlock操作,变量才会被解锁。

    • 如果对一个变量执行lock操作,那将会清空工作内存中此变量的值,在执行引擎使用这个变量前,需要重新执行load或assign操作初始化变量的值。

    • 如果一个变量事先没有被lock操作锁定,那就不允许对它执行unlock操作,也不允许去unlock一个被其他线程锁定住的变量。

    • 对一个变量执行unlock操作之前,必须先把此变量同步回主内存中(执行store、write操作)。

    以上8种内存访问操作以及上述规则限定,再加上volatile的一些特殊规定以及final不可变特性,就已经完成确定了JAVA程序中那些内存访问操作在并发下是安全的!

    三、 GC

    Java堆是GC回收的“重点区域”。堆中基本存放着所有对象实例,gc进行回收前,第一件事就是确认哪些对象存活,哪些死去[即不可能再被引用]

    1.堆的回收区域

    为了高效的回收,jvm将堆分为三个区域
    1.新生代(Young Generation)NewSize和MaxNewSize分别可以控制年轻代的初始大小和最大的大小
    2.老年代(Old Generation)
    3.永久代(Permanent Generation)【1.8以后采用元空间,就不在堆中了】

    判断对象是否存活算法

    2.引用计数算法

    早期判断对象是否存活大多都是以这种算法,这种算法判断很简单,简单来说就是给对象添加一个引用计数器,每当对象被引用一次就加1,引用失效时就减1。当为0的时候就判断对象不会再被引用。
    优点:实现简单效率高,被广泛使用与如python何游戏脚本语言上。
    缺点:难以解决循环引用的问题,就是假如两个对象互相引用已经不会再被其它其它引用,导致一直不会为0就无法进行回收。

    3.可达性分析算法

    目前主流的商用语言[如java、c#]采用的是可达性分析算法判断对象是否存活。这个算法有效解决了循环利用的弊端。
    它的基本思路是通过一个称为“GC Roots”的对象为起始点,搜索所经过的路径称为引用链,当一个对象到GC Roots没有任何引用跟它连接则证明对象是不可用的。

    在这里插入图片描述

    1)可作为GC Roots的对象有四种

    ①虚拟机栈(栈桢中的本地变量表)中的引用的对象,就是平时所指的java对象,存放在堆中。
    ②方法区中的类静态属性引用的对象,一般指被static修饰引用的对象,加载类的时候就加载到内存中。
    ③方法区中的常量引用的对象,
    ④本地方法栈中JNI(native方法)引用的对象

    即使可达性算法中不可达的对象,也不是一定要马上被回收,还有可能被抢救一下。网上例子很多,基本上和深入理解JVM一书讲的一样对象的生存还是死亡
    要真正宣告对象死亡需经过两个过程。
    1.可达性分析后没有发现引用链
    2.查看对象是否有finalize方法,如果有重写且在方法内完成自救[比如再建立引用],还是可以抢救一下,注意这边一个类的finalize只执行一次,这就会出现一样的代码第一次自救成功第二次失败的情况。[如果类重写finalize且还没调用过,会将这个对象放到一个叫做F-Queue的序列里,这边finalize不承诺一定会执行,这么做是因为如果里面死循环的话可能会时F-Queue队列处于等待,严重会导致内存崩溃,这是我们不希望看到的。]

    更详细了解 参考:
    https://www.jianshu.com/p/76959115d486

    四、JVM内存调优

    .监控GC的状态
    对JVM内存的系统级的调优主要的目的是减少GC的频率和Full GC的次数。

    1.Full GC

    会对整个堆进行整理,包括Young、Tenured和Perm。Full GC因为需要对整个堆进行回收,所以比较慢,因此应该尽可能减少Full GC的次数。

    2.导致Full GC的原因

    1)年老代(Tenured)被写满

    调优时尽量让对象在新生代GC时被回收、让对象在新生代多存活一段时间和不要创建过大的对象及数组避免直接在旧生代创建对象 。

    2)持久代Pemanet Generation空间不足

    增大Perm Gen空间,避免太多静态对象 , 控制好新生代和旧生代的比例

    3)System.gc()被显示调用

    垃圾回收不要手动触发,尽量依靠JVM自身的机制

    在对JVM调优的过程中,很大一部分工作就是对于FullGC的调节,下面详细介绍对应JVM调优的方法和步骤。

    3.JVM性能调优方法和步骤

    1).监控GC的状态

    使用各种JVM工具,查看当前日志,分析当前JVM参数设置,并且分析当前堆内存快照和gc日志,根据实际的各区域内存划分和GC执行时间,觉得是否进行优化。

    举一个例子: 系统崩溃前的一些现象:

    每次垃圾回收的时间越来越长,由之前的10ms延长到50ms左右,FullGC的时间也有之前的0.5s延长到4、5s

    FullGC的次数越来越多,最频繁时隔不到1分钟就进行一次FullGC

    年老代的内存越来越大并且每次FullGC后年老代没有内存被释放

    之后系统会无法响应新的请求,逐渐到达OutOfMemoryError的临界值,这个时候就需要分析JVM内存快照dump。

    2).生成堆的dump文件

    通过JMX的MBean生成当前的Heap信息,大小为一个3G(整个堆的大小)的hprof文件,如果没有启动JMX可以通过Java的jmap命令来生成该文件。

    3).分析dump文件

    打开这个3G的堆信息文件,显然一般的Window系统没有这么大的内存,必须借助高配置的Linux,几种工具打开该文件:

    Visual VM

    IBM HeapAnalyzer

    JDK 自带的Hprof工具

    Mat(Eclipse专门的静态内存分析工具)推荐使用

    备注:文件太大,建议使用Eclipse专门的静态内存分析工具Mat打开分析。

    4).分析结果,判断是否需要优化

    如果各项参数设置合理,系统没有超时日志出现,GC频率不高,GC耗时不高,那么没有必要进行GC优化,如果GC时间超过1-3秒,或者频繁GC,则必须优化。

    注:如果满足下面的指标,则一般不需要进行GC:

    Minor GC执行时间不到50ms;

    Minor GC执行不频繁,约10秒一次;

    Full GC执行时间不到1s;

    Full GC执行频率不算频繁,不低于10分钟1次;

    5).调整GC类型和内存分配

    如果内存分配过大或过小,或者采用的GC收集器比较慢,则应该优先调整这些参数,并且先找1台或几台机器进行beta,然后比较优化过的机器和没有优化的机器的性能对比,并有针对性的做出最后选择。

    6).不断的分析和调整

    通过不断的试验和试错,分析并找到最合适的参数,如果找到了最合适的参数,则将这些参数应用到所有服务器。

    4.cms参数优化步流程

    下面我再继续介绍下JVM的关键参数配置(仅用于参考)。

    JVM调优参数参考

    1).针对JVM堆的设置

    一般可以通过-Xms -Xmx限定其最小、最大值,为了防止垃圾收集器在最小、最大之间收缩堆而产生额外的时间,通常把最大、最小设置为相同的值;

    2).年轻代和年老代将根据默认的比例

    年轻代和年老代将根据默认的比例(1:2)分配堆内存, 可以通过调整二者之间的比率NewRadio来调整二者之间的大小,也可以针对回收代。

    比如年轻代,通过 -XX:newSize -XX:MaxNewSize来设置其绝对大小。同样,为了防止年轻代的堆收缩,我们通常会把-XX:newSize -XX:MaxNewSize设置为同样大小。

    3).年轻代和年老代设置多大才算合理

    ① 更大的年轻代必然导致更小的年老代,大的年轻代会延长普通GC的周期,但会增加每次GC的时间;小的年老代会导致更频繁的Full GC

    ② 更小的年轻代必然导致更大年老代,小的年轻代会导致普通GC很频繁,但每次的GC时间会更短;大的年老代会减少Full GC的频率

    如何选择应该依赖应用程序对象生命周期的分布情况: 如果应用存在大量的临时对象,应该选择更大的年轻代;如果存在相对较多的持久对象,年老代应该适当增大。但很多应用都没有这样明显的特性。

    在抉择时应该根 据以下两点:

    • 本着Full GC尽量少的原则,让年老代尽量缓存常用对象,JVM的默认比例1:2也是这个道理 。

    • 通过观察应用一段时间,看其他在峰值时年老代会占多少内存,在不影响Full GC的前提下,根据实际情况加大年轻代,比如可以把比例控制在1:1。但应该给年老代至少预留1/3的增长空间。

    4).在配置较好的机器上(比如多核、大内存),可以为年老代选择并行收集算法:

    -XX:+UseParallelOldGC 。

    5).线程堆栈的设置:

    每个线程默认会开启1M的堆栈,用于存放栈帧、调用参数、局部变量等,对大多数应用而言这个默认值太了,一般256K就足用。

    理论上,在内存不变的情况下,减少每个线程的堆栈,可以产生更多的线程,但这实际上还受限于操作系统。

    为了方便大家对于JVM有关参数有一个参照,如下:
    在这里插入图片描述

    展开全文
  • 最新Jvm面试题总结及答案【附答案解析】Jvm面试题及答案2021,Jvm面试题最新面试题,Jvm面试题新答案已经全部更新完了,有些答案是自己总结的,也有些答案是在网上搜集整理的...常见的 GC 日志开启参数包括: 1、-Xlo.

    最新Jvm面试题总结及答案【附答案解析】Jvm面试题及答案2021,Jvm面试题最新面试题,Jvm面试题新答案已经全部更新完了,有些答案是自己总结的,也有些答案是在网上搜集整理的。这些答案难免会存在一些错误,仅供大家参考。如果发现错误还望大家多多包涵,不吝赐教,谢谢~

    如果不背 Jvm面试题的答案,肯定面试会挂!

    这套Jvm面试题大全,希望对大家有帮助哈~

    博主已将以下这些面试题整理成了一个面试手册,是PDF版的

    1、如何开启和查看 GC 日志?

    常见的 GC 日志开启参数包括:

    1、 -Xloggc:filename,指定日志文件路径

    2、 -XX:+PrintGC,打印 GC 基本信息

    3、 -XX:+PrintGCDetails,打印 GC 详细信息

    4、 -XX:+PrintGCTimeStamps,打印 GC 时间戳

    5、 -XX:+PrintGCDateStamps,打印 GC 日期与时间

    6、 -XX:+PrintHeapAtGC,打印 GC 前后的堆、方法区、元空间可用容量变化

    7、 -XX:+PrintTenuringDistribution,打印熬过收集后剩余对象的年龄分布信息,有助于 MaxTenuringThreshold 参数调优设置

    8、 -XX:+PrintAdaptiveSizePolicy,打印收集器自动设置堆空间各分代区域大小、收集目标等自动调节的相关信息

    9、 -XX:+PrintGCApplicationConcurrentTime,打印 GC 过程中用户线程并发时间

    10、 -XX:+PrintGCApplicationStoppedTime,打印 GC 过程中用户线程停顿时间

    11、 -XX:+HeapDumpOnOutOfMemoryError,堆 oom 时自动 dump

    12、 -XX:HeapDumpPath,堆 oom 时 dump 文件路径

    Java 9 JVM 日志模块进行了重构,参数格式发生变化,这个需要知道。

    GC 日志输出的格式,会随着上面的参数不同而发生变化。关注各个分代的内存使用情况、垃圾回收次数、垃圾回收的原因、垃圾回收占用的时间、吞吐量、用户线程停顿时间。

    借助工具可视化工具可以更方便的分析,在线工具 GCeasy;离线版可以使用 GCViewer。

    如果现场环境不允许,可以使用 JDK 自带的 jstat 工具监控观察 GC 情况。

    2、Parallel Scavenge 收集器(多线程复制算法、高效)

    Parallel Scavenge 收集器也是一个新生代垃圾收集器,同样使用复制算法,也是一个多线程的垃圾收集器, 它重点关注的是程序达到一个可控制的吞吐量(Thoughput, CPU 用于运行用户代码的时间/CPU 总消耗时间,即吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)),高吞吐量可以最高效率地利用 CPU 时间,尽快地完成程序的运算任务,主要适用于在后台运算而不需要太多交互的任务。自适应调节策略也是 ParallelScavenge 收集器与 ParNew 收集器的一个重要区别。

    3、说下有哪些类加载器?

    Bootstrap ClassLoader(启动类加载器) Extention ClassLoader(扩展类加载器) App ClassLoader(应用类加载器)

    4、你做过 JVM 调优,说说如何查看 JVM 参数默认值?

    1、 jps -v 可以查看 jvm 进程显示指定的参数

    2、 使用 -XX:+PrintFlagsFinal 可以看到 JVM 所有参数的值

    3、 jinfo 可以实时查看和调整虚拟机各项参数

    5、什么是双亲委派机制?

    双亲委派机制的意思是除了顶层的启动类加载器以外,其余的类加载器,在加载之前,都会委派给它的父加载器进行加载。这样一层层向上传递,直到祖先们都无法胜任,它才会真正的加载。

    6、内存溢出和内存泄漏的区别?

    内存溢出 OutOfMemory,指程序在申请内存时,没有足够的内存空间供其使用。

    内存泄露 Memory Leak,指程序在申请内存后,无法释放已申请的内存空间,内存泄漏最终将导致内存溢出。

    7、强引用、软引用、弱引用、虚引用是什么,有什么区别?

    1、 强引用,就是普通的对象引用关系,如 String s = new String("ConstXiong")

    2、 软引用,用于维护一些可有可无的对象。只有在内存不足时,系统则会回收软引用对象,如果回收了软引用对象之后仍然没有足够的内存,才会抛出内存溢出异常。SoftReference 实现

    3、 弱引用,相比软引用来说,要更加无用一些,它拥有更短的生命周期,当 JVM 进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。WeakReference 实现

    4、 虚引用是一种形同虚设的引用,在现实场景中用的不是很多,它主要用来跟踪对象被垃圾回收的活动。PhantomReference 实现

    8、垃圾回收的优点和原理。说说2种回收机制

    Java 语言中一个显著的特点就是引入了垃圾回收机制,使 C++ 程序员最头疼的内存管理的问题迎刃而解,它使得 Java 程序员在编写程序的时候不再需要考虑内存管理。由于有个垃圾回收机制,Java 中的对象不再有“作用域”的概念,只有对象的引用才有"作用域"。垃圾回收可以有效的防止内存泄露,有效的使用可以使用的内存。垃圾回收器通常是作为一个单独的低级别的线程运行,不可预知的情况下对内存堆中已经死亡的或者长时间没有使用的对象进行清楚和回收,程序员不能实时的调用垃圾回收器对某个对象或所有对象进行垃圾回收。

    回收机制有分代复制垃圾回收和标记垃圾回收,增量垃圾回收。

    9、说一下垃圾分代收集的过程

    分为新生代和老年代,新生代默认占总空间的 1/3,老年代默认占 2/3。

    新生代使用复制算法,有 3 个分区:Eden、To Survivor、From Survivor,它们的默认占比是 8:1:1。

    当新生代中的 Eden 区内存不足时,就会触发 Minor GC,过程如下:

    1、 在 Eden 区执行了第一次 GC 之后,存活的对象会被移动到其中一个 Survivor 分区;

    2、 Eden 区再次 GC,这时会采用复制算法,将 Eden 和 from 区一起清理,存活的对象会被复制到 to 区;

    3、 移动一次,对象年龄加 1,对象年龄大于一定阀值会直接移动到老年代

    4、 Survivor 区相同年龄所有对象大小的总和 (Survivor 区内存大小 * 这个目标使用率)时,大于或等于该年龄的对象直接进入老年代。其中这个使用率通过 -XX:TargetSurvivorRatio 指定,默认为 50%

    5、 Survivor 区内存不足会发生担保分配

    6、 超过指定大小的对象可以直接进入老年代

    Major GC,指的是老年代的垃圾清理,但并未找到明确说明何时在进行Major GC

    FullGC,整个堆的垃圾收集,触发条件:

    1、 每次晋升到老年代的对象平均大小>老年代剩余空间

    2、 MinorGC后存活的对象超过了老年代剩余空间

    3、 元空间不足

    4、 System.gc() 可能会引起

    5、 CMS GC异常,promotion failed:MinorGC时,survivor空间放不下,对象只能放入老年代,而老年代也放不下造成;concurrent mode failure:GC时,同时有对象要放入老年代,而老年代空间不足造成

    6、 堆内存分配很大的对象

    10、JVM 运行时内存

    Java 堆从 GC 的角度还可以细分为: 新生代(Eden 区、 From Survivor 区和 To Survivor 区)和老年代。

    新生代

    是用来存放新生的对象。一般占据堆的 1/3 空间。由于频繁创建对象,所以新生代会频繁触发MinorGC 进行垃圾回收。新生代又分为 Eden区、 ServivorFrom、 ServivorTo 三个区。

    Eden 区

    Java 新对象的出生地(如果新创建的对象占用内存很大,则直接分配到老年代)。当 Eden 区内存不够的时候就会触发 MinorGC,对新生代区进行一次垃圾回收。

    ServivorFrom

    上一次 GC 的幸存者,作为这一次 GC 的被扫描者。

    ServivorTo

    保留了一次 MinorGC 过程中的幸存者。

    MinorGC 的过程(复制->清空->互换)

    MinorGC 采用复制算法。

    eden、 servicorFrom 复制到 ServicorTo,年龄+1

    首先,把 Eden 和 ServivorFrom 区域中存活的对象复制到 ServicorTo 区域(如果有对象的年龄以及达到了老年的标准,则赋值到老年代区),同时把这些对象的年龄+1(如果 ServicorTo 不够位置了就放到老年区);

    清空 eden、 servicorFrom

    然后,清空 Eden 和 ServicorFrom 中的对象;

    ServicorTo 和 ServicorFrom 互换

    最后, ServicorTo 和 ServicorFrom 互换,原 ServicorTo 成为下一次 GC 时的 ServicorFrom区。

    11、ZGC 了解吗?

    JDK11 中加入的具有实验性质的低延迟垃圾收集器,目标是尽可能在不影响吞吐量的前提下,实现在任意堆内存大小都可以把停顿时间限制在 10ms 以内的低延迟。

    基于 Region 内存布局,不设分代,使用了读屏障、染色指针和内存多重映射等技术实现可并发的标记-整理,以低延迟为首要目标。

    ZGC 的 Region 具有动态性,是动态创建和销毁的,并且容量大小也是动态变化的。

    12、safepoint是什么?

    STW并不会只发生在内存回收的时候。现在程序员这么卷,碰到几次safepoint的问题几率也是比较大的。

    当发生GC时,用户线程必须全部停下来,才可以进行垃圾回收,这个状态我们可以认为JVM是安全的(safe),整个堆的状态是稳定的。

    如果在GC前,有线程迟迟进入不了safepoint,那么整个JVM都在等待这个阻塞的线程,造成了整体GC的时间变长。

    13、JVM 提供的常用工具

    jps:

    用来显示本地的 Java 进程,可以查看本地运行着几个 Java 程序,并显示他们的进程号。 命令格式:jps

    jinfo:

    运行环境参数:Java System 属性和 JVM 命令行参数,Java class path 等信息。 命令格式:jinfo 进程 pid

    jstat:

    监视虚拟机各种运行状态信息的命令行工具。 命令格式:jstat -gc 123 250 20

    jstack:

    可以观察到 JVM 中当前所有线程的运行情况和线程当前状态。 命令格式:jstack 进程 pid

    jmap:

    观察运行中的 JVM 物理内存的占用情况(如:产生哪些对象,及其数量)。 命令格式:jmap [option] pid

    14、CMS 收集器(多线程标记清除算法)

    Concurrent mark sweep(CMS)收集器是一种年老代垃圾收集器,其最主要目标是获取最短垃圾回收停顿时间, 和其他年老代使用标记-整理算法不同,它使用多线程的标记-清除算法。最短的垃圾收集停顿时间可以为交互比较高的程序提高用户体验。CMS 工作机制相比其他的垃圾收集器来说更复杂。整个过程分为以下 4 个阶段:

    初始标记

    只是标记一下 GC Roots 能直接关联的对象,速度很快,仍然需要暂停所有的工作线程。

    并发标记

    进行 GC Roots 跟踪的过程,和用户线程一起工作,不需要暂停工作线程。

    重新标记

    为了修正在并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,仍然需要暂停所有的工作线程。

    并发清除

    清除 GC Roots 不可达对象,和用户线程一起工作,不需要暂停工作线程。由于耗时最长的并发标记和并发清除过程中,垃圾收集线程可以和用户现在一起并发工作, 所以总体上来看CMS 收集器的内存回收和用户线程是一起并发地执行。

    15、对象都是优先分配在年轻代上的吗?

    不是。当新生代内存不够时,老年代分配担保。而大对象则是直接在老年代分配。

    16、有哪些 GC 算法?

    标记-清除算法

    分为标记和清除阶段,首先从每个 GC Roots 出发依次标记有引用关系的对象,最后清除没有标记的对象。

    执行效率不稳定,如果堆包含大量对象且大部分需要回收,必须进行大量标记清除,导致效率随对象数量增长而降低。

    存在内存空间碎片化问题,会产生大量不连续的内存碎片,导致以后需要分配大对象时容易触发 Full GC。

    标记-复制算法

    为了解决内存碎片问题,将可用内存按容量划分为大小相等的两块,每次只使用其中一块。当使用的这块空间用完了,就将存活对象复制到另一块,再把已使用过的内存空间一次清理掉。主要用于进行新生代。

    实现简单、运行高效,解决了内存碎片问题。代价是可用内存缩小为原来的一半,浪费空间。

    HotSpot 把新生代划分为一块较大的 Eden 和两块较小的 Survivor,每次分配内存只使用 Eden 和其中一块 Survivor。垃圾收集时将 Eden 和 Survivor 中仍然存活的对象一次性复制到另一块 Survivor 上,然后直接清理掉 Eden 和已用过的那块 Survivor。HotSpot 默认Eden 和 Survivor 的大小比例是 8:1,即每次新生代中可用空间为整个新生代的 90%。

    标记-整理算法

    标记-复制算法在对象存活率高时要进行较多复制操作,效率低。如果不想浪费空间,就需要有额外空间分配担保,应对被使用内存中所有对象都存活的极端情况,所以老年代一般不使用此算法。

    老年代使用标记-整理算法,标记过程与标记-清除算法一样,但不直接清理可回收对象,而是让所有存活对象都向内存空间一端移动,然后清理掉边界以外的内存。

    标记-清除与标记-整理的差异在于前者是一种非移动式算法而后者是移动式的。如果移动存活对象,尤其是在老年代这种每次回收都有大量对象存活的区域,是一种极为负重的操作,而且移动必须全程暂停用户线程。如果不移动对象就会导致空间碎片问题,只能依赖更复杂的内存分配器和访问器解决。

    17、有什么堆外内存的排查思路?

    进程占用的内存,可以使用top命令,看RES段占用的值。如果这个值大大超出我们设定的最大堆内存,则证明堆外内存占用了很大的区域。

    使用gdb可以将物理内存dump下来,通常能看到里面的内容。更加复杂的分析可以使用perf工具,或者谷歌开源的gperftools。那些申请内存最多的native函数,很容易就可以找到。

    18、SWAP会影响性能么?

    当操作系统内存不足的时候,会将部分数据写入到SWAP交换分中,但是SWAP的性能是比较低的。如果应用的访问量较大,需要频繁申请和销毁内存,就容易发生卡顿。一般高并发场景下,会禁用SWAP。

    19、你知道哪些JVM性能调优

    设定堆内存大小

    1、 -Xmx:堆内存最大限制。设定新生代大小。新生代不宜太小,否则会有大量对象涌入老年代

    2、 -XX:NewSize:新生代大小

    3、 -XX:NewRatio 新生代和老生代占比

    4、 -XX:SurvivorRatio:伊甸园空间和幸存者空间的占比

    5、 设定垃圾回收器 年轻代用 -XX:+UseParNewGC 年老代用-XX:+UseConcMarkSweepGC

    20、你都有哪些手段用来排查内存溢出?

    (这个话题很大,可以从实践环节中随便摘一个进行总结,下面举例一个最普通的)

    你可以来一个中规中矩的回

    内存溢出包含很多种情况,我在平常工作中遇到最多的就是堆溢出。有一次线上遇到故障,重新启动后,使用jstat命令,发现Old区在一直增长。我使用jmap命令,导出了一份线上堆栈,然后使用MAT进行分析。通过对GC Roots的分析,我发现了一个非常大的HashMap对象,这个原本是有位同学做缓存用的,但是一个无界缓存,造成了堆内存占用一直上升。后来,将这个缓存改成 guava的Cache,并设置了弱引用,故障就消失了。

    这个回答不是十分出彩,但着实是常见问题,让人挑不出毛病。

    21、类加载有几个过程?

    加载、验证、准备、解析、初始化。

    22、简述Java的对象结构

    Java对象由三个部分组成:对象头、实例数据、对齐填充。

    对象头由两部分组成,第一部分存储对象自身的运行时数据:哈希码、GC分代年龄、锁标识状态、线程持有的锁、偏向线程ID(一般占32/64 bit)。第二部分是指针类型,指向对象的类元数据类型(即对象代表哪个类)。如果是数组对象,则对象头中还有一部分用来记录数组长度。

    实例数据用来存储对象真正的有效信息(包括父类继承下来的和自己定义的)

    对齐填充:JVM要求对象起始地址必须是8字节的整数倍(8字节对齐 )

    23、怎么查看服务器默认的垃圾回收器是哪一个?

    这通常会使用另外一个参数:-XX:+PrintCommandLineFlags可以打印所有的参数,包括使用的垃圾回收器。

    24、JAVA 强引用

    在 Java 中最常见的就是强引用, 把一个对象赋给一个引用变量,这个引用变量就是一个强引用。当一个对象被强引用变量引用时,它处于可达状态,它是不可能被垃圾回收机制回收的,即使该对象以后永远都不会被用到 JVM 也不会回收。因此强引用是造成 Java 内存泄漏的主要原因之一。

    25、详细介绍一下JVM内存模型

    根据 JVM 规范,JVM 内存共分为虚拟机栈、堆、方法区、程序计数器、本地方法栈五个部分。

    具体可能会聊聊jdk1.7以前的PermGen(永久代),替换成Metaspace(元空间)

    1、 原本永久代存储的数据:符号引用(Symbols)转移到了native heap;字面量(interned strings)转移到了java heap;类的静态变量(class statics)转移到了java heap

    2、 Metaspace(元空间)存储的是类的元数据信息(metadata)

    3、 元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。

    4、 替换的好处:一、字符串存在永久代中,容易出现性能问题和内存溢出。二、永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。

    26、32 位 JVM 和 64 位 JVM 的最大堆内存分别是多数?

    理论上说上 32 位的 JVM 堆内存可以到达 2^32,即 4GB,但实际上会比这个小很多。不同操作系统之间不同,如 Windows 系统大约 1.5GB,Solaris 大约3GB。64 位 JVM 允许指定最大的堆内存,理论上可以达到 2^64,这是一个非常大的数字,实际上你可以指定堆内存大小到 100GB。甚至有的 JVM,如 Azul,堆内存到 1000G 都是可能的。

    27、HashMap中的key,可以是普通对象么?需要什么注意的地方?

    Map的key和value都可以是任何类型。但要注意的是,一定要重写它的equals和hashCode方法,否则容易发生内存泄漏。

    28、你熟悉哪些垃圾收集算法?

    标记清除(缺点是碎片化) 复制算法(缺点是浪费空间) 标记整理算法(效率比前两者差) 分代收集算法(老年代一般使用“标记-清除”、“标记-整理”算法,年轻代一般用复制算法)

    29、GC 垃圾收集器

    Java 堆内存被划分为新生代和年老代两部分,新生代主要使用复制和标记-清除垃圾回收算法;年老代主要使用标记-整理垃圾回收算法,因此 java 虚拟中针对新生代和年老代分别提供了多种不同的垃圾收集器, JDK1.6 中 Sun HotSpot 虚拟机的垃圾收集器

    30、什么情况发生栈溢出?

    -Xss可以设置线程栈的大小,当线程方法递归调用层次太深或者栈帧中的局部变量过多时,会出现栈溢出错误 java.lang.StackOverflowError

    更多 JVM 面试题 60道

    01、标记清除算法( Mark-Sweep)

    02、Serial Old 收集器(单线程标记整理算法 )

    03、你都有哪些手段用来排查内存溢出?

    04、类加载为什么要使用双亲委派模式,有没有什么场景是打破了这个模式?

    05、描述一下 JVM 加载 class 文件的原理机制

    06、能够找到 Reference Chain 的对象,就一定会存活么?

    07、类加载器双亲委派模型机制?

    08、字符串常量存放在哪个区域?

    09、你知道哪些内存分配与回收策略?

    10、Java 8 为什么要将永久代(PermGen)替换为元空间(MetaSpace)呢?

    11、谈谈永久代

    12、简述Java的对象结构

    13、SWAP会影响性能么?

    14、列举一些你知道的打破双亲委派机制的例子。为什么要打破?

    15、内存溢出和内存泄漏的区别?

    16、对于JDK自带的监控和性能分析工具用过哪些?

    17、Serial 垃圾收集器(单线程、 复制算法)

    18、GC垃圾回收算法与垃圾收集器的关系?

    19、方法区/永久代(线程共享)

    20、safepoint 是什么?

    21、什么是双亲委派机制?

    22、垃圾回收的优点和原理。说说2种回收机制

    23、程序计数器(线程私有)

    24、为什么需要双亲委派模式?

    25、介绍一下类文件结构吧!

    26、哪些是 GC Roots?

    27、MinorGC、MajorGC、FullGC 什么时候发生?

    28、方法区溢出的原因?

    29、如何判断对象可以被回收

    30、Java 中堆和栈有什么区别?

    31、双亲委派机制可以被违背吗?请举例说明。

    32、类的实例化顺序

    33、栈溢出的原因?

    34、如何判断一个常量是废弃常量 ?

    35、你知道哪些垃圾收集器?

    36、本地方法区(线程私有)

    37、说说 JVM 如何执行 class 中的字节码。

    38、分代收集算法

    40、怎么查看服务器默认的垃圾回收器是哪一个?

    41、OSGI( 动态模型系统)

    42、对象是怎么从年轻代进入老年代的?

    43、对象分配内存的方式有哪些?

    44、对象在哪块内存分配?

    45、类加载的过程是什么?

    46、谈谈 JVM 中的常量池

    47、ZGC 了解吗?

    48、生产上如何配置垃圾收集器的?

    49、对象都是优先分配在年轻代上的吗?

    50、模块化编程与热插拔

    51、程序计数器

    52、常用JVM基本配置参数

    53、创建对象的过程是什么?

    54、说说你知道的几种主要的JVM参数

    55、说说CMS垃圾收集器的工作原理

    56、在老年代-标记整理算法

    57、垃圾回收器的基本原理是什么?垃圾回收器可以马上回收内存吗?有什么办法主动通知虚拟机进行垃圾回收?

    58、什么是Java虚拟机?为什么Java被称作是“平台无关的编程语言”?

    59、你都用过G1垃圾回收器的哪几个重要参数?

    60、什么是方法内联?

    如果不背 Jvm面试题的答案,肯定面试会挂!

    这套Jvm面试题大全,希望对大家有帮助哈~

    博主已将以下这些面试题整理成了一个面试手册,是PDF版的

    展开全文
  • JVM优化和垃圾回收机制

    千次阅读 2020-07-19 15:02:21
    JVM优化 Java堆 堆内存用于存放由new创建的对象和数组。在堆中分配的内存,由java虚拟机自动垃圾回收器来管理。在堆中产生了一个数组或者对象后,还可以在栈中定义一个特殊的变量,这个变量的取值等于数组或者对象在...

    一、垃圾回收机制概念

    不定时去堆内存中清理不可达对象。不可达的对象并不会马上就会直接回收, 垃圾收集器在一个Java程序中的执行是自动的,不能强制执行,即使程序员能明确地判断出有一块内存已经无用了,是应该回收的,程序员也不能强制垃圾收集器回收该内存块。程序员唯一能做的就是通过调用System.gc 方法来"建议"执行垃圾收集器,但其是否可以执行,什么时候执行却都是不可知的。这也是垃圾收集器的最主要的缺点。当然相对于它给程序员带来的巨大方便性而言,这个缺点是瑕不掩瑜的。

    1. finalize方法

    Java使用finalize()方法在垃圾收集器将对象从内存中清除出去前,做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的。它是在Object类中定义的,因此所有的类都继承了它。子类覆盖finalize()方法以整理系统资源或者执行其他清理工作。finalize()方法是在垃圾收集器删除对象之前对这个对象调用的。

    2. 判断对象是否存活算法

    1. 引用计数法

      引用计数法就是如果一个对象没有被任何引用指向,则可视之为垃圾。这种方法的缺点就是不能检测到环的存在。首先需要声明,至少主流的Java虚拟机里面都没有选用引用计数算法来管理内存。 什么是引用计数算法:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值加1;当引用失效时,计数器值减1.任何时刻计数器值为0的对象就是不可能再被使用的。那为什么主流的Java虚拟机里面都没有选用这种算法呢?其中最主要的原因是它很难解决对象之间相互循环引用的问题。

    2. 根搜索算法
      根搜索算法的基本思路就是通过一系列名为”GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。
      在Java语言中,可以作为GCRoots的对象包括下面几种:
      (1). 虚拟机栈(栈帧中的局部变量区,也叫做局部变量表)中引用的对象。
      (2). 方法区中的类静态属性引用的对象。
      (3). 方法区中常量引用的对象。
      (4). 本地方法栈中JNI(Native方法)引用的对象。

    3.垃圾回收机制策略

    1. 标记清除算法
      该算法有两个阶段。

      1. 标记阶段:找到所有可访问的对象,做个标记
      2. 清除阶段:遍历堆,把未被标记的对象回收

      该算法一般应用于老年代,因为老年代的对象生命周期比较长。

      优缺点:

      1. 优点
        • 是可以解决循环引用的问题
        • 必要时才回收(内存不足时)
      2. 缺点:
        • 回收时,应用需要挂起,也就是stop the world。
        • 标记和清除的效率不高,尤其是要扫描的对象比较多的时候
        • 会造成内存碎片(会导致明明有内存空间,但是由于不连续,申请稍微大一些的对象无法做到),
    2. 复制算法
      如果jvm使用了coping算法,一开始就会将可用内存分为两块,from域和to域, 每次只是使用from域,to域则空闲着。当from域内存不够了,开始执行GC操作,这个时候,会把from域存活的对象拷贝到to域,然后直接把from域进行内存清理。
      coping算法一般是使用在新生代中,因为新生代中的对象一般都是朝生夕死的,存活对象的数量并不多,这样使用coping算法进行拷贝时效率比较高。jvm将Heap 内存划分为新生代与老年代,又将新生代划分为Eden(伊甸园) 与2块Survivor Space(幸存者区) ,然后在Eden –>Survivor Space 以及From Survivor Space 与To Survivor Space 之间实行Copying 算法。 不过jvm在应用coping算法时,并不是把内存按照1:1来划分的,这样太浪费内存空间了。一般的jvm都是8:1。也即是说,Eden区:From区:To区域的比例是
      优缺点:

      1. 优点:在存活对象不多的情况下,性能高,能解决内存碎片和java垃圾回收算法之-标记清除 中导致的引用更新问题。
      2. 缺点: 会造成一部分的内存浪费。不过可以根据实际情况,将内存块大小比例适当调整;如果存活对象的数量比较大,coping的性能会变得很差。
    3. 标记压缩算法
      标记清除算法和标记压缩算法非常相同,但是标记压缩算法在标记清除算法之上解决内存碎片化。
      任意顺序 : 即不考虑原先对象的排列顺序,也不考虑对象之间的引用关系,随意移动对象;
      线性顺序 : 考虑对象的引用关系,例如a对象引用了b对象,则尽可能将a和b移动到一块;
      滑动顺序 : 按照对象原来在堆中的顺序滑动到堆的一端。

      优缺点:

      1. 优点:解决内存碎片问题
      2. 缺点压缩阶段,由于移动了可用对象,需要去更新引用。
    4. 分代算法
      这种算法,根据对象的存活周期的不同将内存划分成几块,新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。可以用抓重点的思路来理解这个算法。
      新生代对象朝生夕死,对象数量多,只要重点扫描这个区域,那么就可以大大提高垃圾收集的效率。另外老年代对象存储久,无需经常扫描老年代,避免扫描导致的开销。

      新生代:

      在新生代,每次垃圾收集器都发现有大批对象死去,只有少量存活,采用复制算法,只需要付出少量存活对象的复制成本就可以完成收集;可以参看我之前写的java垃圾回收算法之-coping复制。

      老年代:

      而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须“标记-清除-压缩”算法进行回收。参看java垃圾回收算法之-标记_清除压缩新创建的对象被分配在新生代,如果对象经过几次回收后仍然存活,那么就把这个对象划分到老年代。老年代区存放Young区Survivor满后触发minor GC后仍然存活的对象,当Eden区满后会将存活的对象放入Survivor区域,如果Survivor区存不下这些对象,GC收集器就会将这些对象直接存放到Old区中,如果Survivor区中的对象足够老,也直接存放到Old区中。如果Old区满了,将会触发Full GC回收整个堆内存。

    二、JVM参数配置

    JVM提供了诸多的参数进行JVM各个方面内存大小的设置,为Java应用进行优化提供了诸多的工具。

    1.常见参数

    -XX:+PrintGC 每次触发GC的时候打印相关日志
    -XX:+UseSerialGC 串行回收
    -XX:+PrintGCDetails 更详细的GC日志
    -Xms 堆初始值
    -Xmx 堆最大可用值
    -Xmn 新生代堆最大可用值
    -XX:SurvivorRatio 用来设置新生代中eden空间和from/to空间的比例.
    -XX:NewRatio 配置新生代与老年代占比 1:2
    含以-XX:SurvivorRatio=eden/from=den/to
    总结:在实际工作中,我们可以直接将初始的堆大小与最大堆大小相等,
    这样的好处是可以减少程序运行时垃圾回收次数,从而提高效率。
    -XX:SurvivorRatio 用来设置新生代中eden空间和from/to空间的比例.

    2. 堆内存大小配置

    Java应用最大可用内存为20M, 初始内存为5M

    -Xmx20m -Xms5m 
    

    打印信息:

    System.out.println("最大内存"+Runtime.getRuntime().maxMemory() / 1024.0 / 1024 + "M");
    System.out.println("可用内存"+Runtime.getRuntime().freeMemory() / 1024.0 / 1024 + "M");
    System.out.println("已经使用内存"+Runtime.getRuntime().totalMemory() / 1024.0 / 1024 + "M");
    

    3. 设置新生代比例参数

    堆内存初始化值20m,堆内存最大值20m,新生代最大值可用1m,eden空间和from/to空间的比例为2/1, 串行回收。

    -Xms20m -Xmx20m -Xmn1m -XX:SurvivorRatio=2 -XX:+PrintGCDetails -XX:+UseSerialGC
    

    测试:

    byte[] b = null;
    for (int i = 0; i < 10; i++) {
    b = new byte[1 * 1024 * 1024];
    }
    

    4. 设置新生代与老年代比例参数

    堆内存初始化值20m,堆内存最大值20m,新生代最大值可用1m,eden空间和from/to空间的比例为2/1,串行回收。

    -Xms20m -Xmx20m -XX:SurvivorRatio=2 -XX:+PrintGCDetails -XX:+UseSerialGC-XX:NewRatio=2
    

    三、内存溢出解决

    1.Java堆溢出

    java.lang.OutOfMemoryError: Java heap space 堆内存溢出
    设置堆内存大小:

     -Xms1m -Xmx10m -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError
    
    List<Object> listObject = new ArrayList<>();
    for (int i = 0; i < 10; i++) {
    System.out.println("i:" + i);
    Byte[] bytes = new Byte[1 * 1024 * 1024];
    listObject.add(bytes);
    }
    

    解决方法:根据服务器配置适当增大堆内存大小。

    2. 虚拟机栈溢出

    java.lang.StackOverflowError 栈内存溢出
    栈溢出 产生于递归调用,循环遍历是不会的,但是循环方法里面产生递归调用, 也会发生栈溢出。
    解决办法:设置线程最大调用深度

    -Xss5m 设置最大调用深度
    

    四、内存溢出与内存泄漏区别

    Java内存泄漏就是没有及时清理内存垃圾,导致系统无法再给你提供内存资源(内存资源耗尽);而Java内存溢出就是你要求分配的内存超出了系统能给你的,系统不能满足需求,于是产生溢出。内存溢出,这个好理解,说明存储空间不够大。就像倒水倒多了,从杯子上面溢出了来了一样。内存泄漏,原理是,使用过的内存空间没有被及时释放,长时间占用内存,最终导致内存空间不足,而出现内存溢出。

    五、垃圾收集器

    串行与并行收集器:

    1. 串行回收: JDK1.5前的默认算法 缺点是只有一个线程,执行垃圾回收时程序停止的时间比较长
    2. 并行回收: 多个线程执行垃圾回收适合于吞吐量的系统,回收时系统会停止运行

    1. serial收集器

    串行收集器是最古老,最稳定以及效率高的收集器,可能会产生较长的停顿,只使用一个线程去回收。新生代、老年代使用串行回收;新生代复制算法、老年代标记-压缩;垃圾收集的过程中会Stop The World(服务暂停)一个单线程的收集器,在进行垃圾收集时候,必须暂停其他所有的工作线程直到它收集结束。
    特点:CPU利用率最高,停顿时间即用户等待时间比较长。
    适用场景:小型应用
    通过JVM参数-XX:+UseSerialGC可以使用串行垃圾回收器。

    2. ParNew收集器

    ParNew收集器其实就是Serial收集器的多线程版本。新生代并行,老年代串行;新生代复制算法、老年代标记-压缩
    参数控制:-XX:+UseParNewGC ParNew收集器
    -XX:ParallelGCThreads 限制线程数量

    3. parallel 收集器

    Parallel Scavenge收集器类似ParNew收集器,Parallel收集器更关注系统的吞吐量。可以通过参数来打开自适应调节策略,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或最大的吞吐量;也可以通过参数控制GC的时间不大于多少毫秒或者比例;新生代复制算法、老年代标记-压缩采用多线程来通过扫描并压缩堆
    特点:停顿时间短,回收效率高,对吞吐量要求高。
    适用场景:大型应用,科学计算,大规模数据采集等。
    通过JVM参数 XX:+USeParNewGC 打开并发标记扫描垃圾回收器。

    4. cms收集器

    CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。目前很大一部分的Java应用都集中在互联网站或B/S系统的服务端上,这类应用尤其重视服务的响应速度,希望系统停顿时间最短,以给用户带来较好的体验。
    从名字(包含“Mark Sweep”)上就可以看出CMS收集器是基于“标记-清除”算法实现的,它的运作过程相对于前面几种收集器来说要更复杂一些,整个过程分为4个步骤,包括:
    初始标记(CMS initial mark)
    并发标记(CMS concurrent mark)
    重新标记(CMS remark)
    并发清除(CMS concurrent sweep)
    其中初始标记、重新标记这两个步骤仍然需要“Stop The World”。初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快,并发标记阶段就是进行GC Roots Tracing的过程,而重新标记阶段则是为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短。
    由于整个过程中耗时最长的并发标记和并发清除过程中,收集器线程都可以与用户线程一起工作,所以总体上来说,CMS收集器的内存回收过程是与用户线程一起并发地执行。老年代收集器(新生代使用ParNew)
    优点:并发收集、低停顿
    缺点:产生大量空间碎片、并发阶段会降低吞吐量

    采用“标记-清除”算法实现,使用多线程的算法去扫描堆,对发现未使用的对象进行回收。
    (1)初始标记
    (2)并发标记
    (3)并发预处理
    (4)重新标记
    (5)并发清除
    (6)并发重置
    特点:响应时间优先,减少垃圾收集停顿时间
    适应场景:大型服务器等。
    通过JVM参数 -XX:+UseConcMarkSweepGC设置

    5. g1收集器

    在G1中,堆被划分成 许多个连续的区域(region)。采用G1算法进行回收,吸收了CMS收集器特点。
    特点:支持很大的堆,高吞吐量
    –支持多CPU和垃圾回收线程
    –在主线程暂停的情况下,使用并行收集
    –在主线程运行的情况下,使用并发收集
    实时目标:可配置在N毫秒内最多只占用M毫秒的时间进行垃圾回收
    通过JVM参数 -XX:+UseG1GC 使用G1垃圾回收器
    注意: 并发是指一个处理器同时处理多个任务。
    并行是指多个处理器或者是多核的处理器同时处理多个不同的任务。
    并发是逻辑上的同时发生(simultaneous),而并行是物理上的同时发生。

    展开全文
  • 本章讲解JVM优化常见的工具的使用 方法 1.概念 在JVM优化的道路上,任重道远,我们需要借助JDK本身的工具进行分析。 2.工具详情 下面介绍的小工具均在JAVA_HOME/bin下,我的路径是这样的。 1)jps:JVM ...
  • jvm常用调优方式

    2018-04-17 10:14:23
    jvm常用调优方式jvm常用调优方式jvm常用调优方式jvm常用调优方式
  • Sun Classic VM 世界上第一款商用java虚拟机,JDK1.4时被完全淘汰; 只有解释器 Exact VM Exact Memory Management:准确式内存管理。...通过编译器与解释器协同工作,在最优化的程序响应时间与最佳执
  • Jboss_JVM优化

    2013-05-30 12:55:54
    Jboss中间件下jvm参数调优配置 Jvm常见调优配置汇总
  • 现在大家用得最多的 Java 版本是 Java 8,如果你的公司比较保守,那么使用较多...由于 JVM 一直处在变化之中,所以一些参数的配置并不总是有效的。有时候你加入一个参数,“感觉上”运行速度加快了,但通过 -XX:+Print
  • JVM参数类型与设置 安装JDK,配置系统环境参数后,输入java -help,红框标记的就是我们设置 JVM参数的区域。 [-options]意思是不强制要求设置,java命令支持各种参数设置,这些参数可以分为以下类别 标准参数:JVM...
  • jvm优化 JVM 内存大小设置

    千次阅读 2022-04-19 16:32:58
    Tomcat的内存溢出本质就是JVM内存溢出,所以在本文开始时,应该先对Java JVM有关内存方面的知识进行详细介绍。 一、Java JVM内存介绍 JVM管理两种类型的内存,堆和非堆。按照官方的说法:“Java 虚拟机具有一个堆,...
  • 一、代码层面 二、数据库层面 三、JVM层面 四、缓存层面 五、实现异步 开辟新线程 整理自:常见性能优化策略的总结 - 知乎
  • JVM之几种常见的JIT优化

    千次阅读 2022-03-11 19:00:51
    一、公共子表达式消除(经典的JIT优化技术) 1、概述 如果一个表达式E已经经过计算,并且从先前的计算到本次计算E中的所有变量值保持不变,那么E在此次计算中...package jvm.study; /** * @author ghCode * @E...
  • 主要介绍了Java虚拟机JVM性能优化(二):编译器,本文先是讲解了不同种类的编译器,并对客户端编译,服务器端编译器和多层编译的运行性能进行了对比,然后给出了几种常见JVM优化方法,需要的朋友可以参考下
  • Tomcat的内存溢出本质就是JVM内存溢出,所以在本文开始时,应该先对Java JVM有关内存方面的知识进行详细介绍。 一、Java JVM内存介绍 JVM管理两种类型的内存,堆和非堆。按照官方的说法:“Java 虚拟机具有一个堆,...
  • 待更新
  • JVM参数分析、配置优化

    千次阅读 2022-03-19 14:49:47
    JVM参数分析、配置优化
  • 我们平常开发的时候肯定遇到过两个异常内存溢出(OutOfMemoryError)和栈溢出(StackOverflowError),这两个异常是怎么出现的?为什么会出现?怎么解决呢?这就需要我们对JVM的内存结构有个详细的认知了。
  • 总结了JVM一些经典面试题,分享出我自己的解题思路,希望对大家有帮助,有哪里你觉得不正确的话,欢迎指出,后续有空会更新。 1.什么情况下会发生栈内存溢出。 思路: 描述栈定义,再描述为什么会溢出,再说明一下...
  • 六个命令行排查工具 ...jps(JVM Process Status tool,虚拟机进程状况工具),用于列出正在运行的 JVM 的 LVMID(Local Virtual Machine IDentifier,本地虚拟机唯一 ID),以及 JVM 的执行主类、JVM 启动
  • 一、常见JVM参数配置: 1、垃圾回收统计信息: -XX:+PrintGC打印GC简要信息 -XX:+PrintGCDetails打印GC的详细信息 -XX:+PrintGCTimeStamps打印CG发生的时间戳 -Xloggc:log/gc.log指定GC log的位置,以文件...
  • Tomcat8性能JVM优化

    2020-03-31 15:34:58
    JVM性能调优: 常见 catalina.sh 配置汇总 JAVA_OPTS="$JAVA_OPTS -server -Xms1024m -Xmx1024m -Xss256k -XX:+UseParallelOldGC -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/heap_dump -XX:+...
  • JVM常见面试题

    千次阅读 2022-05-01 09:16:16
    JVM场景面试

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 93,853
精华内容 37,541
热门标签
关键字:

常见jvm优化