面试之-JVM
一、JVM垃圾回收的时候如何确定是垃圾?是否知道什么是GC Root:
1. 什么是垃圾:
简单来说就是内存中已经不再被使用到的空间就是垃圾。例如:Person p1 = null
2. 要进行垃圾回收,如何判断一个对象是否可以被回收?
2.1 引用计数法:
Java中,引用和对象是有关联的。如果要操作对象则必须用引用进行。因此,很显然一个简单的方法就是通过引用计数来判断一个对象是否可以回收。简单说,给对象添加一个引用计数器,每当有地方引用它,计数器加1,每当有一个引用失效时,计数器值减1.任何时刻计数器为0的对象就是不可能再被使用的,那么这个对象就是可回收对象。那么为什么主流的Java虚拟机里面都没有选用这种算法呢?其中最主要的原因是它很难解决对象之间相互循环引用的问题。(该算法存在,但目前无人使用,解决不掉循环引用问题,了解即可)
2.2 枚举根节点做可达性分析(根搜索路径):
为了解决引用计数循环引用问题,Java使用了可定分析的方法。所谓的“GC Roots”或者说tracing GC的“根集合”就是一组必须活跃的引用。基本思路就是通过一系列名为“GC Root”的对象作为起始点,从这个被称为GC Roots的对象向下搜索,如果一个对象到GC Roots没有任何引用链相连时,则说明此对象不可用。也即给定一个集合的引用作为根出发,通过引用关系便利对象图,能被便利到(可达性的)的对象就被判断为存活,没有便利掉的自然就被判定为死亡。
Java中哪些可以作为GC Roots的对象:
虚拟机栈(栈帧中的局部变量区,也叫局部变量表)中引用的对象。
方法区中类静态属性引用的对象。
方法区中常量引用的对象。
本地方法栈中JNI(Native方法)引用的对象。
*****************************************************************************************************************************
二、你说你做过JVM调优和参数配置,请问如何盘点查看JVM系统默认值
1. JVM参数类型:
标配参数:
-version -help java -showversion
x参数(了解):
-Xint:解释执行 -Xcomp:第一次使用就编译成本地代码 -Xmixed:混合模式
xx参数:
Boolean类型:
公式:-XX:+或者-某个属性值(+表示开启,-表示关闭)
Case:
是否打印GC收集细节: -XX:-PrintGCDetails -XX:+PrintGCDetails
是否使用串行垃圾回收器: -XX:-UseSerialGC -XX:+UseSerialGC
KV设值类型:jps -l jinfo -flag MetaspaceSize 2116
公式:-XX:属性key=属性值value
Case:
-XX:MetaspaceSize=128m:元空间 -XX:MaxTenuringThreshold=15:to-form,最大存活次数
jinfo举例,如何查看当前运行程序的配置
公式:jinfo -flag 项目项 进程编号
Case: jinfo -flags 5988:全部参数
题外话(坑题 ):
有两个经典参数:-Xms和-Xmx,这个你如何解释:
-Xms:等价于-XX:InitialHeapSize(初始化堆内存)
-Xmx:等价于-XX:MaxHeapSize(最大堆内存)
2. 盘点家底查看JVM默认值:
1. 第一种,查看参数盘点家底:
jps
jinfo -flag 具体参数 java进程编号
jinfo -flags java进程编号
2. 第二种,查看参数盘点家底:
-XX:+PrintFlagsInitial:主要查看初始默认
java -XX:+PrintFlagsInitial -version
java -XX:+PrintFlagsInitial
-XX:+PrintFlagsFinal:主要查看修改更新
java -XX:+PrintFlagsFinal -version “=”:没修改过 “:=”:是修改过的
运行java命令的同时打印出参数:java -XX:PrintFlagsFinal -Xss128k T(运行java类名称)
-XX:+PrintCommandLineFlags:打印命令行参数(可以看垃圾回收器)
java -XX:+PrintCommandLineFlags -version
-XX:+UseSerialGC:串行垃圾回收器
-XX:+UseParallelGC:并行垃圾回收器
*****************************************************************************************************************************
三、你平时工作用过的JVM常用基本配置有哪些?
1. -Xms:初始内存大小,默认为物理内存61/1,等价于-XX:InitialHeapSize
2. -Xmx:最大分配内存,默认物理内存1/4,等价于-XX:MaxHeapSize
3. -Xss:设置单个线程栈的大小,一般默认为512k~1024k,等价于-XX:ThreadStackSize
4. -Xmn:设置年轻代大小
5. -XX:MetaspaceSize:设置元空间大小
元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间和永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小受本地内存控制。
-Xms10m -Xmx10m -XX:MetaspaceSize=1024m -XX:+PrintFlagsFinal
6. 典型设置案例:
-Xms128m -Xmx4096m -Xss1024k -XX:MetaspaceSize=512m -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseSerialGC
7. -XX:+PrintGCDetails:输出详细GC收集日志信息
8. -XX:SurvivorRatio:
设置新生代中eden和s0/s1空间的比例,默认:-XX:SurvivorRation=8,Eden:S0:S1=8:1:1,假如:-XX:SurvivorRation=4,Eden:S0:S1=4:1:1。SurvivorRatio值就是设置eden区的比例占多少,S0/S1相同。
9. -XX:NewRatio:
配置年轻代于老年代在堆结构的占比,默认:-XX:NewRatio=2新生代占1,老年代占2,年轻代占整个堆得1/3,假如:-XX:NewRatio=4新生代占1,老年代占4,年轻代占整个堆的1/5。NewRadio值就是设置老年代的占比,剩下的1交给新生代。
10. -XX:MaxTenuringThreshold:设置垃圾最大年龄
-Xms与-Xmx(堆内存)配成一样,避免内存上下波动;
*****************************************************************************************************************************
四、强引用、软引用、弱引用、虚引用分别是什么?
1. 整体架构:
Reference:强引用 SoftReference:软引用 WeakReference:弱引用 PhantomReference:虚引用
2. 强引用:(默认支出模式)【死都不回收】
当内存不足时,JVM开始垃圾回收,对于强引用对象,就算出现OOM也不会对该对象进行回收,死都不会。
强引用是我们最常见的的普通对象引用,只要还有强引用指向一个对象,那就表明对象还“活着”,垃圾回收器不会碰这种对象。在Java中最常见的就是强引用,把一个对象赋给一个引用变量,这个引用变量就是一个强引用。当一个对象被强引用变量引用时,它处于可达状态,它是不可能被垃圾回收机制回收的,即使该对象以后永远不会被用到JVM也不会回收。因此强引用是造成Java内存泄漏的主要原因之一。
对于一个普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显示的将相应(强)引用赋值为null,一般认为就是可以被垃圾收集的了(当然具体回收时机还要看垃圾收集策略)
public static void main(String[] args) { Object obj1 = new Object(); //这样定义的默认就是强引用 Object obj2 = obj1; //obj2引用赋值 obj1 = null; //置空 System.gc(); System.out.println("obj1: " + obj1 + "\t " + "obj2:" + obj2); }
3. 软引用:【内存够用不回收,内存不够用回收】
软引用是一种相对于强引用弱化了一些的引用,需要java.lang.ref.SoftReference类来实现,可以让对象豁免一些垃圾收集。对于软引用的对象来说,当系统内存充足时:它不会被回收,当系统内存不足时:它会被回收。
软引用通常用在对内存敏感的程序中,比如高速缓存就有用到软引用,内存够用的时候就保留,不够用就回收。
代码示例:
public class SoftReferenceDemo { /** * 内存够用就保留,不够用就回收 */ public static void softRef_Memory_Enough() { Object o1 = new Object(); SoftReference<Object> softReference = new SoftReference<>(o1); System.out.println("o1:" + o1 + "\t" + "softReference:" + softReference.get()); o1 = null; System.gc(); System.out.println("o1:" + o1 + "\t" + "softReference:" + softReference.get()); } /** * JVM配置:故意产生大对象并配置小内存,让内存不够了就导致OOM,看软引用的回收情况。 * -Xms5m -Xmx5m -XX:+PrintGCDetails */ public static void softRef_Memory_NotEnough() { Object o1 = new Object(); SoftReference<Object> softReference = new SoftReference<>(o1); System.out.println("o1:" + o1 + "\t" + "softReference:" + softReference.get()); o1 = null; try { byte[] bytes = new byte[30 * 1024 * 1024]; }catch (Exception e) { e.printStackTrace(); }finally { System.out.println("o1:" + o1 + "\t" + "softReference:" + softReference.get()); } } public static void main(String[] args) { softRef_Memory_NotEnough(); } }
4. 弱引用:【碰到GC就回收】
弱引用需要用java.lang.ref.WeakReference类来实现,它比软引用的生存期更短,对于只有弱引用的对象来说,只要垃圾回收机制一运行,不管JVM的内存空间是否足够,都会回收该对象占用的内存。
4.1 代码示例:
public static void main(String[] args) { Object o1 = new Object(); WeakReference<Object> weakReference = new WeakReference<>(o1); System.out.println("o1:" + o1 + "\t" + "weakReference:" + weakReference.get()); o1 = null; System.gc(); System.out.println("o1:" + o1 + "\t" + "weakReference:" + weakReference.get()); }
4.2 软引用和弱引用的适用场景:
假如有一个应用需要读取大量的本地图片:
* 如果每次读取图片都从硬盘读取则会严重影响性能;
* 如果一次性全部加载到内存中又可能造成内存溢出;
此时使用软引用、弱引用可以解决这个问题:
设计思路:用一个HashMap来保存图片路径和相关图片对象关联的软引用之间的映射关系,在内存不足时,JVM会自动回收这些缓存图片对象所占用的空间,从而有效避免了OOM的问题。
Map<String, SoftReference<Bitmap>> imageCache = new Hash<>();
4.3 你知道软引用的话,能谈谈WeakHashMap吗?
WeakHashMap:只要gc就会被回收,方便做告诉缓存,和对内存敏感的相关需求开发。
public class WeakHashMapDemo { public static void main(String[] args) { myHashMaP(); System.out.println("============================"); myWeakHashMap(); } private static void myWeakHashMap() { WeakHashMap<Integer, String> map = new WeakHashMap<>(); Integer key = new Integer(1); String value = "HashMap"; map.put(key, value); System.out.println(map); key = null; System.out.println(map); System.gc(); System.out.println(map); } private static void myHashMaP() { HashMap<Integer, String> map = new HashMap<>(); Integer key = new Integer(1); String value = "HashMap"; map.put(key, value); System.out.println(map); key = null; System.out.println(map); System.gc(); System.out.println(map); } }
5. 虚引用:(又称幽灵引用)
虚引用需要java.lang.ref.PhantomReference类来实现。顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可以被垃圾回收器回收,它不能单独使用也不能通过它访问对象,虚引用必须和引用队列(ReferenceQueue)联合使用。
虚引用主要的作用是跟踪对象被垃圾回收的状态。仅仅提供了一种确保对象被finalize以后,做某些事情的机制。PhantomReferabce的get方法总是返回null,因此无法访问对应的引用对象。其意义在于说明一个对象已经进入finalization阶段,可以被gc回收,用来实现比finalization机制更灵活的回收操作。换句话说,设置虚引用关联的唯一目的,就是对象在被回收的时候,收到一个系统通知或者后续添加进一步的处理。java技术允许使用finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。
5.1 引用队列:Class ReferenceQueue<T>
我被回收前需要被引用队列保存下。
代码示例:ReferenceQueue(引用队列)
public class ReferenceQueueDemo { public static void main(String[] args) throws InterruptedException { Object o1 = new Object(); ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>(); WeakReference<Object> weakReference = new WeakReference<>(o1, referenceQueue); System.out.println(o1); System.out.println(weakReference.get()); System.out.println(referenceQueue.poll()); System.out.println("=============================="); o1 = null; System.gc(); TimeUnit.SECONDS.sleep(3); System.out.println(o1); System.out.println(weakReference.get()); System.out.println(referenceQueue.poll()); } }
示例代码:PhantomReference(虚引用)
/** * @Author luliang * @Date 2020-01-06 17:42 * java 提供了4种引用类型,在垃圾回收的时候,都有各自的特点。 * ReferenceQueue是用来配合引用工作的,没有ReferenceQueue一样可以运行。 * * 创建引用的时候可以指定关联的队列,当GC释放内存对象的时候,会将引用加入到引用队列, * 如果程序发现某个虚引用已经被加入到引用队列,那么久可以在所引用对象的内存被回收之前采取必要的行动,相当于一种通知机制 * 当关联的引用队列中有数据的时候,意味着引用指向的堆内存中的对象被回收。通过这种方式,JVM允许我们在对象被销毁后,做一些我们自己想做的事情 */ public class PhantomReferenceDemo { public static void main(String[] args) throws InterruptedException { Object o1 = new Object(); ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>(); PhantomReference<Object> phantomReference = new PhantomReference<>(o1, referenceQueue); System.out.println(o1); System.out.println(phantomReference.get()); System.out.println(referenceQueue.poll()); System.out.println("=============================="); o1 = null; System.gc(); TimeUnit.SECONDS.sleep(3); System.out.println(o1); System.out.println(phantomReference.get()); System.out.println(referenceQueue.poll()); } }
6. GCRoots 和四大引用小总结:
*****************************************************************************************************************************
五、谈谈你对OOM的认识
1. java.lang.StackOverflowError:深度的方法调用,导致栈出不来【栈挂了】
//递归调用,方法特别多,把栈空间撑爆了 public class StackOverflowErrorDemo { public static void main(String[] args) { stackOverflowError(); } private static void stackOverflowError() { stackOverflowError(); //Exception in thread "main" java.lang.StackOverflowError } }
2. java.lang.OutOfMemoryError:Java heap space【堆挂了】
/** * -Xms10m -Xmx10m * java.lang.OutOfMemoryError: Java heap space */ public class JavaHeapSpaceDemo { public static void main(String[] args) { byte[] bytes = new byte[1024 * 1024 * 50]; } }
3. java.lang.OutOfMemoryError:GC overhead limit exceeded【GC回收挂了】
/** * JVM参数配置 * -Xms10m -Xmx10m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m * * GC回收时间过长时会抛出OutOfMemoryError。过长的定义是,超过98%的时间用来做GC并且回收了不到2%的堆内存。 * 连续多次GC都只回收了不到2%的极端情况下才会抛出。假如,不抛出GC overhead limit错误会发生什么情况呢? * 那就是GC清理的那么点内存很快会再次填满,迫使GC再次执行,这样就形成了恶性循环,CPU使用率一直是100%,而GC没有任何成果 */ public class GCOverheadDemo { public static void main(String[] args) { int i = 0; List<Object> list = new ArrayList<>(); try { while (true) {list.add(String.valueOf(++i).intern());} //java.lang.OutOfMemoryError: GC overhead limit exceeded }catch (Throwable e) { System.out.println("*********i " + i); e.printStackTrace(); } } }
4. java.lang.OutOfMemoryError:Direct buffer memory【直接内存挂了】
主要是Netty、NIO所引起的,此异常反应你是否知道NIO
/** * 配置参数: * -Xms10m -Xmx10m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m * 故障现象: * Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory * 导致原因: * 写NIO程序经常使用ByteBuffer来读取或者写入,这是一种基于通道(Channel)于缓冲区(Buffer)的IO方式, * 它可以使用Native函数库直接分配堆外内存,然后通过一个直接存储在java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。 * 这样能在一些场景中显著的提高性能,因为避免了在Java堆和native堆中来回复制数据。 * * ByteBuffer.allocate(capability):第一种方式是分配JVM堆内存,属于GC管辖范围,由于需要拷贝所以速度相对较慢 * ByteBuffer.allocateDirect():第二种方式是分配OS本地内存,不属于GC管辖范畴,由于不需要内存拷贝所以速度相对较快。 * * 但如果不断分配本都内存,堆内存很少使用,那么JVM不需要执行GC,DirectByteBuffer对象就不会被回收, * 这时候堆内存充足,但是本地内存可能已经使用光了,那么在尝试分配本地内存就会出现OutOfMemoryError,程序直接崩溃 */ public class DirectBufferMemoryDemo { public static void main(String[] args) { System.out.println("配置的MaxDirectMemory:" + (sun.misc.VM.maxDirectMemory() / (double)1024/1024)+ "MB");//默认最大1/4 try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } // -XX:MaxDirectMemorySize=5m 我们配置为5m,但实际使用6m,故意使坏 ByteBuffer byteBuffer = ByteBuffer.allocateDirect(6 * 1024 * 1024); } }
5. java.lang.OutOfMemoryError:unable to create new native thread【无法创建新的本地线程】
linux系统下面,root用户创建线程无上限,其他用于理论默认1024,如果要想修改:vim /etc/security/limits.d/90-nproc.conf
/** * 高并发请求服务器时,经常出现如下异常:Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread * 准确来讲native thread异常与对应的平台有关 * * 导致原因: * 1. 你的应用创建了太多线程了,一个应用进程创建多个线程,超过系统承载极限; * 2. 你的服务器并不允许你创建这么多线程,Linux系统默认允许单个进程可以创建的线程数为1024个, * 你的应用创建超过这个数量,就会报java.lang.OutOfMemoryError: unable to create new native thread * * 解决方法: * 1. 想办法降低你应用程序创建线程的数量,分析应用真的需要创建这么多的线程,如果不是,改代码将线程数降到最低; * 2. 对于有的应用确实需要创建很多线程,远超过Linux系统的默认1024个线程的限制,可以通过修改Linux服务器配置,扩大Linux默认极限; */ public class UnableCreateNewThreadDemo { public static void main(String[] args) { for (int i = 0; ; i++) { System.out.println("**** i = " + i); new Thread(() -> { try { TimeUnit.SECONDS.sleep(Integer.MAX_VALUE); } catch (InterruptedException e) { e.printStackTrace(); } },String.valueOf(i)).start(); } } }
6. java.lang.OutOfMemoryError:Metaspace【元空间】
使用java -XX:+PrintFlagsInitial命令查看本机初始化参数,-XX:Metaspacesize为21810376B(大约20.8M)
/** * 异常:java.lang.OutOfMemoryError-->Metaspace * * JVM参数: * -XX:MetaspaceSize=8m -XX:MaxMetaspaceSize=8m * * Java8及以后的版本使用Metaspace来替代永久代。 * Metaspace是方法区在Hotspot中的实现,它与永久代最大的区别在于:Meatspace并不在虚拟机内存中而是使用本地内存, * * 元空间存放信息:虚拟机加载的类信息、常量池、静态变量、即时编译后的代码 * * 模拟Metaspace空间溢出,我们不断生成类往元空间灌,类占据的空间总会超过Metaspace指定的空间大小 */ public class MetaspaceOOMTest { static class OOMTest {} public static void main(String[] args) { int i = 0; try { while (true) { i++; Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(OOMTest.class); enhancer.setUseCache(false); enhancer.setCallback(new MethodInterceptor() { @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { return methodProxy.invokeSuper(o, args); } }); enhancer.create(); } }catch (Throwable e) { System.out.println("***多少次后发生了异常:" + i); e.printStackTrace(); } } }
*****************************************************************************************************************************
六、GC垃圾回收算法和垃圾收集器的关系?分别是什么你谈谈
1. GC算法(引用计数/复制/标清/标整)是内存回收的方法论,垃圾收集器就是算法的落地实现。
2. 因为目前为止还没有完美的收集器出现,更加没有万能的收集器,只是针对具体应用选最合适的收集器,进行分带收集。
3. 四种主要垃圾收集器:
Serial:串行回收 Parallel:并行回收 CMS:并发标记清除 G1 ZGC(java11/12)
Serial:串行垃圾回收器:【用餐、一个清洁工打扫卫生、用餐】会暂停
它为单线程环境设计且只使用一个线程进行垃圾回收,会暂停所有的用户线程。所以不适合服务器环境。
Parallel:并行垃圾回收器:【用餐、多个清洁工打扫卫生、用餐】会暂停
多个垃圾收集线程并行工作,此时用户线程是暂停的,适用于科学计算/大数据处理等若交互场景。
CMS:并发垃圾回收器:【边用餐边打扫卫生】
用户线程和垃圾收集线程同时执行(不一定并行,可能交替执行),不需要停顿用户线程,互联网公司多用它,适用对响应时间有要求的场景。但是会有内存碎片产生
G1垃圾回收器:
G1垃圾回收器将堆内存分割成不同的区域然后并发对其进行垃圾回收;
*****************************************************************************************************************************
七、怎么查看服务器的默认垃圾收集器是哪个?生产上是如何配置垃圾收集器的?谈谈你对垃圾收集器的理解?
天上飞的理念、必然有落地的实现
思想:垃圾收集算法思想
引用计数、复制拷贝、标记清除、标记整理
落地实现:垃圾收集器
串行回收、并行回收、并发回收、G1回收
1. 怎么查看默认的垃圾收集器是哪个?
/** * 默认垃圾收集器可以使用命令查看: * JVM参数: java -XX:+PrintCommandLineFlags -version * * 串行回收: -XX:UseSerialGC * 并行回收: -XX:UseParallelGC * Java8默认使用,并行回收:-XX:+UseParallelGC */
2. 默认的垃圾收集器有哪些:粗分4中,细分7种
Java的GC回收类型主要有几种:UseSerialGC、UseParallelGC、UseConcMarkSweepGC、UseParNewGC、UseParallelOldGC、UseG1GC
3. 垃圾收集器
3.1 部分参数预先说明:
3.2 Server/Client模式分别是什么意思:
3.3 新生代:
串行GC(Serial)/(Serial Copying)【串行收集器:Serial收集器】
一句话:一个单线程的收集器,在进行垃圾收集时候,必须暂停其他所有的工作线程直到它收集结束。
串行收集器是最古老的,最稳定的以及效率高的收集器,只使用一个线程去回收,但其在进行垃圾收集过程中可能会产生较长的停顿(Stop-The-World 状态)。虽然在收集垃圾过程中需要暂停其他的工作线程,但是它简单高效,对于限定单个CPU的环境来讲,没有线程交互的开销可以获得最高的单线程垃圾收集效率,因此Serial垃圾收集器依然是java虚拟机在运行在Client模式下默认的新生代垃圾收集器。
对应JVM参数是:-XX:+UseSerialGC
开启后会使用:Serial(Young区用)+Serial Old(Old区用)的收集器组合
表示:新生代、老年代都会使用串行垃圾回收器,新生代使用复制算法,老年代使用标记-整理算法。
-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+UseSerialGC
并行GC(ParNew)【ParNew(并行)收集器】
一句话:使用多线程进行垃圾回收,在收集垃圾时,会Stop-the-World会暂停其他所有的工作线程直到它收集结束。
ParNew收集器其实就是Serial收集器新生代的并行多线程版本,最常见的应用场景是配合老年代的CMS GC工作,其余的行为和Serial收集器完全一样,ParNew垃圾收集器在垃圾收集过程中同样也要暂停其它所有的工作线程。它是很多java虚拟机运行在Server模式下新生代的默认收集器。
常用对应JVM参数:-XX:+UseParNewGC 启用ParNew收集器,只影响新生代的收集,不影响老年代。开启上述参数后,会使用:ParNew(Young区用) + Serial Old的收集器组合,新生代使用复制算法,老年代采用标记-整理算法。
但是,ParNew+Tenured这样的搭配,java8已经不再推荐
备注:-XX:ParallelGCThreads 限时线程数量,默认开启和CPU数目相同的线程数
-Xms10m -Xmx10m -XX:PrintGCDetails -XX:+UseParNewGC
并行回收GC(Parallel)/(Parallel Scavenge)
Parallel Scavenge收集器类似ParNew也是一个新生代垃圾收集算法,使用复制算法,也是一个并行的的多线程的垃圾收集器,俗称吞吐量优先收集器。一句话:串行收集器在新生代和老年代的并行化。
它重点关注的是:可控制的吞吐量(Thoughput=运行用户代码时间/(运行用户代码时间+垃圾收集时间),也即比如程序运行100分钟,垃圾收集1分钟,吞吐量就是99% )。高吞吐量意味着高效利用CPU时间,它多用于后台运算而不需要太多的交互任务。
自适应调节策略也是ParallelScavenge收集器于ParNew收集器的一个重要区别。(自适应调节策略:虚拟机会根据当前系统的运行情况收集性能监控信息,动态调节这些参数已提供合适的停顿时间(-XX:MaxGCPauseMillis)或最大的吞吐量)
常用JVM参数:-XX:+UseParallelGC或-XX:+UseParallelOldGC(可互相激活),使用Parallel Scanvenge收集器
多说一句:-XX:ParallelGCThreads=数字N,表示启动多少个线程 cpu>8,N=5/8 cpu<8,N=实际个数
-Xms10m -Xmx10m -XX:PrintGCDetails -XX:+UseParallelGC
-Xms10m -Xmx10m -XX:PrintGCDetails -XX:+UseParallelOldGC
3.4 老年代:
串行GC(Serial Old)/(Serial MSC)
Serial Old是Serial垃圾收集器老年代版本,它同样是个单线程收集器,使用标记-整理算法,这个收集器也主要运行在Client默认的java虚拟机默认的年老代垃圾收集器。
在Server模式下,主要有两个用途(了解,版本已经到8以后):
1. 在JDK1.5之前版本中与新生代的Parallel Scavenge收集器搭配使用。(Parallel Scavenge + Serial Old)
2. 作为老年代中CMS收集器的后背垃圾收集方案。
并行GC(Parallel Old)/(Parallel MSC)
Parallel Old收集器是Parallel Scavenge的老年代版本,使用多线程的标记-整理算法,Parallel Old收集器在JDK1.6才开始提供。
在JDK1.6之前,新生代使用ParallelScavenge收集器只能搭配老年代的Serial Old收集器,只能保证新生代的吞吐量优先,无法保证整体的吞吐量。在JDK1.6之前(Parallel Scavenge + Serial Old)。Parallel Old正是为了在老年代同样提供吞吐量优先的垃圾收集器,如果系统对吞吐量要求比较高,JDK1.8后可以考虑新生代Parallel Scavenge和年老代Parallel Old收集器的搭配策略。在JDK1.8及以后(Parallel Scavenge + Parallel Old)
JVM常用参数:
-XX:+UseParallelOldGC 使用Parallel Old收集器,设置该参数后,新生代Parallel + 年老代Parallel Old
并发标记清除GC(CMS)
CMS收集器(Concurrent Mark Sweep:并发标记清除)是一种以获取最短回收停顿时间为目标的收集器。适合应用在互联网站或者B/S系统的服务器上,这类应用尤其重视服务器的响应速度,希望系统停顿时间最短。CMS非常适合堆内存大、CPU核数多的服务器端应用,也是G1出现之前大型应用的首选收集器。
Concurrent Mark Sweep 并发标记清除,并发收集低停顿,并发指的是和用户线程一起执行。
开启该收集器的JVM参数: -XX:+UseConcMarkSweepGC 开启该参数后会自动将 -XX:+UseParNewGC打开
开启该参数后,使用ParNew(Young区分) + CMS(Old区用) + Serial Old的收集器组合,Serial Old将作为CMS出错的后备收集器
-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+UseConcMarkSweepGC
1. 4步过程:
初始标记(CMS initial mark):只是标记一下GC Roots能直接关联的对象,速度很快,仍然需要暂停所以的工作线程。
并发标记(CMS concurrent mark)和用户线程一起:进行GC Roots的跟踪过程,和用户线程一起工作,不需要暂停工作线程。主要标记过程,标记全部对象。
重新标记(CMS remark):
为了修正在并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,任然需要暂停所以的工作线程。由于并发标记时,用户线程依然运行,因此在正式清理前,在做修正。
并发清除(CMS concurrent sweep)和用户线程一起:
清除了GC Roots不可达对象,和用户线程一起工作,不需要暂停工作线程。基于标记结果,直接清理对象,由于耗时最长的并发标记和并发清除,垃圾收集线程可以和用户线程一起并发工作,所以总体上来看,CMS收集器的内存回收和用户线程是一起并发执行的。
2. 优缺点:
优点:并发收集停顿低;
缺点:
并发执行对CPU的压力比较大:(由于是并发的,CMS的收集与用户线程会同时增加对堆内存的占用,也就是说,CMS必须要在老年代堆内存用尽之前完成垃圾回收,否则CMS回收失败时,将处罚担保机制,串行老年代收集器将会以SWT的方式进行一次GC,从而造成较大停顿时间)
采用标记清除算法会造成大量的内存碎片:(标记清除算法无法整理空间碎片,老年代空间会随着应用时长被逐步耗尽,最后将不得不通过担保机制对堆内存进行压缩。CMS也提供了参数-XX:CMSFullGCsBeForeCompaction[默认0,即每次都进行内存整理]来指定多少次CMS收集之后,进行一次压缩的Full GC。)
3.5 垃圾收集器配置代码总结:
4. 如何选择垃圾收集器
* 单CPU或者小内存,单机程序:-XX:+UseSerialGC
* 多CPU,需要最大吞吐量,如后台计算型应用:-XX:+UseParallelGC 或者 -XX:+UseParallelOldGC
* 多CPU,追求低停顿时间,需快速响应互联网应用:-XX:+UseConcMarkSweepGC 或 -XX:+ParNewGC
*****************************************************************************************************************************
八、G1垃圾收集器:
1. 以前收集器特点:
年轻代和老年代各自独立,且连续的内存块;
年轻代收集使用单eden + s0 + s1进行复制算法;
老年代收集必须扫描整个老年代区域;
都是以尽可能少而快速地执行GC为设计原则;
2. G1是这么:
G1(Garbage-First)收集器,是一款面向服务端应用的收集器;从官网的描述中,我们知道G1是一种服务器端的垃圾收集器,应用在多处理器和大容量内存环境中,在实现高吞吐量的同时,尽可能的满足垃圾收集暂停时间的要求。另外,它还具有以下特征:
像CMS收集器一样,能与应用程序线程并发执行;
整理空闲空间更快;
需要更更多地时间来预测GC停顿时间;
不希望牺牲大量的吞吐性能;
不需要更大的Java Heap;
G1收集器的设计目的是取代CMS收集器,它同CMS相比,在以下方面表现的更出色:
G1是一个有整理内存过程的垃圾收集器,不会产生很多内存碎片;
G1的Stop World(STW)更可控,G1在停顿时间上添加了预测机制,用户可以指定期望预测时间。
CMS垃圾收集器虽然减少了暂停应用程序的运行时间,但是它还是存在着内存碎片问题。于是,为了去除内存碎片问题,同时又保留CMS垃圾收集器低暂停时间的优点,JAVA7发布了一个新的垃圾收集器-G1垃圾收集器。
G1是在2012年才在jdk1.7中可用。Oracle官网计划在jdk9中将G1变成默认的垃圾收集器以替代CMS。它是一款面向服务端应用的收集器,主要应用在多CPU和大内存服务器环境下,极大减少垃圾收集停顿时间,全面提升服务器性能,逐步替代java8以前的CMS收集器。主要改变是Eden,Survivor和Tenured等内存区域不再是连续的了,而是变成了一个个大小的region,每个region从1M到32M不等。一个region有可能属于Eden,Survivor或者Tenured内存区域。
特点:
1. G1能充分利用多CPU、多核环境硬件优势、尽量缩短ATW;
2. G1整体上采用标记-整理算法,局部是通过复制算法,不会产生内存碎片;
3. 宏观上看G1不再区分年轻代和老年代。把内存划分成多个独立的子区域(region),可以近似理解为一个围棋的棋盘。
4. G1收集器里面整个内存区都混合在一起,但其本身依然要在小范围内要进行年轻代和老年代的区分,保留了新生代和老年代,但它们不再是物理隔离,而是一部分region的集合且不需要region是连续的,也就说依然会采用不同的GC方式来处理不同的区域。
5. G1虽然是分代收集器,但整个内存分区不存在物理上的年轻代和老年代的区别,也不需要完全独立的survivor(to space)堆做复制准备。G1只有逻辑上的分代概念,或者说每个分区都有可能随G1的运行在不同代之间前后切换。
3. 底层原理:
region区域化垃圾收集器:
最大好处化整为零,避免全内存扫描,主需要按照区域来扫描即可。
区域化内存划片region,整体变为了一系列不连续的内存碎片,避免了全内存区的GC操作。核心思想是将整个堆内存区域分成大小相同的子区域(region),在JVM启动时会自动设置这些子区域的大小,在堆得使用上,G1并不要求对象的存储一定是物理上连续的,只要是逻辑上连续即可,每个分区也不会固定的为某个代服务,可以按需在年轻代和老年代之间切换。启动时可以通过参数-XX:G1HeapRegionSize=n可指定分区大小(1MB~32MB,且必须是2的幂),默认将堆划分为2048个分区。大小范围在1MB~32MB,最多设置2048个区域,也即能支持的最大内存为:32MB * 2048 = 65536MB = 64G。
回收步骤:
4步过程:
4. case案例:
5. 常用配置参数(了解):
-XX:+UseG1GC
-XX:G1HeapRegionSize=n:设置G1区域的大小、值时2的幂,范围是1MB到32MB。目标是根据最小的Java堆大小划分出2048个区域。
-XX:MaxGCPauseMillis=n:最大GC停顿时间,这个是软目标,JVM将尽可能(但不保证)停顿小于这个时间。
-XX:InitiatingHeapOccupancyPercent=n:堆占用多少的时候就触发GC,默认为45。
-XX:ConcGCThreads=n:并发GC使用的线程数
-XX:G1ReserverPercent=n:设置作为空闲空间的预留内存百分百,以降低内存空间溢出的风险,默认值为10%
开发人员仅仅需要声明一下参数即可:
三步归纳:开始G1 + 设置最大内存 + 设置最大停顿时间
-XX:+UseG1GC -Xmx32g -XX:MaxGCPauseMillis=100
6. 和CMS相比的优势:
1):G1不会产生内存碎片;
2):是可以精确控制停顿。该收集器是把整个堆(新生代、老生代)划分成多个固定大小的区域,每次根据允许停顿的时间去收集垃圾最多的区域。
7. 小总结:
*****************************************************************************************************************************
九、生产环境服务器变慢,诊断思路和性能评估谈谈?
*****************************************************************************************************************************
十、假如生产环境出现CPU占用过高,请谈谈你的分析思路和定位
*****************************************************************************************************************************
十一、对于JDK自带的JVM监控和性能分析工具用过哪些?一般你是怎么用的?
*****************************************************************************************************************************
*****************************************************************************************************************************
*****************************************************************************************************************************