1、JVM垃圾回收的时候如何确定垃圾?是否知道什么是 GC Roots
什么是垃圾:简单的说就是内存中已经不再被使用到的空间就是垃圾
要进行垃圾回收,如何判断一个对象是否可以被回收
① 引用计数法
Java中,引用和对象是有关联的。如果要操作对象则必须用引用进行
因此,很显然一个简单的办法是通过引用计数来判断一个对象是否可以回收。简单说,给对象中添加一个引用计数器,
每当有一个地方引用它,计数器值加1
每当有一个引用失效时,计数器值减1。
任何时刻计数器值为零的对象就是不可能再被使用的,那么这个对象就是可回收对象
那为什么主流的Java虚拟机里面都没有选用这种算法呢?其中最主要的原因是它很难解决对象之间相互循环引用的问题
②枚举根节点做可达性分析(根搜索路径)
为了解决引用计数法的循环引用问题,Java使用了可达性分析的方法
所谓“GC roots”或者说tracing GC的“根集合”就是一组必须活跃的引用。
基本思路就是通过一系列名为“GC Roots”的对象为起始点,从这个被称为GC Roots的对象开始向下搜索,如果一个对象到GC Roots没有任何引用链相连时,则说明此对象不可用。也即给定一个集合的引用作为根出发,通过引用关系遍历对象图,能被遍历到的(可到达的)对象就被判定为存活;没有被遍历到的就自然被判定为死亡。
--> case
--> Java 可以做GCRoots的对象
(1) 虚拟机栈(栈帧中的局部变量区,也叫做局部变量表)中引用的对象
(2) 方法区中的类静态属性引用的对象。
(3) 方法区中常量引用的对象
(4) 本地方法栈中JNI( Native方法)引用的对象
2、你说你做过JVM调优和参数配置,请问如何盘点查看MM系统默认值
2.1 JVM的参数类型
(1) 标配参数
-verison
-help
-showversion
(2) X参数(了解)
-Xint 解释执行
-Xcomp 第一次使用就编译成本地代码
-Xmixed 混合模式
(3) XX参数
① Boolean类型
公式 :-XX:+或者- 某个属性值
+表示开启 -表示关闭
case
是否打印GC收集细节 -XX:+PrintGCDetails
-XX:-PrintGCDetails
是否使用串行垃圾收集器 -XX:-UseSerialGC
-XX:+UseSerialGC
② KV设值类型
公式: -XX:属性key=属性值value
Case:
元空间 -XX:MetaspaceSize=128m
-XX:MaxTenuringThreshold=15
③ jinfo举例,如何查看当前运行程序的配置
公式 :jinfo -flag 配置项 进程编号
④ 题外话(坑题)
两个经典参数:-Xms和-Xmx 属于-XX参数
-Xms 等价于 -XX:InitialHeapSize
-Xmx 等价于-XX:MaxHeapSize
2.2 查看JVM默认值
(1) -XX:+PrintFlagsInitial 查看初始默认值
公式 ① java -XX:+PrintFlagsInitial -version ② java -XX:+PrintFlagsInitial
Case
(2) -XX:+PrintFlagsFinal 主要查看修改更新
公式 ① java -XX:+PrintFlagsFinal ② java -XX:+PrintFlagsFinal -version
Case
(3) PrintFlagsFinal举例,运行Java命令的同时打印出参数
(4) -XX:+PrintCommandLineFlags 打印命令行参数
3、你平时工作用过的JVM常用基本配置参数有哪些?
3.1基础知识复习
//返回Java虚拟机中的内存总量。 long totalMemory = Runtime.getRuntime().totalMemory(); //返回Java虚拟机试图使用的最大内存量。 long maxMemory = Runtime.getRuntime().maxMemory(); System.out.println("TOTAL_MEMORY(-Xms) = " + totalMemory + " (字节), " + (totalMemory / (double) 1024 / 1024) + " MB"); System.out.println("MAX_MEMORY(-Xmx) = " + maxMemory + " (字节), " + (maxMemory / (double) 1024 / 1024) + " MB");
3.2 常用参数
(1) -Xms 初始大小内存,默认为物理内存1/64 ,等价于-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收集日志信息
GC
FullGC
规律:
[名称: GC前内存占用 -> GC后内存占用 (该区内存总大小)
(8) -XX:SurvivoRatio
设置新生代中eden和S0/S1空间的占比
默认
-XX:SurvivorRatio=8,Eden:S0:S1 = 8:1:1
-XX:+UseSerialGC 串行化GC的情况下
默认 -XX:+UseParallelGC jdk8并行GC
假如
-XX:SurvivorRatio=4 ,Eden:S0:S1=4:1:1
SurvivorRatio值就是设置eden区的比例占多少,S0/S1相同
(9) -XX:NewRatio
默认年轻代与老年代在堆结构上的占比
默认
-XX:NewRatio=2 新生代占1,老年代2,年轻代占整个堆的1/3
-XX:+UseSerialGC 串行化GC的情况下
默认 -XX:+UseParallelGC jdk8并行GC
假如
-XX:NewRatio=4 新生代占1,老年代占4,年轻代占整个堆的1/5
NewRatio值就是设置老年代的占比,剩下的1给新生代
(10) -XX:MaxTenuringThreshold 设置垃圾最大年龄
查看默认进入老年代年龄:
D:\project\my_project\interview_second_jvm>jps -l 7984 com.shuidiit.second.jvm.HelloGC 4436 7812 org.jetbrains.jps.cmdline.Launcher 11468 sun.tools.jps.Jps D:\project\my_project\interview_second_jvm>jinfo -flag MaxTenuringThreshold 7984 -XX:MaxTenuringThreshold=15
-XX:MaxTenuringThreshold=0 :设置垃圾最大年龄,如果设置为0的话,则年轻代对象不经过Survivor区,直接进入老年代。对于老年代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象在年轻代的存活时间,增加在年轻代即被回收的概论。
4、强引用、软引用、弱引用、虚引用分别是什么?
4.1 整体架构
4.2 强引用(默认支持模式)
当内存不足,JVM开始垃圾回收,对于强引用的对象,就算是出现了OOM也不会对该对象进行回收,死都不收。
强引用是我们最常见的普通对象引用,只要还有强引用指向一个对象,就能表明对象还“活着”,垃圾收集器不会碰这种对象。在Java中最常见的就是强引用,把一个对象赋给一个引用变量,这个引用变量就是一个强引用。当一个对象被强引用变量引用时,它处于可达状态,它是不可能被垃圾回收机制回收的,即使该对象以后永远都不会被用到JVM也不会回收。因此强引用是造成Java内存泄漏的主要原因之一。
对于一个普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显式地将相应(强)引用赋值为null,一般认为就是可以被垃圾收集的了(当然具体回收时机还是看垃圾收集策略)
4.3 软引用
软引用是一种相对强引用弱化了一些的引用,需要用java.lang.ref.SortReference类来实现,可以让对象避免一些垃圾收集。
对于只有软引用的对象来说,
当系统内存充足时它 不会 被回收,
当系统内存不足时它 会 被回收
软引用通常用在对内存敏感的程序中,比如高速缓存就有用到软引用,内存够用的时候就保留,不够用就回收
package com.shuidiit.second.jvm; import java.lang.ref.SoftReference; /** * Created by shizan on 2020/2/16. * 内存够用的时候就保留,不够用就回收 */ public class SoftReferenceDemo { /** * 内存够用的时候就保留,不够用就回收! */ public static void softRef_Memory_Enough() { Object o1 = new Object(); SoftReference<Object> softReference = new SoftReference<>(o1); System.out.println(o1);//有值 System.out.println(softReference.get());//有值 o1 = null; System.gc(); System.out.println(o1);//null System.out.println(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);//有值 System.out.println(softReference.get());//有值 o1 = null; try { byte[] bytes = new byte[30 * 1024 * 1024]; } catch (Throwable e) { e.printStackTrace(); } finally { System.out.println(o1);//null System.out.println(softReference.get());//null } } public static void main(String[] args) { // softRef_Memory_Enough(); softRef_Memory_NotEnough(); } }
4.4 弱引用
弱引用需要用java.lang.ref.WeakReference类来实现,它比软引用的生存期更短
对于只有弱引用的对象来说,只要垃圾回收机制一运行,不管JVM的内存空间是否足够,都会回收该对象占用的内存
(1)case
package com.shuidiit.second.jvm; import java.lang.ref.SoftReference; import java.lang.ref.WeakReference; /** * Created by shizan on 2020/2/16. */ public class WeakReferenceDemo { public static void main(String[] args) { Object o1 = new Object(); WeakReference<Object> weakReference = new WeakReference<>(o1); System.out.println(o1); System.out.println(weakReference.get()); o1 = null; System.gc(); System.out.println("============================"); System.out.println(o1); System.out.println(weakReference.get()); } } 运行结果: "E:\Program Files\Java\jdk1.8.0_144\bin\java" "-javaagent:E:\Program Files\JetBrains\IntelliJ IDEA 2018.1\lib\idea_rt.jar=55943:E:\Program Files\JetBrains\IntelliJ IDEA 2018.1\bin" -Dfile.encoding=UTF-8 -classpath "E:\Program Files\Java\jdk1.8.0_144\jre\lib\charsets.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\deploy.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\access-bridge-64.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\cldrdata.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\dnsns.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jaccess.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jfxrt.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\localedata.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\nashorn.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunec.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunjce_provider.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunmscapi.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunpkcs11.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\zipfs.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\javaws.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\jce.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\jfr.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\jfxswt.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\jsse.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\management-agent.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\plugin.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\resources.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\rt.jar;D:\project\my_project\interview_second_jvm\out\production\interview_second_jvm" com.shuidiit.second.jvm.WeakReferenceDemo java.lang.Object@4554617c java.lang.Object@4554617c ============================ null null Process finished with exit code 0
(2) 软引用和弱引用的适用场景
假如有一个应用需要读取大量的本地图片:
● 如果每次读取图片都从硬盘读取则会严重影响性能,
● 如果一次性全部加载到内存中有可能造成内存溢出。
此时使用软引用可以解决这个问题
设计思路是:用一个HashMap来保存图片的路径和相应图片关联的软引用之间的映射关系,在内存不足时,JVM会自动回收这些缓存图片对象所占用的空间,从而有效地避免了OOM的问题。
Map<String,SoftReference<Bitmap>> imageCache = new HashMap<String,SoftReference<Bitmap>>();
(3) 你知道弱引用的话,能谈谈WeakHashMap吗?
package com.shuidiit.second.jvm; import java.util.HashMap; import java.util.WeakHashMap; /** * Created by shizan on 2020/2/16. */ public class WeakHashMapDemo { public static void main(String[] args) { myHashmap(); System.out.println("==================="); myWeakHshMap(); } private static void myWeakHshMap() { WeakHashMap<Integer, String> map = new WeakHashMap<>(); Integer key = new Integer(2); String value = "WeakHashMap"; map.put(key, value); System.out.println(map); key = null; System.out.println(map); System.gc(); System.out.println(map + "\t" + map.size()); } 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 + "\t" + map.size()); } } 运行结果: "E:\Program Files\Java\jdk1.8.0_144\bin\java" "-javaagent:E:\Program Files\JetBrains\IntelliJ IDEA 2018.1\lib\idea_rt.jar=56256:E:\Program Files\JetBrains\IntelliJ IDEA 2018.1\bin" -Dfile.encoding=UTF-8 -classpath "E:\Program Files\Java\jdk1.8.0_144\jre\lib\charsets.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\deploy.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\access-bridge-64.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\cldrdata.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\dnsns.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jaccess.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jfxrt.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\localedata.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\nashorn.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunec.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunjce_provider.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunmscapi.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunpkcs11.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\zipfs.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\javaws.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\jce.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\jfr.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\jfxswt.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\jsse.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\management-agent.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\plugin.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\resources.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\rt.jar;D:\project\my_project\interview_second_jvm\out\production\interview_second_jvm" com.shuidiit.second.jvm.WeakHashMapDemo {1=HashMap} {1=HashMap} {1=HashMap} 1 =================== {2=WeakHashMap} {2=WeakHashMap} {} 0 Process finished with exit code 0
4.5 虚引用
虚引用需要java.lang.ref.PhantomReference类来实现。
顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收,它不能单独使用也不能通过它访问对象,虚引用必须和引用队列(ReferenceQueue)联合使用。
虚引用的主要作用是跟踪对象被垃圾回收的状态。仅仅是提供了一种确保对象被finalize以后,做某些事情的机制。PhantomReference的get方法总是返回null,因此无法访问对应的引用对象。其意义在于说明一个对象已经进入finalization阶段,可以被gc回收,用来实现比finalization机制更灵活的回收操作。
换句话说,设置虚引用关联的唯一目的,就是在这个对象被收集器回收的时候收到一个系统通知或者后续添加进一步的处理。Java技术允许使用finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。
(1) 引用队列
引用队列case:
package com.shuidiit.second.jvm; import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; /** * Created by shizan on 2020/2/16. */ 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(); Thread.sleep(500); System.out.println(o1); System.out.println(weakReference.get()); System.out.println(referenceQueue.poll()); } } 运行结果: "E:\Program Files\Java\jdk1.8.0_144\bin\java" "-javaagent:E:\Program Files\JetBrains\IntelliJ IDEA 2018.1\lib\idea_rt.jar=56850:E:\Program Files\JetBrains\IntelliJ IDEA 2018.1\bin" -Dfile.encoding=UTF-8 -classpath "E:\Program Files\Java\jdk1.8.0_144\jre\lib\charsets.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\deploy.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\access-bridge-64.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\cldrdata.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\dnsns.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jaccess.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jfxrt.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\localedata.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\nashorn.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunec.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunjce_provider.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunmscapi.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunpkcs11.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\zipfs.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\javaws.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\jce.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\jfr.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\jfxswt.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\jsse.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\management-agent.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\plugin.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\resources.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\rt.jar;D:\project\my_project\interview_second_jvm\out\production\interview_second_jvm" com.shuidiit.second.jvm.ReferenceQueueDemo java.lang.Object@4554617c java.lang.Object@4554617c null ======================= null null java.lang.ref.WeakReference@74a14482 Process finished with exit code 0
(2) 虚引用case
package com.shuidiit.second.jvm; import java.lang.ref.PhantomReference; import java.lang.ref.Reference; import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; /** * Created by shizan on 2020/2/16. * * 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(); Thread.sleep(500); System.out.println(o1); System.out.println(phantomReference.get()); System.out.println(referenceQueue.poll()); } } 运行结果: "E:\Program Files\Java\jdk1.8.0_144\bin\java" "-javaagent:E:\Program Files\JetBrains\IntelliJ IDEA 2018.1\lib\idea_rt.jar=56944:E:\Program Files\JetBrains\IntelliJ IDEA 2018.1\bin" -Dfile.encoding=UTF-8 -classpath "E:\Program Files\Java\jdk1.8.0_144\jre\lib\charsets.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\deploy.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\access-bridge-64.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\cldrdata.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\dnsns.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jaccess.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jfxrt.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\localedata.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\nashorn.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunec.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunjce_provider.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunmscapi.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunpkcs11.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\zipfs.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\javaws.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\jce.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\jfr.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\jfxswt.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\jsse.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\management-agent.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\plugin.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\resources.jar;E:\Program Files\Java\jdk1.8.0_144\jre\lib\rt.jar;D:\project\my_project\interview_second_jvm\out\production\interview_second_jvm" com.shuidiit.second.jvm.PhantomReferenceDemo java.lang.Object@4554617c null null =========================== null null java.lang.ref.PhantomReference@74a14482 Process finished with exit code 0
4.6 GCRoots和四大引用的小总结
5、请谈谈你对OOM的认识
5.1 Java.lang.StackOverflowError
package com.shuidiit.second.jvm; /** * Created by shizan on 2020/2/16. */ public class StackOverflowErrorDemo { public static void main(String[] args) { stackOverflowError(); } private static void stackOverflowError() { stackOverflowError();//Exception in thread "main" java.lang.StackOverflowError } }
5.2 Java.lang.OutOfMemoryError:Java heap space
package com.shuidiit.second.jvm; import java.util.Random; /** * Created by shizan on 2020/2/16. */ public class JavaHeapSpaceDemo { public static void main(String[] args) { String str = "shuidiit"; while (true) { str += str + new Random().nextInt(111111111) + new Random().nextInt(222222222); str.intern(); } } }
5.3 Java.lang.OutOfMemoryError:GC overhead limit exceeded
GC 回收时间过长时会抛出OutOfMemoryError。过长的定义是,超过98%的时间用来做GC并且回收了不到2%的堆内存连续多次GC 都只回收了不到2%的极端情况下才会抛出。假如不抛出GC overhead limit 错误会发生什么情况呢?那就是GC清理这么点内存很快会再次填满,迫使GC再次执行。这样就形成恶性循环,CPU使用率一直是100%,而GC却没有任何成果
package com.shuidiit.second.jvm; import java.util.ArrayList; import java.util.List; /** * Created by shizan on 2020/2/16. * JVM 参数配置演示 * -Xms10m -Xmx10m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m */ public class GCOverheadDemo { public static void main(String[] args) { int i = 0; List<String> list = new ArrayList<>(); try { while (true) { list.add(String.valueOf(++i).intern()); } } catch (Throwable e) { System.out.println("***********i: " + i); e.printStackTrace(); throw e; } } }
5.4 Java.lang.OutOfMemeoryError:Direct buffer memory
写NIO程序经常使用ByteBuffer来读取或者写入数据,这是一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在Java堆和Native堆中来回复制数据。
ByteBuffer.allocate(capability)第一种方式是分配JVM堆内存,属于GC管辖范围,由于需要拷贝所以速度相对较慢
ByteBuffer.allocateDirect(capability)第一种方式是分配OS本地内存,不属于GC管理范围,由于不需要内存拷贝所以速度相对较快。
但如果不断分配本地内存,堆内存很少使用,那么JVM就不需要执行GC,DirectByteBuffer对象们就不会被回收,这时候堆内存充足,但本地内存可能已经使用光了,再次尝试分配本地内存就会出现OutOfMemoryError ,那程序就直接崩溃了
package com.shuidiit.second.jvm; import java.nio.ByteBuffer; /** * Created by shizan on 2020/2/16. * JVM 参数配置演示 * -Xms10m -Xmx10m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m */ public class DirectBufferMemoryDemo { public static void main(String[] args) { System.out.println("配置的maxDirectMemory: " + (sun.misc.VM.maxDirectMemory() / (double) 1024 / 1024) + " MB"); try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } // -XX:MaxDirectMemorySize=5m 我们配置为5MB,但实际使用6MB,故意使坏 ByteBuffer bb = ByteBuffer.allocateDirect(6 * 1024 * 1024); } }
5.5 Java.lang.OutOfMemeoryError:unable to create new native thread
高并发请求服务器时,经常出现如下异常:Java.lang.OutOfMemeoryError:unable to create new native thread 准确的讲该native thread 异常与对应的平台有关
导致原因:
(1) 你的应用创建了太多线程了, 一个应用进程创建多个线程,超过系统承载极限
(2) 你的服务器并不允许你的应用程序创建这么多线程,linux系统默认允许单个进程可以创建的线程数是1024个,你的应用创建超过这个数量,就会报 Java.lang.OutOfMemeoryError:unable to create new native thread
解决办法:
(1) 想办法降低你应用程序创建线程的数量,分析应用是否真的需要创建这么多线程,如果不是,改代码将线程数降到最低
(2) 对于有的应用,确实需要创建很多线程,远超过linux系统默认1024个线程的限制,可以通过修改linux服务器配置,扩大linux默认限制
package com.shuidiit.second.jvm; /** * Created by shizan on 2020/2/17. */ public class UnableCreateNewThreadDemo { public static void main(String[] args) { for (int i = 1; ; i++) { System.out.println("************* i = " + i); new Thread(() -> { try { Thread.sleep(Integer.MAX_VALUE); } catch (InterruptedException e) { e.printStackTrace(); } }, "" + i).start(); } } }
非root用户登录Linux系统测试
服务器级别参数调优
查看线程数 ulimit -u
vim /etc/security/limits.d/20-nproc.conf 查看配置
5.6 Java.lang.OutOfMemeoryError:Metaspace
使用Java -XX:+PrintFlagsInitial命令查看本机的初始化参数,-XX:MetaspaceSize为21810376B(约20M)
Java 8及之后的版本使用Metaspace来替代永久代。
Metaspace是方法区在HotSpot中的实现,它与持久代最大的区别在于:Metaspace并不在虚拟机内存中而是使用本地内存也即java8中,class metadata(the virtual machines internal presentation of Java class),被存储在叫做Metaspace的native memory
永久代 (java 8后被元空间Metaspace取代了)存放了以下信息:
● 虚拟机加载的类信息
● 常量池
● 静态变量
● 即时编译后的代码
模拟Metaspace空间溢出,我们不断生成类往元空间灌,类占据的空间总是会超过Metaspace指定的空间大小的
6、GC垃圾回收算法和垃圾收集器的关系?分别是什么请你谈谈
GC算法(引用计数/复制/标清/标整)是内存回收的方法论,垃圾收集器就是算法落地实现
因为目前为止还没有完美的收集器出现,更加没有万能的收集器,只是针对具体应用最合适的收集器,进行分代收集
4种主要垃圾收集器
(1) 串行垃圾回收器(Serial):它为单线程环境设计并且只使用一个线程进行垃圾回收,会暂停所有的用户线程。所以不适合服务器环境
(2) 并行垃圾回收器(Parallel):多个垃圾回收线程并行工作,此时用户线程是暂停的,适用于科学计算/大数据处理等弱交互场景
(3) 并发垃圾回收器(CMS):用户线程和垃圾收集线程同时执行(不一定是并行,可能交替执行),不需要停顿用户线程
互联网公司多用它,适用于对响应时间有要求的场景
以上三个小总结:
(4) G1垃圾回收器:G1垃圾回收器将堆内存分割成不同的区域然后并发的对其进行垃圾回收
7、怎么查看服务器默认的垃圾收集器是那个? 生产上如何配置垃圾收集器的? 谈谈你对垃圾收集器的理解?
7.1 怎么查看默认的垃圾收集器是哪个?
java -XX:+PrintCommandLineFlags -version
7.2 默认的垃圾收集器有哪些
java的gc回收的类型主要有几种:
UseSerialGC,UseParallelGC,UseConcMarkSweepGC,UseParNewGC,UseParallelOldGC
看下面的代码就非常清楚
7.3 垃圾收集器
垃圾收集器就来具体实现这些GC算法并实现内存回收。不同厂商、不同版本的虚拟机实现差别很大,HotSpot中包含的收集器如下所示:
红色表示java8版本开始,对应的垃圾收集器Deprecated,不推荐使用。
(1) 部分参数预先说明
① DefNew : Default New Generation
② Tenured : Old
③ ParNew : Parallel New Generation
④ PSYoungGen:Parallel Scavenge
⑤ ParOldGen:Parallel Old Generation
(2) Server/Client模式分别是什么意思
① 适用范围:只需要掌握Server模式即可,Client模式基本不会用
② 操作系统:
32位Window操作系统,不论硬件如何都默认使用Client的JVM模式
32位其他操作系统,2G内存同时有2个cpu以上用Server模式,低于该配置还是Client模式
64位only server模式
(3) 新生代
① 串行GC(Serial)/(Serial Coping)
【串行收集器:Serial收集器】
一句话:一个单线程的收集器,在进行垃圾收集时候,必须暂停其他所有的工作线程直到它收集结束。
串行收集器是最古老,最稳定以及效率高的收集器,只使用一个线程去回收但其在进行收集过程中可能会产生较长的停顿(Stop-The-World状态)。虽然在收集垃圾过程中需要暂停所有其他的工作线程,但是它简单高效,对于限定单个CPU环境来说,没有线程交互的开销可以获得最高的单线程收集效率,因此Serial垃圾收集器依然是java虚拟机运行在Client模式下默认的新生代垃圾收集器。
对应JVM参数是:-XX:+UseSerialGC
开启后会使用:Serial(Young区用)+Serial Old(Old区用)的收集器组合
表示:新生代,老年代都会使用串行回收收集器,新生代使用复制算法,老年代使用标记-整理算法
-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+UseSerialGC
② 并行GC(ParNew)
一句话:使用多线程进行垃圾回收,在垃圾收集时,会Stop-the-World暂停其他所有的工作线程直到它收集结束。
ParNew收集器其实就是Serial收集器新生代的并行多线程版本,最常见的应用场景是配合老年代的CMS GC工作,其余的行为和Serial收集器完全一样,ParNew垃圾收集器在垃圾收集过程中同样也要暂停所有其他的工作线程。它是很多java虚拟机运行在Server模式下新生代的默认垃圾收集器。
常用对应JVM参数:-XX+UseParNewGC 启用ParNew收集器,只影响新生代的收集,不影响老年代,开启上述参数后,会使用:ParNew(Yong区用)+Serial Old 的收集器组合,新生代使用复制算法,老年代采用标记-整理算法
但是,ParNew+Tenured这样的搭配,java8已经不再被推荐
Java HotSpot(TM)64-Bit Server VM Waring
Using the ParNew young collector with the Serial old collector is deprecated and will likely be removed in a future release
备注:
-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 表示启动多少个GC线程
cpu>8 N = 5/8
cpu<8 N = 实际个数
-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+UseParallelGC
-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+UseParallelOldGC
(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
4步过程
1) 初始标记(CMS initial mark)
2) 并发标记(CMS concurrent mark)和用户线程一起
进行GC Roots跟踪的过程,和用户线程一起工作,不需要暂停工作线程。主要标记过程,标记全部对象
3) 重新标记(CMS remark)
为了修正在并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,仍然需要暂停所有的工作线程。
由于并发标记时,用户线程依然运行,因此在正式清理前,再做修正
4) 并发清除(CMS concurrent sweep)和用户线程一起
清除GC Roots不可达对象,和用户线程一起工作,不需要暂停工作线程。基于标记结果,直接清理对象
由于耗时最长的并发标记和并发清除过程中,垃圾收集器线程可以和用户线程一起并发工作,所以总体上来看CMS收集器的内存回收和用户线程是一起并发地执行。
优缺点:
优:并发收集低停顿
缺: 并发执行,对CPU资源压力大
由于并发进行,CMS在收集与应用线程会同时增加对堆内存的占用,也就是说,CMS必须要在老年代堆内存用尽之前完成垃圾回收,否则CMS回收失败时,将触发担保机制,串行老年代收集器将会以STW的方式进行一次GC,从而造成较大停顿时间
采用的标记清除算法会导致大量碎片
标记清除算法无法整理空间碎片,老年代空间会随着应用时长被逐步耗尽,最后将不得不担保机制对堆内存进行压缩。CMS也提供了参数-XX:CMSFullGCsBeForeCompaction(默认0,即每次都进行内存整理)来指定多少次CMS收集之后,进行一次压缩的Full GC.
(5) 垃圾收集器配置代码总结
7.4 如何选择垃圾收集器
组合的选择
- CPU或小内存,单机程序
-XX:+UseSerialGC
- CPU,需要最大量吞吐量,如后台计算型应用
-XX:+UseParallelGC 或者
-XX:+UseParallelOldGC
- CPU,追求低停顿时间,需快速响应如互联网应用
-XX:+UseConcMarkSweepGC
-XX:+ParNewGC
参数 | 新生代垃圾收集器 | 新生代算法 | 老年代垃圾收集器 | 老年代算法 |
-XX:+UseSerialGC | SerialGC | 复制 | SerialOldGC | 标整 |
-XX:+UseParNewGC | ParNew | 复制 | SerialOldGC | 标整 |
-XX:+UseParallelGC/-XX:+UseParallelOldGC | Parallel[Scavenge] | 复制 | Parallel Old | 标整 |
-XX:+UseConcMarkSweepGC | ParNew | 复制 | CMS+Serial Old的收集器组合(Serial Old作为CMS出错的后备收集器) | 标清 |
-XX:+UseG1GC | G1整体上采用标记-整理算法 | 局部是通过复制算法,不会产生内存碎片 |
8、G1垃圾收集器
8.1 以前收集器特点
(1) 年轻代和老年代是各自独立且连续的内存块
(2) 年轻代收集使用单eden+S0 +S1进行复制算法
(3) 老年代收集必须扫描整个老年代区域
(4) 都是以尽可能少而快速地执行GC为设计原则
8.2 G1是什么
G1(Garbage-First) 收集器,是一款面向服务端应用的收集器
从官网的描述中,我们知道G1是一种服务端的垃圾收集器,应用在多处理器和大容量内存环境中,在实现高吞吐量的同时,尽可能的满足垃圾收集暂停时间的要求。另外,它还具有以下特点:
像CMS收集器一样,能与应用程序线程并发执行。
整理空闲空间更快。
需要更多的时间来预测GC停顿时间。
不需要牺牲大量的吞吐性能。
不需要更大的Java Heap。
G1收集器的设计目标是取代CMS收集器,它同CMS相比,在以下方面表现的更出色:
G1是一个有整理内存过程的垃圾收集器,不会产生很多内存碎片。
G1的Stop The Word(STW)更可控,G1在停顿时间上添加了预测机制,用户可以指定期望停顿时间。
CMS垃圾收集器虽然减少了暂停应用程序的运行时间,但它还是存在着内存碎片问题。于是,为了去除内存碎片问题,同时又保留CMS垃圾收集器低暂停时间的优点,JAVA7发布了一个新的垃圾收集器-G1垃圾收集器。
G1是2012年才在jdk1.7u4中可用。oracle官方计划在jdk9中将G1变成默认的垃圾收集器以替代CMS。它是一款面向服务端应用的收集器,主要应用在多CPU和大内存服务器环境下,极大的减少垃圾收集的停顿时间,全面提升服务器的性能,逐步替换java8以前的CMS收集器。
主要改变是Eden,Survivor和Tenured等内存区域不再是连续的了,而是变成了一个个大小一样的region,每个region从1M到32M不等。一个region有可能属于Eden,Survivor或者Tenured内存区域。
特点:
- 能充分利用多CPU,多核环境硬件优势,尽量缩短STW。
- 整体上采用标记-整理算法,局部是通过复制算法,不会产生内存碎片。
- G1之中不再区分年轻代和老年代。把内存划分成多个独立的子区域(Region),可以近似理解为一个围棋的棋盘。
- 收集器里面讲整个的内存区域都混合在一起了,但其本身依然在小范围内要进行年轻代和老年代的区分,保留了新生代和老年代,但它们不再是物理隔离的,而是一部分Region的集合且不需要Region是连续的,也就是说依然会采用不同的GC方式来处理不同的区域。
- 虽然也是分代收集器,但整个内存分区 不存在物理上的 年轻代与老年代的区别,也不需要完全独立的survivor(to space)堆做复制准备。只有逻辑上的分代概念,或者说每个分区都可随着G1的运行在不同代之间前后切换;
8.3 底层原理
(1) Region区域化垃圾收集器:最大好处是化整为零,避免全内存扫描,只需要按照区域来进行扫描即可
区域化内存划片Region,整体编为了一些列不连续的内存区域,避免了全内存区的GC操作。
核心思想是将整个堆内存区域分成大小相同的子区域(Region),在JVM启动时会自动设置这些子区域的大小,在堆使用上,G1并不要求对象的存储一定是物理上连续的只要逻辑上连续即可,每个分区也不会固定地为某个服务,可以按需在年轻代和老年代之间切换。启动时可以通过参数:-XX:G1HeapRegionSize=n可指定分区大小(1MB~32MB,且必须是2的幂),默认将整堆划分为2046个分区。
大小范围在1MB~32MB,最多能设置2048个区域,也即能够支持的最大内存为:32MB*2048=65536MB=64G内存
(2) 回收步骤
针对Eden区进行收集,Eden区耗尽后会被触发,主要是小区域收集+形成连续的内存块,避免内存碎片
- 区的数据移动到Survivor区,假如出现Survivor区空间不够,区数据会部份晋升到Old区
- 区的数据移动到新的区,部分数据晋升到Old区
- Eden区收拾干净了,GC结束,用户的应用程序继续执行。
(3) 4步过程
- GC Roots能直接关联到的对象
- GC Roots Tracing的过程
形如:
8.4 case案例
package com.shuidiit.second.jvm; import java.util.Random; /** * Created by shizan on 2020/3/8. * -Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+UseG1GC */ public class G1Demo { public static void main(String[] args) { String str = "shuidiit"; while(true){ str+=str+new Random().nextInt(777777)+new Random(888888); str.intern(); } } }
8.5 常用配置参数(了解)
- -XX:+UseG1GC
- -XX:G1HeapRegionSize=n : 设置G1区域的大小。值是2的幂,范围是1M到32M。目标是根据最小的Java堆大小划分出约2048个区域
- -XX:MaxGCPauseMillis=n : 最大停顿时间,这是个软目标,JVM将尽可能(但不保证)停顿时间小于这个时间
- -XX:InitiatingHeapOccupancyPercent=n 堆占用了多少的时候就触发GC,默认是45
- -XX:ConcGCThreads=n 并发GC使用的线程数
- -XX:G1ReservePercent=n 设置作为空闲空间的预留内存百分比,以降低目标空间溢出的风险,默认值是10%
开发人员仅仅需要声明以下参数即可:
三步归纳:开始G1+设置最大内存+设置最大停顿时间
-XX:+UseG1GC -Xmx32g -XX:MaxGCPauseMillis=100
-XX:MaxGCPauseMillis=n 最大GC停顿时间单位毫秒,这是个软目标,JVM将尽可能(但不保证)停顿小于这个时间
8.6 和CMS相比的优势
比起cms有两个有数:
1) G1不会产生内存碎片
2)是可以精确控制停顿。该收集器是把整个堆(新生代、老年代)划分成多个固定大小的区域。
每次根据允许停顿的时间去收集垃圾最多的区域
8.7 小总结