当经过 Java 命令启动 Java 进程的时候,会为它分配内存。内存的一部分用于建立堆空间,当程序中建立对象的时候,就从对空间中分配内存。GC 是 JVM 内部的一个进程,回收无效对象的内存用于未来的分配。html
JVM包含两个子系统和两个组件,两个子系统为Class loader(类装载)、Execution engine(执行引擎);两个组件为Runtime data area(运行时数据区)、Native Interface(本地接口)。java
做用 :首先经过编译器把 Java 代码转换成字节码,类加载器(ClassLoader)再把字节码加载到内存中,将其放在运行时数据区(Runtime data area)的方法区内,而字节码文件只是 JVM 的一套指令集规范,并不能直接交给底层操做系统去执行,所以须要特定的命令解析器执行引擎(Execution Engine),将字节码翻译成底层系统指令,再交由 CPU 去执行,而这个过程当中须要调用其余语言的本地库接口(Native Interface)来实现整个程序的功能。程序员
下面是Java程序运行机制详细说明面试
Java程序运行机制步骤算法
从上图能够看,java文件经过编译器变成了.class文件,接下来类加载器又将这些.class文件加载到JVM中。
其实能够一句话来解释:类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,而后在堆区建立一个 java.lang.Class对象,用来封装类在方法区内的数据结构。sql
思路: 给面试官画一下JVM内存模型图,并描述每一个模块的定义,做用,以及可能会存在的问题,如栈溢出等。编程
Java 虚拟机在执行 Java 程序的过程当中会把它所管理的内存区域划分为若干个不一样的数据区域。这些区域都有各自的用途,以及建立和销毁的时间,有些区域随着虚拟机进程的启动而存在,有些区域则是依赖线程的启动和结束而创建和销毁。Java 虚拟机所管理的内存被划分为以下几个区域:设计模式
浅拷贝(shallowCopy)只是增长了一个指针指向已存在的内存地址,数组
深拷贝(deepCopy)是增长了一个指针而且申请了一个新的内存,使这个增长的指针指向这个新的内存,浏览器
使用深拷贝的状况下,释放内存的时候不会由于出现浅拷贝时释放同一个内存的错误。
浅复制:仅仅是指向被复制的内存地址,若是原地址发生改变,那么浅复制出来的对象也会相应的改变。
深复制:在计算机中开辟一块新的内存地址用于存放复制的对象。
物理地址
堆的物理地址分配对对象是不连续的。所以性能慢些。在GC的时候也要考虑到不连续的分配,因此有各类算法。好比,标记-消除,复制,标记-压缩,分代(即新生代使用复制算法,老年代使用标记——压缩)
栈使用的是数据结构中的栈,先进后出的原则,物理地址分配是连续的。因此性能快。
内存分别
堆由于是不连续的,因此分配的内存是在运行期
确认的,所以大小不固定。通常堆大小远远大于栈。
栈是连续的,因此分配的内存大小要在编译期
就确认,大小是固定的。
存放的内容
堆存放的是对象的实例和数组。所以该区更关注的是数据的存储
栈存放:局部变量,操做数栈,返回结果。该区更关注的是程序方法的执行。
PS:
程序的可见度
堆对于整个应用程序都是共享、可见的。
栈只对于线程是可见的。因此也是线程私有。他的生命周期和线程相同。
JVM 中堆和栈属于不一样的内存区域,使用目的也不一样。栈经常使用于保存方法帧和局部变量,而对象老是在堆上分配。栈一般都比堆小,也不会在多个线程之间共享,而堆被整个 JVM 的全部线程共享。
队列和栈都是被用来预存储数据的。
是描述java方法执行的内存模型,每一个方法在执行的同时都会建立一个栈帧(Stack Frame)用于存储局部变量表、操做数栈、动态连接、方法出口等信息。 每个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
栈帧( Frame)是用来存储数据和部分过程结果的数据结构,同时也被用来处理动态连接(Dynamic Linking)、 方法返回值和异常分派( Dispatch Exception)。 栈帧随着方法调用而建立,随着方法结束而销毁——不管方法是正常完成仍是异常完成(抛出了在方法内未被捕获的异常)都算做方法结束。
一块较小的内存空间, 是当前线程所执行的字节码的行号指示器,每条线程都要有一个独立的程序计数器,这类内存也称为“线程私有” 的内存。
正在执行 java 方法的话,计数器记录的是虚拟机字节码指令的地址(当前指令的地址) 。若是仍是 Native 方法,则为空。这个内存区域是惟一一个在虚拟机中没有规定任何 OutOfMemoryError 状况的区域。
直接内存并非 JVM 运行时数据区的一部分, 但也会被频繁的使用: 在 JDK 1.4 引入的 NIO 提供了基于 Channel 与 Buffer 的 IO 方式, 它可使用 Native 函数库直接分配堆外内存, 而后使用DirectByteBuffer 对象做为这块内存的引用进行操做(详见: Java I/O 扩展), 这样就避免了在 Java堆和 Native 堆中来回复制数据, 所以在一些场景中能够显著提升性能。
说到对象的建立,首先让咱们看看 Java
中提供的几种对象建立方式:
Header | 解释 |
---|---|
使用new关键字 | 调用了构造函数 |
使用Class的newInstance方法 | 调用了构造函数 |
使用Constructor类的newInstance方法 | 调用了构造函数 |
使用clone方法 | 没有调用构造函数 |
使用反序列化 | 没有调用构造函数 |
下面是对象建立的主要流程:
虚拟机遇到一条new指令时,先检查常量池是否已经加载相应的类,若是没有,必须先执行相应的类加载。类加载经过后,接下来分配内存。若Java堆中内存是绝对规整的,使用“指针碰撞“方式分配内存;若是不是规整的,就从空闲列表中分配,叫作”空闲列表“方式。划份内存时还须要考虑一个问题-并发,也有两种方式: CAS同步处理,或者本地线程分配缓冲(Thread Local Allocation Buffer, TLAB)。而后内存空间初始化操做,接着是作一些必要的对象设置(元信息、哈希码…),最后执行<init>
方法。
类加载完成后,接着会在Java堆中划分一块内存分配给对象。内存分配根据Java堆是否规整,有两种方式:
选择哪一种分配方式是由 Java 堆是否规整来决定的,而 Java 堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。
对象的建立在虚拟机中是一个很是频繁的行为,哪怕只是修改一个指针所指向的位置,在并发状况下也是不安全的,可能出现正在给对象 A 分配内存,指针还没来得及修改,对象 B 又同时使用了原来的指针来分配内存的状况。解决这个问题有两种方案:
Java
程序须要经过 JVM
栈上的引用访问堆中的具体对象。对象的访问方式取决于 JVM
虚拟机的实现。目前主流的访问方式有 句柄 和 直接指针 两种方式。
指针: 指向对象,表明一个对象在内存中的起始地址。
句柄: 能够理解为指向指针的指针,维护着对象的指针。句柄不直接指向对象,而是指向对象的指针(句柄不发生变化,指向固定内存地址),再由对象的指针指向对象的真实内存地址。
Java
堆中划分出一块内存来做为句柄池,引用中存储对象的句柄地址,而句柄中包含了对象实例数据与对象类型数据各自的具体地址信息,具体构造以下图所示:
优点:引用中存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是很是广泛的行为)时只会改变句柄中的实例数据指针,而引用自己不须要修改。
若是使用直接指针访问,引用 中存储的直接就是对象地址,那么Java
堆对象内部的布局中就必须考虑如何放置访问类型数据的相关信息。
优点:速度更快,节省了一次指针定位的时间开销。因为对象的访问在Java
中很是频繁,所以这类开销聚沙成塔后也是很是可观的执行成本。HotSpot 中采用的就是这种方式。
Java 中,int 类型变量的长度是一个固定值,与平台无关,都是 32 位。意思就是说,在 32 位 和 64 位 的 Java 虚拟机中,int 类型的长度是相同的。
32 位和 64 位的 JVM 中,int 类型变量的长度是相同的,都是 32 位或者 4个字节。
你能够检查某些系统属性如 sun.arch.data.model 或 os.arch 来获取该信息。
理论上说上 32 位的 JVM 堆内存能够到达 2^32, 即 4GB,但实际上会比这个小不少。不一样操做系统之间不一样,如 Windows 系统大约 1.5GB,Solaris 大约3GB。64 位 JVM 容许指定最大的堆内存,理论上能够达到 2^64,这是一个很是大的数字,实际上你能够指定堆内存大小到 100GB。甚至有的 JVM,如 Azul,堆内存到 1000G 都是可能的。
JRE 表明 Java 运行时(Java run-time),是运行 Java 引用所必须的。
JDK 表明 Java 开发工具(Java development kit),是 Java 程序的开发工具,如 Java编译器,它也包含 JRE。
JVM 表明 Java 虚拟机(Java virtual machine),它的责任是运行 Java 应用。
JIT 表明即时编译(Just In Time compilation),当代码执行的次数超过必定的阈值时,会将 Java 字节码转换为本地代码,如,主要的热点代码会被准换为本地代码,这样有利大幅度提升 Java 应用的性能。
内存泄漏是指再也不被使用的对象或者变量一直被占据在内存中。理论上来讲,Java是有GC垃圾回收机制的,也就是说,再也不被使用的对象,会被GC自动回收掉,自动从内存中清除。
可是,即便这样,Java也仍是存在着内存泄漏的状况,java致使内存泄露的缘由很明确:长生命周期的对象持有短生命周期对象的引用就极可能发生内存泄露,尽管短生命周期对象已经再也不须要,可是由于长生命周期对象持有它的引用而致使不能被回收,这就是java中内存泄露的发生场景。
思路: 描述栈定义,再描述为何会溢出,再说明一下相关配置参数,OK的话能够给面试官手写是一个栈溢出的demo。
参考答案:
在java中,程序员是不须要显示的去释放一个对象的内存的,而是由虚拟机自行执行。在JVM中,有一个垃圾回收线程,它是低优先级的,在正常状况下是不会执行的,只有在虚拟机空闲或者当前堆内存不足时,才会触发执行,扫面那些没有被任何引用的对象,并将它们添加到要回收的集合中,进行回收。
GC 是垃圾收集的意思(Gabage Collection),内存处理是编程人员容易出现问题的地方,忘记或者错误的内存
回收会致使程序或系统的不稳定甚至崩溃,Java 提供的 GC 功能能够自动监测对象是否超过做用域从而达到自动
回收内存的目的,Java 语言没有提供释放已分配内存的显示操做方法。
java语言最显著的特色就是引入了垃圾回收机制,它使java程序员在编写程序时再也不考虑内存管理的问题。
因为有这个垃圾回收机制,java中的对象再也不有“做用域”的概念,只有引用的对象才有“做用域”。
垃圾回收机制有效的防止了内存泄露,能够有效的使用可以使用的内存。
垃圾回收器一般做为一个单独的低级别的线程运行,在不可预知的状况下对内存堆中已经死亡的或很长时间没有用过的对象进行清除和回收。
程序员不能实时的对某个对象或全部对象调用垃圾回收器进行垃圾回收。
垃圾回收有分代复制垃圾回收、标记垃圾回收、增量垃圾回收。
对于GC来讲,当程序员建立对象时,GC就开始监控这个对象的地址、大小以及使用状况。
一般,GC采用有向图的方式记录和管理堆(heap)中的全部对象。经过这种方式肯定哪些对象是"可达的",哪些对象是"不可达的"。当GC肯定一些对象为"不可达"时,GC就有责任回收这些内存空间。
能够。程序员能够手动执行System.gc(),通知GC运行,可是Java语言规范并不保证GC必定会执行。
不能,虽然你能够调用 System.gc() 或者 Runtime.gc(),可是没有办法保证 GC的执行。
思路: 先说一下四种引用的定义,能够结合代码讲一下,也能够扩展谈到ThreadLocalMap里弱引用用处。
参考答案:
1)强引用
咱们平时new了一个对象就是强引用,例如 Object obj = new Object();即便在内存不足的状况下,JVM宁愿抛出OutOfMemory错误也不会回收这种对象。
2)软引用
若是一个对象只具备软引用,则内存空间足够,垃圾回收器就不会回收它;若是内存空间不足了,就会回收这些对象的内存。
SoftReference<String> softRef=new SoftReference<String>(str); // 软引用
用处: 软引用在实际中有重要的应用,例如浏览器的后退按钮。按后退时,这个后退时显示的网页内容是从新进行请求仍是从缓存中取出呢?这就要看具体的实现策略了。
(1)若是一个网页在浏览结束时就进行内容的回收,则按后退查看前面浏览过的页面时,须要从新构建
(2)若是将浏览过的网页存储到内存中会形成内存的大量浪费,甚至会形成内存溢出
以下代码:
Browser prev = new Browser(); // 获取页面进行浏览 SoftReference sr = new SoftReference(prev); // 浏览完毕后置为软引用 if(sr.get()!=null){ rev = (Browser) sr.get(); // 尚未被回收器回收,直接获取 }else{ prev = new Browser(); // 因为内存吃紧,因此对软引用的对象回收了 sr = new SoftReference(prev); // 从新构建 }
3)弱引用
具备弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程当中,一旦发现了只具备弱引用的对象,无论当前内存空间足够与否,都会回收它的内存。
String str=new String("abc"); WeakReference<String> abcWeakRef = new WeakReference<String>(str); str=null; 等价于 str = null; System.gc();
4)虚引用
若是一个对象仅持有虚引用,那么它就和没有任何引用同样,在任什么时候候均可能被垃圾回收器回收。虚引用主要用来跟踪对象被垃圾回收器回收的活动。
垃圾收集器在作垃圾回收的时候,首先须要断定的就是哪些内存是须要被回收的,哪些对象是「存活」的,是不能够被回收的;哪些对象已经「死掉」了,须要被回收。
通常有两种方法来判断:
当对象对当前使用这个对象的应用程序变得不可触及的时候,这个对象就能够被回收了。
垃圾回收不会发生在永久代,若是永久代满了或者是超过了临界值,会触发彻底垃圾回收(Full GC)。若是你仔细查看垃圾收集器的输出信息,就会发现永久代也是被回收的。这就是为何正确的永久代大小对避免Full GC是很是重要的缘由。
Java 堆从 GC 的角度还能够细分为: 新生代(Eden 区、 From Survivor 区和 To Survivor 区)和老年代。
参考图1:
参考图2:
从图中能够看出: 堆大小 = 新生代 + 老年代。其中,堆的大小能够经过参数 –Xms、-Xmx 来指定。
默认的,新生代 ( Young ) 与老年代 ( Old ) 的比例的值为 1:2 ( 该值能够经过参数 –XX:NewRatio 来指定 ),
即:新生代 ( Young ) = 1/3 的堆空间大小。老年代 ( Old ) = 2/3 的堆空间大小。
其中,新生代 ( Young ) 被细分为 Eden 和 两个 Survivor 区域,这两个 Survivor 区域分别被命名为 from 和 to,以示区分
默认的,Eden: from : to = 8 :1 : 1 ( 能够经过参数–XX:SurvivorRatio 来设定 ),即: Eden = 8/10 的新生代空间大小,from = to = 1/10 的新生代空间大小。
JVM 每次只会使用 Eden 和其中的一块 Survivor 区域来为对象服务,因此不管何时,老是有一块Survivor区域是空闲着的。
所以,新生代实际可用的内存空间为 9/10 ( 即90% )的新生代空间。
是用来存放新生的对象。通常占据堆的 1/3 空间。因为频繁建立对象,因此新生代会频繁触发MinorGC 进行垃圾回收。新生代又分为 Eden区、 ServivorFrom、 ServivorTo 三个区。
Eden 区
Java 新对象的出生地(若是新建立的对象占用内存很大,则直接分配到老年代)。当 Eden 区内存不够的时候就会触发 MinorGC,对新生代区进行一次垃圾回收。
Servivor from 区
上一次 GC 的幸存者,做为这一次 GC 的被扫描者。
Servivor to 区
保留了一次 MinorGC 过程当中的幸存者。
MinorGC 的过程(复制->清空->互换)
MinorGC 采用复制算法。
主要存放应用程序中生命周期长的内存对象。
老年代的对象比较稳定,因此 MajorGC (经常称之为 FULL GC)不会频繁执行。在进行 FULL GC前通常都先进行了一次 MinorGC,使得有新生代的对象晋身入老年代,致使空间不够用时才触发。当没法找到足够大的连续空间分配给新建立的较大对象时也会提早触发一次 MajorGC 进行垃圾回收腾出空间。
FULL GC 采用标记清除算法:首先扫描一次全部老年代,标记出存活的对象,而后回收没有标记的对象。 ajorGC 的耗时比较长,由于要扫描再回收。 FULL GC 会产生内存碎片,为了减小内存损耗,咱们通常须要进行合并或者标记出来方便下次直接分配。当老年代也满了装不下的时候,就会抛出 OOM(Out of Memory)异常。
指内存的永久保存区域,主要存放 Class 和 Meta(元数据)的信息,Class 在被加载的时候被放入永久区域, 它和和存放实例的区域不一样,GC 不会在主程序运行期对永久区域进行清理。因此这也致使了永久代的区域会随着加载的 Class 的增多而胀满,最终抛出 OOM 异常。
思路: 先讲一下JAVA堆,新生代的划分,再谈谈它们之间的转化,相互之间一些参数的配置(如: –XX:NewRatio,–XX:SurvivorRatio等),再解释为何要这样划分,最好加一点本身的理解。
参考答案:
这样划分的目的是为了使 JVM 可以更好的管理堆内存中的对象,包括内存的分配以及回收。
1)共享内存区划分
2)一些参数的配置
3)为何要分为Eden和Survivor?为何要设置两个Survivor区?
思路: 先描述一下Java堆内存划分,再解释Minor GC,Major GC,full GC,描述它们之间转化流程。
个人答案:
垃圾回收不会发生在永久代,若是永久代满了或者是超过了临界值,会触发彻底垃圾回收(Full GC)。若是你仔细查看垃圾收集器的输出信息,就会发现永久代也是被回收的。这就是为何正确的永久代大小对避免Full GC是很是重要的缘由。请参考下Java8:从永久代到元数据区
(译者注:Java8中已经移除了永久代,新加了一个叫作元数据区的native内存区)
在 Java8 中, 永久代已经被移除,被一个称为“元数据区”(元空间)的区域所取代。元空间的本质和永久代相似,元空间与永久代之间最大的区别在于: 元空间并不在虚拟机中,而是使用本地内存。所以,默认状况下,元空间的大小仅受本地内存限制。 类的元数据放入native memory, 字符串池和类的静态变量放入 java 堆中, 这样能够加载多少类的元数据就再也不由MaxPermSize 控制, 而由系统的实际可用空间来控制。
判断对象是否存活通常有两种方式:
每一个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时能够回收。此方法简单,没法解决对象相互循环引用的问题。
从GC Roots开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时,则证实此对象是不可用的,不可达对象。
在 Java 中,引用和对象是有关联的。若是要操做对象则必须用引用进行。所以,很显然一个简单的办法是经过引用计数来判断一个对象是否能够回收。简单说,即一个对象若是没有任何与之关联的引用, 即他们的引用计数都不为 0, 则说明对象不太可能再被用到,那么这个对象就是可回收对象。
为了解决引用计数法的循环引用问题, Java 使用了可达性分析的方法。经过一系列的“GC roots”对象做为起点搜索。若是在“GC roots”和一个对象之间没有可达路径,则称该对象是不可达的。要注意的是,不可达对象不等价于可回收对象, 不可达对象变为可回收对象至少要通过两次标记过程。两次标记后仍然是可回收对象,则将面临回收。
新生代内存不够用时候发生MGC也叫YGC,JVM内存不够的时候发生FGC
GC最基础的算法有三类: 标记 -清除算法、复制算法、标记-压缩算法,咱们经常使用的垃圾回收器通常都采用分代收集算法。
标记 -清除算法,“标记-清除”(Mark-Sweep)算法,如它的名字同样,算法分为“标记”和“清除”两个阶段:首先标记出全部须要回收的对象,在标记完成后统一回收掉全部被标记的对象。
复制算法,“复制”(Copying)的收集算法,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另一块上面,而后再把已使用过的内存空间一次清理掉。
标记-压缩算法,标记过程仍然与“标记-清除”算法同样,但后续步骤不是直接对可回收对象进行清理,而是让全部存活的对象都向一端移动,而后直接清理掉端边界之外的内存
分代收集算法,“分代收集”(Generational Collection)算法,把Java堆分为新生代和老年代,这样就能够根据各个年代的特色采用最适当的收集算法
标记无用对象,而后进行清除回收。
标记-清除算法(Mark-Sweep)是一种常见的基础垃圾收集算法,它将垃圾收集分为两个阶段:
标记-清除算法之因此是基础的,是由于后面讲到的垃圾收集算法都是在此算法的基础上进行改进的。
优势:实现简单,不须要对象进行移动。
缺点:标记、清除过程效率低,产生大量不连续的内存碎片,提升了垃圾回收的频率。
标记-清除算法的执行的过程以下图所示
为了解决标记-清除算法的效率不高的问题,产生了复制算法。它把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾收集时,遍历当前使用的区域,把存活对象复制到另一个区域中,最后将当前使用的区域的可回收的对象进行回收。
优势:按顺序分配内存便可,实现简单、运行高效,不用考虑内存碎片。
缺点:可用的内存大小缩小为原来的一半,对象存活率高时会频繁进行复制。
复制算法的执行过程以下图所示
在新生代中可使用复制算法,可是在老年代就不能选择复制算法了,由于老年代的对象存活率会较高,这样会有较多的复制操做,致使效率变低。标记-清除算法能够应用在老年代中,可是它效率不高,在内存回收后容易产生大量内存碎片。所以就出现了一种标记-整理算法(Mark-Compact)算法,与标记-整理算法不一样的是,在标记可回收的对象后将全部存活的对象压缩到内存的一端,使他们紧凑的排列在一块儿,而后对端边界之外的内存进行回收。回收后,已用和未用的内存都各自一边。
优势:解决了标记-清理算法存在的内存碎片问题。
缺点:仍须要进行局部对象移动,必定程度上下降了效率。
标记-整理算法的执行过程以下图所示
分代收集法是目前大部分 JVM 所采用的方法,其核心思想是根据对象存活的不一样生命周期将内存划分为不一样的域,通常状况下将 GC 堆划分为老生代(Tenured/Old Generation)和新生代(YoungGeneration)。老生代的特色是每次垃圾回收时只有少许对象须要被回收,新生代的特色是每次垃圾回收时都有大量垃圾须要被回收,所以能够根据不一样区域选择不一样的算法。
当前商业虚拟机都采用分代收集的垃圾收集算法。分代收集算法,顾名思义是根据对象的存活周期将内存划分为几块。通常包括年轻代、老年代 和 永久代,如图所示:
当前主流 VM 垃圾收集都采用”分代收集” (Generational Collection)算法, 这种算法会根据对象存活周期的不一样将内存划分为几块, 如 JVM 中的 新生代、老年代、永久代, 这样就能够根据各年代特色分别采用最适当的 GC 算法
每次垃圾收集都能发现大批对象已死, 只有少许存活. 所以选用复制算法, 只须要付出少许存活对象的复制成本就能够完成收集
目前大部分 JVM 的 GC 对于新生代都采起 Copying 算法,由于新生代中每次垃圾回收都要回收大部分对象,即要复制的操做比较少,但一般并非按照 1: 1 来划分新生代。通常将新生代划分为一块较大的 Eden 空间和两个较小的 Survivor 空间(From Space, To Space),每次使用Eden 空间和其中的一块 Survivor 空间,当进行回收时,将该两块空间中还存活的对象复制到另外一块 Survivor 空间中。
由于老年代对象存活率高、没有额外空间对它进行分配担保, 就必须采用“标记—清理”或“标记—整理” 算法来进行回收, 没必要进行内存复制, 且直接腾出空闲内存。于是采用 Mark-Compact 算法。
Java 堆内存被划分为新生代和年老代两部分,新生代主要使用复制和标记-清除垃圾回收算法;年老代主要使用标记-整理垃圾回收算法,所以 java 虚拟中针对新生代和年老代分别提供了多种不一样的垃圾收集器, JDK1.6 中 Sun HotSpot 虚拟机的垃圾收集器以下:
若是说垃圾收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。下图展现了7种做用于不一样分代的收集器,其中用于回收新生代的收集器包括Serial、PraNew、Parallel Scavenge,回收老年代的收集器包括Serial Old、Parallel Old、CMS,还有用于回收整个Java堆的G1收集器。不一样收集器之间的连线表示它们能够搭配使用。
Serial收集器(复制算法): 新生代单线程收集器,标记和清理都是单线程,优势是简单高效;
ParNew收集器 (复制算法): 新生代收并行集器,其实是Serial收集器的多线程版本,在多核CPU环境下有着比Serial更好的表现;
Parallel Scavenge收集器 (复制算法): 新生代并行收集器,追求高吞吐量,高效利用 CPU。吞吐量 = 用户线程时间/(用户线程时间+GC线程时间),高吞吐量能够高效率的利用CPU时间,尽快完成程序的运算任务,适合后台应用等对交互相应要求不高的场景;
Serial Old收集器 (标记-整理算法): 老年代单线程收集器,Serial收集器的老年代版本;
Parallel Old收集器 (标记-整理算法): 老年代并行收集器,吞吐量优先,Parallel Scavenge收集器的老年代版本;
CMS(Concurrent Mark Sweep)收集器(标记-清除算法): 老年代并行收集器,以获取最短回收停顿时间为目标的收集器,具备高并发、低停顿的特色,追求最短GC回收停顿时间。
G1(Garbage First)收集器 (标记-整理算法): Java堆并行收集器,G1收集器是JDK1.7提供的一个新收集器,G1收集器基于“标记-整理”算法实现,也就是说不会产生内存碎片。此外,G1收集器不一样于以前的收集器的一个重要特色是:G1回收的范围是整个Java堆(包括新生代,老年代),而前六种收集器回收的范围仅限于新生代或老年代。
Serial 与 Parallel 在 GC 执行的时候都会引发 stop-the-world。它们之间主要不一样 serial 收集器是默认的复制收集器,执行 GC 的时候只有一个线程,而parallel 收集器使用多个 GC 线程来执行。
思路: 必定要记住典型的垃圾收集器,尤为cms和G1,它们的原理与区别,涉及的垃圾回收算法。
参考答案:
1)几种垃圾收集器:
2)CMS收集器和G1收集器的区别:
CMS收集器是老年代的收集器,能够配合新生代的Serial和ParNew收集器一块儿使用;
G1收集器收集范围是老年代和新生代,不须要结合其余收集器使用;
CMS收集器以最小的停顿时间为目标的收集器;
G1收集器可预测垃圾回收的停顿时间
CMS收集器是使用“标记-清除”算法进行的垃圾回收,容易产生内存碎片
G1收集器使用的是“标记-整理”算法,进行了空间整合,下降了内存空间碎片。
CMS 是英文 Concurrent Mark-Sweep 的简称,是以牺牲吞吐量为代价来得到最短回收停顿时间的垃圾回收器。对于要求服务器响应速度的应用上,这种垃圾回收器很是适合。在启动 JVM 的参数加上“-XX:+UseConcMarkSweepGC”来指定使用 CMS 垃圾回收器。
CMS 使用的是标记-清除的算法实现的,因此在 gc 的时候回产生大量的内存碎片,当剩余内存不能知足程序运行要求时,系统将会出现 Concurrent Mode Failure,临时 CMS 会采用 Serial Old 回收器进行垃圾清除,此时的性能将会被下降。
Serial(英文连续) 是最基本垃圾收集器,使用复制算法,曾经是JDK1.3.1 以前新生代惟一的垃圾收集器。 Serial 是一个单线程的收集器, 它不但只会使用一个 CPU 或一条线程去完成垃圾收集工做,而且在进行垃圾收集的同时,必须暂停其余全部的工做线程,直到垃圾收集结束。
Serial 垃圾收集器虽然在收集垃圾过程当中须要暂停全部其余的工做线程,可是它简单高效,对于限定单个 CPU 环境来讲,没有线程交互的开销,能够得到最高的单线程垃圾收集效率,所以 Serial垃圾收集器依然是 java 虚拟机运行在 Client 模式下默认的新生代垃圾收集器。
ParNew 垃圾收集器实际上是 Serial 收集器的多线程版本,也使用复制算法,除了使用多线程进行垃圾收集以外,其他的行为和 Serial 收集器彻底同样, ParNew 垃圾收集器在垃圾收集过程当中一样也要暂停全部其余的工做线程。
ParNew 收集器默认开启和 CPU 数目相同的线程数,能够经过-XX:ParallelGCThreads 参数来限制垃圾收集器的线程数。 【Parallel:平行的】
ParNew 虽然是除了多线程外和Serial 收集器几乎彻底同样,可是ParNew垃圾收集器是不少 java虚拟机运行在 Server 模式下新生代的默认垃圾收集器。
Parallel Scavenge 收集器也是一个新生代垃圾收集器,一样使用复制算法,也是一个多线程的垃圾收集器, 它重点关注的是程序达到一个可控制的吞吐量(Thoughput, CPU 用于运行用户代码的时间/CPU 总消耗时间,即吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)),高吞吐量能够最高效率地利用 CPU 时间,尽快地完成程序的运算任务,主要适用于在后台运算而不须要太多交互的任务。 自适应调节策略也是 ParallelScavenge 收集器与 ParNew 收集器的一个重要区别。
Serial Old 是 Serial 垃圾收集器年老代版本,它一样是个单线程的收集器,使用标记-整理算法,这个收集器也主要是运行在 Client 默认的
java 虚拟机默认的年老代垃圾收集器。在 Server 模式下,主要有两个用途:
Parallel Old 收集器是Parallel Scavenge的年老代版本,使用多线程的标记-整理算法,在 JDK1.6才开始提供。
在 JDK1.6 以前,新生代使用 ParallelScavenge 收集器只能搭配年老代的 Serial Old 收集器,只能保证新生代的吞吐量优先,没法保证总体的吞吐量, Parallel Old 正是为了在年老代一样提供吞吐量优先的垃圾收集器, 若是系统对吞吐量要求比较高,能够优先考虑新生代Parallel Scavenge和年老代 Parallel Old 收集器的搭配策略。
新生代 Parallel Scavenge 和年老代 Parallel Old 收集器搭配运行过程图
Concurrent mark sweep(CMS)收集器是一种年老代垃圾收集器,其最主要目标是获取最短垃圾回收停顿时间, 和其余年老代使用标记-整理算法不一样,它使用多线程的标记-清除算法。最短的垃圾收集停顿时间能够为交互比较高的程序提升用户体验。CMS 工做机制相比其余的垃圾收集器来讲更复杂。整个过程分为如下 4 个阶段:
初始标记
只是标记一下 GC Roots 能直接关联的对象,速度很快,仍然须要暂停全部的工做线程。
并发标记
进行 GC Roots 跟踪的过程,和用户线程一块儿工做,不须要暂停工做线程。
从新标记
为了修正在并发标记期间,因用户程序继续运行而致使标记产生变更的那一部分对象的标记记录,仍然须要暂停全部的工做线程。
并发清除
清除 GC Roots 不可达对象,和用户线程一块儿工做,不须要暂停工做线程。因为耗时最长的并发标记和并发清除过程当中,垃圾收集线程能够和用户如今一块儿并发工做, 因此整体上来看CMS 收集器的内存回收和用户线程是一块儿并发地执行。CMS 收集器工做过程
Garbage first 垃圾收集器是目前垃圾收集器理论发展的最前沿成果,相比与 CMS 收集器, G1 收集器两个最突出的改进是:
1.基于标记-整理算法,不产生内存碎片。
2.能够很是精确控制停顿时间,在不牺牲吞吐量前提下,实现低停顿垃圾回收。G1 收集器避免全区域垃圾收集,它把堆内存划分为大小固定的几个独立区域,而且跟踪这些区域的垃圾收集进度,同时在后台维护一个优先级列表,每次根据所容许的收集时间, 优先回收垃圾最多的区域。区域划分和优先级区域回收机制,确保 G1 收集器能够在有限时间得到最高的垃圾收集效率
新生代垃圾回收器通常采用的是复制算法,复制算法的优势是效率高,缺点是内存利用率低;老年代回收器通常采用的是标记-整理的算法进行垃圾回收。
分代回收器有两个分区:老生代和新生代,新生代默认的空间占比总空间的 1/3,老生代的默认占比是 2/3。
新生代使用的是复制算法,新生代里有 3 个分区:Eden、To Survivor、From Survivor,它们的默认占比是 8:1:1,它的执行流程以下:
每次在 From Survivor 到 To Survivor 移动时都存活的对象,年龄就 +1,当年龄到达 15(默认配置是 15)时,升级为老生代。大对象也会直接进入老生代。
老生代当空间占用到达某个值以后就会触发全局垃圾收回,通常使用标记整理的执行算法。以上这些循环往复就构成了整个分代垃圾回收的总体执行流程。
除直接调用System.gc外,触发Full GC执行的状况有以下四种。
1.旧生代空间不足
老生代空间只有在新生代对象转入及建立为大对象、大数组时才会出现不足的现象,当执行Full GC后空间仍然不足,则抛出以下错误:
java.lang.OutOfMemoryError: Java heap space
为避免以上两种情况引发的FullGC,调优时应尽可能作到让对象在Minor GC阶段被回收、让对象在新生代多存活一段时间及不要建立过大的对象及数组。
2.Permanet Generation空间满
PermanetGeneration中存放的为一些class的信息等,当系统中要加载的类、反射的类和调用的方法较多时,Permanet Generation
可能会被占满,在未配置为采用CMS GC的状况下会执行Full GC。若是通过Full GC仍然回收不了,那么JVM会抛出以下错误信息:
java.lang.OutOfMemoryError: PermGen space
为避免Perm Gen占满形成Full GC现象,可采用的方法为增大Perm Gen空间或转为使用CMS GC。
3.CMS GC时出现promotion failed和concurrent mode failure
对于采用CMS进行老生代GC的程序而言,尤为要注意GC日志中是否有promotion failed和concurrent mode failure两种情况,当这两种情况出现时可能会触发Full GC。
promotionfailed是在进行Minor GC时,survivor space放不下、对象只能放入老生代,而此时老生代也放不下形成的;concurrent mode failure是在执行CMS GC的过程当中同时有对象要放入老生代,而此时老生代空间不足形成的。
应对措施为:增大survivorspace、老生代空间或调低触发并发GC的比率,但在JDK 5.0+、6.0+的版本中有可能会因为JDK的bug29致使CMS在remark完毕后好久才触发sweeping动做。对于这种情况,可经过设置-XX:CMSMaxAbortablePrecleanTime=5(单位为ms)来避免。
4.统计获得的Minor GC晋升到旧生代的平均大小大于旧生代的剩余空间
这是一个较为复杂的触发状况,Hotspot为了不因为新生代对象晋升到旧生代致使旧生代空间不足的现象,在进行Minor GC时,作了一个判断,若是以前统计所获得的Minor GC晋升到旧生代的平均大小大于旧生代的剩余空间,那么就直接触发Full GC。
例如程序第一次触发MinorGC后,有6MB的对象晋升到旧生代,那么当下一次Minor GC发生时,首先检查旧生代的剩余空间是否大于6MB,若是小于6MB,则执行Full GC。
当新生代采用PSGC时,方式稍有不一样,PS GC是在Minor GC后也会检查,例如上面的例子中第一次Minor GC后,PS GC会检查此时旧生代的剩余空间是否大于6MB,如小于,则触发对旧生代的回收。除了以上4种情况外,对于使用RMI来进行RPC或管理的Sun JDK应用而言,默认状况下会一小时执行一次Full GC。可经过在启动时经过- java-Dsun.rmi.dgc.client.gcInterval=3600000来设置Full GC执行的间隔时间或经过-XX:+ DisableExplicitGC来禁止RMI调用System.gc
所谓自动内存管理,最终要解决的也就是内存分配和内存回收两个问题。前面咱们介绍了内存回收,这里咱们再来聊聊内存分配。
对象的内存分配一般是在 Java 堆上分配(随着虚拟机优化技术的诞生,某些场景下也会在栈上分配,后面会详细介绍),对象主要分配在新生代的 Eden 区,若是启动了本地线程缓冲,将按照线程优先在 TLAB 上分配。少数状况下也会直接在老年代上分配。总的来讲分配规则不是百分百固定的,其细节取决于哪种垃圾收集器组合以及虚拟机相关参数有关,可是虚拟机对于内存的分配仍是会遵循如下几种「普世」规则:
多数状况,对象都在新生代 Eden 区分配。当 Eden 区分配没有足够的空间进行分配时,虚拟机将会发起一次 Minor GC。若是本次 GC 后仍是没有足够的空间,则将启用分配担保机制在老年代中分配内存。
这里咱们提到 Minor GC,若是你仔细观察过 GC 平常,一般咱们还能从日志中发现 Major GC/Full GC。
所谓大对象是指须要大量连续内存空间的对象,频繁出现大对象是致命的,会致使在内存还有很多空间的状况下提早触发 GC 以获取足够的连续空间来安置新对象。
前面咱们介绍过新生代使用的是标记-清除算法来处理垃圾回收的,若是大对象直接在新生代分配就会致使 Eden 区和两个 Survivor 区之间发生大量的内存复制。所以对于大对象都会直接在老年代进行分配。
虚拟机采用分代收集的思想来管理内存,那么内存回收时就必须判断哪些对象应该放在新生代,哪些对象应该放在老年代。所以虚拟机给每一个对象定义了一个对象年龄的计数器,若是对象在 Eden 区出生,而且可以被 Survivor 容纳,将被移动到 Survivor 空间中,这时设置对象年龄为 1。对象在 Survivor 区中每「熬过」一次 Minor GC 年龄就加 1,当年龄达到必定程度(默认 15) 就会被晋升到老年代。
虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验,解析和初始化,最终造成能够被虚拟机直接使用的java类型。
Java中的全部类,都须要由类加载器装载到JVM中才能运行。类加载器自己也是一个类,而它的工做就是把class文件从硬盘读取到内存中。在写程序的时候,咱们几乎不须要关心类的加载,由于这些都是隐式装载的,除非咱们有特殊的用法,像是反射,就须要显式的加载所须要的类。
类装载方式,有两种 :
1.隐式装载, 程序在运行过程当中当碰到经过new 等方式生成对象时,隐式调用类装载器加载对应的类到jvm中,
2.显式装载, 经过class.forname()等方法,显式加载须要的类
Java类的加载是动态的,它并不会一次性将全部类所有加载后再运行,而是保证程序运行的基础类(像是基类)彻底加载到jvm中,至于其余类,则在须要的时候才加载。这固然就是为了节省内存开销。
JVM 中类的装载是由类加载器(ClassLoader)和它的子类来实现的,Java 中的类加载器是一个重要的 Java 运行时系统组件,它负责在运行时查找和装入类文件中的类。
因为 Java 的跨平台性,通过编译的 Java 源程序并非一个可执行程序,而是一个或多个类文件。当 Java 程序须要使用某个类时,JVM 会确保这个类已经被加载、链接(验证、准备和解析)和初始化。类的加载是指把类的.class 文件中的数据读入到内存中,一般是建立一个字节数组读入.class 文件,而后产生与所加载类对应
的 Class 对象。
加载完成后,Class 对象还不完整,因此此时的类还不可用。当类被加载后就进入链接阶段,这一阶段包括验证、准备(为静态变量分配内存并设置默认的初始值)和解析(将符号引用替换为直接引用)三个步骤。最后 JVM 对
类进行初始化,包括:1)若是类存在直接的父类而且这个类尚未被初始化,那么就先初始化父类;2)若是类中存在初始化语句,就依次执行这些初始化语句。
类的加载是由类加载器完成的,类加载器包括:根加载器(BootStrap)、扩展加载器(Extension)、系统加载器(System)和用户自定义类加载器(java.lang.ClassLoader 的子类)。
从 Java 2(JDK 1.2)开始,类加载过程采起了父亲委托机制(PDM)。PDM 更好的保证了 Java 平台的安全性,在该机制中,JVM 自带的Bootstrap 是根加载器,其余的加载器都有且仅有一个父类加载器。类的加载首先请求父类加载器加载,父类加载器无能为力时才由其子类加载器自行加载。JVM 不会向 Java 程序提供对 Bootstrap 的引用。下面是关于几个类
加载器的说明:
JVM 类加载机制分为五个部分:加载,验证,准备,解析,初始化,下面咱们就分别来看一下这五个过程。
加载
加载是类加载过程当中的一个阶段, 这个阶段会在内存中生成一个表明这个类的 java.lang.Class 对象, 做为方法区这个类的各类数据的入口。注意这里不必定非得要从一个 Class 文件获取,这里既能够从 ZIP 包中读取(好比从 jar 包和 war 包中读取),也能够在运行时计算生成(动态代理),也能够由其它文件生成(好比将 JSP 文件转换成对应的 Class 类)。
验证
这一阶段的主要目的是为了确保 Class 文件的字节流中包含的信息是否符合当前虚拟机的要求,而且不会危害虚拟机自身的安全。
准备
准备阶段是正式为类变量分配内存并设置类变量的初始值阶段,即在方法区中分配这些变量所使用的内存空间。注意这里所说的初始值概念,好比一个类变量定义为:
实际上变量 v 在准备阶段事后的初始值为 0 而不是 8080, 将 v 赋值为 8080 的 put static 指令是程序被编译后, 存放于类构造器方法之中。
可是注意若是声明为:public static final int v = 8080;
在编译阶段会为 v 生成 ConstantValue 属性,在准备阶段虚拟机会根据 ConstantValue 属性将 v赋值为 8080。
解析
解析阶段是指虚拟机将常量池中的符号引用替换为直接引用的过程。符号引用就是 class 文件中的:
public static int v = 8080;
实际上变量 v 在准备阶段事后的初始值为 0 而不是 8080, 将 v 赋值为 8080 的 put static 指令是程序被编译后, 存放于类构造器方法之中。可是注意若是声明为:
在编译阶段会为 v 生成 ConstantValue 属性,在准备阶段虚拟机会根据 ConstantValue 属性将 v
赋值为 8080。解析
解析阶段是指虚拟机将常量池中的符号引用替换为直接引用的过程。符号引用就是 class 文件中的:
public static final int v = 8080;
在编译阶段会为 v 生成 ConstantValue 属性,在准备阶段虚拟机会根据 ConstantValue 属性将 v赋值为 8080。
解析
解析阶段是指虚拟机将常量池中的符号引用替换为直接引用的过程。符号引用就是 class 文件中的:
符号引用
符号引用与虚拟机实现的布局无关, 引用的目标并不必定要已经加载到内存中。 各类虚拟机实现的内存布局能够各不相同,可是它们能接受的符号引用必须是一致的,由于符号引用的字面量形式明肯定义在 Java 虚拟机规范的 Class 文件格式中。
直接引用
直接引用能够是指向目标的指针,相对偏移量或是一个能间接定位到目标的句柄。若是有了直接引用,那引用的目标一定已经在内存中存在。
初始化
初始化阶段是类加载最后一个阶段,前面的类加载阶段以后,除了在加载阶段能够自定义类加载器之外,其它操做都由 JVM 主导。到了初始阶段,才开始真正执行类中定义的 Java 程序代码。
类构造器
初始化阶段是执行类构造器方法的过程。 方法是由编译器自动收集类中的类变量的赋值操做和静态语句块中的语句合并而成的。虚拟机会保证子方法执行以前,父类的方法已经执行完毕, 若是一个类中没有对静态变量赋值也没有静态语句块,那么编译器能够不为这个类生成() 方法。注意如下几种状况不会执行类初始化:
实现经过类的权限定名获取该类的二进制字节流的代码块叫作类加载器。
主要有一下四种类加载器:
类装载分为如下 5 个步骤:
在介绍双亲委派模型以前先说下类加载器。对于任意一个类,都须要由加载它的类加载器和这个类自己一同确立在 JVM 中的惟一性,每个类加载器,都有一个独立的类名称空间。类加载器就是根据指定全限定名称将 class 文件加载到 JVM 内存,而后再转化为 class 对象。
类加载器分类:
双亲委派模型:若是一个类加载器收到了类加载的请求,它首先不会本身去加载这个类,而是把这个请求委派给父类加载器去完成,每一层的类加载器都是如此,这样全部的加载请求都会被传送到顶层的启动类加载器中,只有当父加载没法完成加载请求(它的搜索范围中没找到所需的类)时,子加载器才会尝试去加载类。
当一个类收到了类加载请求时,不会本身先去加载这个类,而是将其委派给父类,由父类去加载,若是此时父类不能加载,反馈给子类,由子类去完成类的加载。
思路: 先说明一下什么是类加载器,能够给面试官画个图,再说一下类加载器存在的意义,说一下双亲委派模型,最后阐述怎么打破双亲委派模型。
参考的答案:
类加载器 就是根据指定全限定名称将class文件加载到JVM内存,转为Class对象。
- 启动类加载器(Bootstrap ClassLoader):由C++语言实现(针对HotSpot),负责将存放在<JAVA_HOME>\lib目录或-Xbootclasspath参数指定的路径中的类库加载到内存中。
- 其余类加载器:由Java语言实现,继承自抽象类ClassLoader。如:
- 扩展类加载器(Extension ClassLoader):负责加载<JAVA_HOME>\lib\ext目录或java.ext.dirs系统变量指定的路径中的全部类库。
- 应用程序类加载器(Application ClassLoader)。负责加载用户类路径(classpath)上的指定类库,咱们能够直接使用这个类加载器。通常状况,若是咱们没有自定义类加载器默认就是用这个加载器。
2)双亲委派模型
双亲委派模型工做过程是:
若是一个类加载器收到类加载的请求,它首先不会本身去尝试加载这个类,而是把这个请求委派给父类加载器完成。每一个类加载器都是如此,只有当父加载器在本身的搜索范围内找不到指定的类时(即ClassNotFoundException),子加载器才会尝试本身去加载。
双亲委派模型图:
3)为何须要双亲委派模型?
在这里,先想一下,若是没有双亲委派,那么用户是否是能够本身定义一个java.lang.Object的同名类,java.lang.String的同名类,并把它放到ClassPath中,那么类之间的比较结果及类的惟一性将没法保证,所以,为何须要双亲委派模型?防止内存中出现多份一样的字节码
4)怎么打破双亲委派模型?
打破双亲委派机制则不只要继承ClassLoader类,还要重写loadClass和findClass方法。
Java虚拟机是一个能够执行Java字节码的虚拟机进程。Java源文件被编译成能被Java虚拟机执行的字节码文件。 Java被设计成容许应用程序能够运行在任意的平台,而不须要程序员为每个平台单独重写或者是从新编译。Java虚拟机让这个变为可能,由于它知道底层硬件平台的 指令长度和其余特性。
JDK 自带了不少监控工具,都位于 JDK 的 bin 目录下,其中最经常使用的是 jconsole 和 jvisualvm 这两款视图监控工具。
Sun JDK监控和故障处理命令有jps jstat jmap jhat jstack jinfo
经常使用调优工具分为两类,jdk自带监控工具:jconsole和jvisualvm,第三方有:MAT(Memory AnalyzerTool)、GChisto。
思路: 能够说一下堆栈配置相关的,垃圾收集器相关的,还有一下辅助信息相关的。
参考答案:
1)堆栈配置相关
java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:MaxPermSize=16m -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MaxTenuringThreshold=0
-Xmx3550m: 最大堆大小为3550m。
-Xms3550m: 设置初始堆大小为3550m。
-Xmn2g: 设置年轻代大小为2g。
-Xss128k: 每一个线程的堆栈大小为128k。
-XX:MaxPermSize: 设置持久代大小为16m
-XX:NewRatio=4: 设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)。
-XX:SurvivorRatio=4: 设置年轻代中Eden区与Survivor区的大小比值。设置为4,则两个Survivor区与一个Eden区的比值为2:4,一个Survivor区占整个年轻代的1/6
-XX:MaxTenuringThreshold=0: 设置垃圾最大年龄。若是设置为0的话,则年轻代对象不通过Survivor区,直接进入年老代。
2)垃圾收集器相关
-XX:+UseParallelGC-XX:ParallelGCThreads=20-XX:+UseConcMarkSweepGC -XX:CMSFullGCsBeforeCompaction=5-XX:+UseCMSCompactAtFullCollection:
-XX:+UseParallelGC: 选择垃圾收集器为并行收集器。
-XX:ParallelGCThreads=20: 配置并行收集器的线程数
-XX:+UseConcMarkSweepGC: 设置年老代为并发收集。
-XX:CMSFullGCsBeforeCompaction:因为并发收集器不对内存空间进行压缩、整理,因此运行一段时间之后会产生“碎片”,使得运行效率下降。此值设置运行多少次GC之后对内存空间进行压缩、整理。
-XX:+UseCMSCompactAtFullCollection: 打开对年老代的压缩。可能会影响性能,可是能够消除碎片
3)辅助信息相关
-XX:+PrintGC-XX:+PrintGCDetails
-XX:+PrintGC 输出形式:
[GC 118250K->113543K(130112K), 0.0094143 secs] [Full GC 121376K->10414K(130112K), 0.0650971 secs]
-XX:+PrintGCDetails 输出形式:
[GC [DefNew: 8614K->781K(9088K), 0.0123035 secs] 118250K->113543K(130112K), 0.0124633 secs] [GC [DefNew: 8614K->8614K(9088K), 0.0000665 secs][Tenured: 112761K->10414K(121024K), 0.0433488 secs] 121376K->10414K(130112K), 0.0436268 secs
思路: 能够说一下jps,top ,jstack这几个命令,再配合一次排查线上问题进行解答。
参考答案: