尚硅谷大厂面试题第二季(下)
11、 JVM
11.1 GC Roots 理解
- 什么是垃圾:内存中已经不再被使用到的空间就是垃圾,要进行垃圾回收,首先需要判断一个对象是否可以被回收
- 判断一个对象是否可以被回收
- 引用计数法
- 枚举根节点做可达性分析:以GC Roots集合中的对象为根节点向下进行搜索,如果对象到GC Roots没有任何引用链相连接,说明该对象不可达
- 哪些对象可以作为GC Roots的对象:
- 虚拟机栈(栈帧中的局部变量区,也叫局部变量表)中引用的对象
- 方法区中的类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈JNI(Native)方法引用的对象
11.2 JVM参数配置
- JVM参数类型
- 标配参数:-version,-help, java -showversion,标配参数从Java 1.0开始就有
- X参数:java -Xint|-Xcomp|-Xmixed -version
- -Xint:解释执行,,Java HotSpot™ 64-Bit Server VM (build 25.211-b12, interpreted mode)
- -Xcomp:第一次使用就编译成本地代码,Java HotSpot™ 64-Bit Server VM (build 25.211-b12, compiled mode)
- -Xmixed:混合模式,Java HotSpot™ 64-Bit Server VM (build 25.211-b12, mixed mode)
- XX参数:
- Boolean类型L-XX:+|-属性值
// 开启/关闭打印GC详细信息属性
-XX:+PrintGCDetails
-XX:-PrintGCDetails
// 开启/关闭串行垃圾回收器
-XX:+UseSerialGC
-XX:-UseSerialGC
// 查看某个正在运行的Java程序是否开启了某个JVM参数
jps -l
jinfo -flag [对应的JVM参数:PrintGCDetails] [pid: 17693]
// jinfo -flag [pid:17693] 查看所有默认的JVM参数
// 上面这一步可能在Ubuntu中报错:
修改/etc/sysctl.d/10-ptrace.conf:
kernel.yama.ptrace_scope = 0
examples:
sherman@dell:~/workspace/idea_home$ jps -l
2260 com.intellij.idea.Main
17691 org.jetbrains.jps.cmdline.Launcher
17981 sun.tools.jps.Jps
17693 nwpu.sherman.atsilicon.session2.JVMParametersDemo
3263 org.jetbrains.kotlin.daemon.KotlinCompileDaemon
sherman@dell:~/workspace/idea_home$ jinfo -flag PrintGCDetails 17693
-XX:+PrintGCDetails
sherman@dell:~/workspace/idea_home$ jinfo -flags 2672
Attaching to process ID 2672, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.211-b12
Non-default VM flags: -XX:CICompilerCount=3 -XX:InitialHeapSize=197132288 -XX:MaxHeapSize=3126853632 -XX:MaxNewSize=1042284544 -XX:MinHeapDeltaBytes=524288 -XX:NewSize=65536000 -XX:OldSize=131596288 -XX:+PrintGCDetails -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:+UseParallelGC
Command line: -XX:+PrintGCDetails -javaagent:/home/sherman/dev-tools/ideaIU-2019.1.3/lib/idea_rt.jar=44615:/home/sherman/dev-tools/ideaIU-2019.1.3/bin -Dfile.encoding=UTF-8
- KV 键值对类型:-XX:属性key=属性值value:
// 设置JVM元空间大小
-XX:MetaspaceSize=128m // 元空间默认大小21m左右
// 设置从Young升到老年代区的年龄
-XX:MaxTenuringThreshold=10 // 默认年龄15
- 注意:-Xms和-Xms属于KV键值对类型的缩写形式:他们分别等价于-XX:InitialHeapSize和-XX:MapHeapSize
11.3 查看JVM默认参数
- -XX:+PrintFlagsInitial:查看JVM默认参数,能够在Java程序未运行时查看
java -XX:+PrintFlagsInitial [version] [| grep MetaspaceSize]
- -XX:+PrintFlagsFinal:修改更新时的参数:
java -XX:+PrintFlagsFinal [version] [| grep MetaspaceSize]
sherman@dell:~$ java -XX:+PrintFlagsFinal -version | grep InitialHeapSize
uintx InitialHeapSize := 197132288
// 注意::=代表被JVM修改过或者认为修改过后的值
11.4 常见参数
- -Xms:初始大小内存,默认为物理内存1/64,等价于:-XX:InitialHeapSize
- -Xmx:最大分配内存,默认为物理内存1/4,等价于:-XX:MaxHeapSize
- -Xss:设置单个线程栈的大小,一般默认为512K-1024k(依赖平台),但是JVM该参数默认为0,代表的是使用默认值而不是说单个线程的大小为0,等价于:-XX:ThreadStackSize
- -Xmn:设置年轻代大小,一般不调节该参数
- -XX:MetaspaceSize:设置元空间大小,元空间并不在虚拟机中,而是使用本地内存,元空间的大小仅受本地内存限制
- -XX:SurvivorRatio:设置新生代和老年代在堆结构中的占比,默认是-XX:NewRatio=2,即新生代占1,老年代占2,-XX:NewRatio=4->新生代占1,老年代占4,NewRatio所设置的数值为老年代占的比例,新生代始终为1
- -XX:MaxTenuringThreahold:设置垃圾的最大年龄,默认值为15
// 典型配置
-Xms128m -Xmx4096m -Xss1024k -XX:MetaspaceSize=512m -XX:+PrintCommandLineFlags
-XX:+PrintGCDetails -XX:+UseSerialGC
11.5 GC日志阅读
上面图是在JDK1.8之前的,没有元空间的GC日志信息,在JDK1.8及之后,GC日志一共分为四部分:
- 新生代
- 老年代
- 元空间
- GC时间
新生代、老年代、元空间和GC日志格式固定:**名称:->[GC前该区域内存大小]->GC后该区域内存大小(该区域内存总大小)例如:Full GC (System.gc()) [PSYoungGen: 2496K->0K(56320K)] [ParOldGen: 0K->2408K(128512K)] 2496K->2408K(184832K), [Metaspace: 3006K->3006K(1056768K)], 0.0059206 secs
12、 四大引用
12.1 四大引用关系图
Java提供四种引用:强引用、软引用、弱引用、虚引用,他们的关系图为:
- 强引用:当JVM进行GC时,对于强引用对象,就算出现了OOM也不会对该对象进行回收。强引用是造成Java内存泄露的主要原因之一:
/**
* 演示强引用
*/
public class StrongReference {
public static void main(String[] args) {
Object obj1 = new Object();
Object obj2 = obj1;
obj1 = null;
System.gc();
/**
* obj2属于强引用,不会回收
*/
System.out.println(obj2);
}
}
-
软引用:对于软引用关联的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之内。如果这次回收后还没有足够的内存,才会抛出异常。即:
- 当系统内存充足时,软引用不会回收;
- 当系统内存不足时,软引用会被回收,回收后内存仍然不足,就抛出异常;
软引用通常用在内存敏感的程序中,例如高速缓存中就用到软引用。软引用在Java中用java.lang.ref.SoftReference来表示:
/** * 演示软引用 * VM: -Xms5m -Xmx5m -XX:+PrintGCDetails */ public class SoftReferenceDemo { public static void main(String[] args) { Object obj1 = new Object(); SoftReference<Object> softReference = new SoftReference(obj1); System.out.println(obj1); System.out.println(softReference.get()); obj1 = null; try { byte[] bytes = new byte[20 * 1024 * 1024]; } catch (Exception e) { e.printStackTrace(); } finally { System.out.println(obj1); System.out.println(softReference.get()); } } }
-
弱引用:弱引用通过java.lang.ref.WeakReference来完成,当JVM进行GC时,无论内存是否充足,被弱引用关联的对象都会被回收,即弱引用关联的对象活不到下次GC时刻:
/**
* 演示弱引用
*/
public class WeakReferenceDemo {
public static void main(String[] args) {
Object obj1 = new Object();
WeakReference<Object> wr = new WeakReference<>(obj1);
System.out.println(obj1);
System.out.println(wr.get());
obj1 = null;
/**
* 弱引用活不到下一次gc,因此即使内存充足,弱引用也会被回收
*/
System.gc();
System.out.println(obj1);
System.out.println(wr.get());
}
}
// output:
// java.lang.Object@1540e19d
// java.lang.Object@1540e19d
// null
// null
- 虚引用:虚引用需要java.lang.ref.PhantomReference类来实现,如果一个对象仅持有虚引用,南无他和任何引用一样,调用get()方法总返回null,在任何时候都可能被垃圾回收器回收,虚引用必须和引用队列ReferenceQueue联合使用;
- 作用:虚引用的唯一目的就是:当该对象被垃圾收集器回收的时候收到一个系统通知或者后续添加进一步处理,即当一个对象进入finalization阶段,可以被gc回收,用来实现比finalization更加灵活的机制;
- 具体流程:创建引用时候可以指定关联的队列,当GC释放对象的时候会将引用的对象添加到引用队列中,如果程序发现某个虚引用对象已经被加入到引用队列中,那么就可以在引用对象的内存回收之前采取必要的措施,这就相当于一种通知机制‘
/** * 演示PhantomReference */public class PhantomReferenceDemo { public static void main(String[] args) throws InterruptedException { Object obj = new Object(); ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>(); PhantomReference<Object> phantomReference = new PhantomReference<>(obj, referenceQueue); System.out.println(obj); /** * PhantomReference任何时候get都是null */ System.out.println(phantomReference.get()); System.out.println(referenceQueue.poll()); System.out.println("==============="); /** * obj=null,gc之后,引用的对象会被添加到引用队列中, * 因此最后的poll方法能够获取到值 */ obj = null; System.gc(); Thread.sleep(100); System.out.println(obj); System.out.println(phantomReference.get()); System.out.println(referenceQueue.poll()); }}
12.2 软引用和弱引用的使用场景
- 场景:有一个应用需要读取大量的本地图片:如果每次读取图片都从硬盘读取则会严重影响性能;如果一次都加载到内存中有可能造成内存溢出,此时可以通过软引用或弱引用来解决该问题。
- 设计思路:用一个HashMap来保存图片路径和相应图片对象关联的软引用之间的映射关系,当系统内存不足时,JVM会自动回收这些缓存图片对象所占用的空间,从而有效的避免OOM:
Map<String,SoftReference> imgCache = new HashMap<String,SoftReference>();
12.3 WeakHashMap
WeakHashMap和HashMap一样也是一个散列表,但是它的键是“弱键”,其类型是WeakReference,对于“弱键”,其对应的映射的存在并不会阻止垃圾回收器对该键的丢弃,也就是说,该弱键是可被终止的。当某个键被终止时,其对应的键值对映射就会从散列表中移除:
public class WeakHashMapDemo { public static void main(String[] args) { Map<Integer, Object> weakHashMap = new WeakHashMap<>(); Map<Integer, Object> hashMap = new HashMap<>(); /** * 注意这里两个map不能共用一对key-value,会相互影响 */ Integer key1 = new Integer(250); String value1 = "value1"; Integer key2 = new Integer(250); String value2 = "value2"; weakHashMap.put(key1, value1); hashMap.put(key2, value2); System.out.println("weakHashMap: " + weakHashMap); System.out.println("hashMap: " + hashMap); key1 = null; key2 = null; System.gc(); System.out.println("=========================="); System.out.println("weakHashMap: " + weakHashMap); System.out.println("hashMap: " + hashMap); }}// 从输出结果理解:【对应的映射的存在并不会阻止垃圾回收器对该键的丢弃】的含义// output:// weakHashMap: {250=value1}// hashMap: {250=value2}// ==========================// weakHashMap: {}// hashMap: {250=value2}
13、 OOM
13.1 Java异常和错误的继承体系
注意:虽然StackOverFlowError和OutOfMemoryError通常口语上都称为“异常”,但是实际上他们都是Error的子类,属于错误。
13.2 常见的异常和错误
-
StackOverflowError:栈溢出异常,通常发生在递归调用且未添加终止条件时。使用-Xss参数可以更改栈的大小。
-
OOM:java heap space
当new大对象或者不断new新对象,导致new出来的内存超过了heap的大小,会导致OOM:java heapspace异常:
/** * 演示OOM:java heap size * VM -Xms5m -Xmx5m -XX:+PrintGCDetails */public class JavaHeapSpace { public static void main(String[] args) { // Exception in thread "main" java.lang.OutOfMemoryError: Java heap space byte[] bytes = new byte[80 * 1024 * 1024]; }}
- OOM:GC overhead limit exceeded:
GC回收时间过长,过长的定义:**超过98%的时间用来做GC并且回收了不到2%的堆内存,连续多次GC仍然出现这种情况时,才会抛出该异常。**如果多次出现GC回收时间过长时间,但是并不抛出异常则会出现:GC清理的内存很快又会被再次填满,破事再次GC,形成恶性循环,CPU使用率一直在100%,而GC却没有任何效果。
/** * 演示GC overhead limit exceeded异常 * -Xms12m -Xmx12m -XX:+PrintGCDetails * 注意上面Xms和Xmx值不能太小,否则还没到达GC limit的限制就直接移除了,抛出java heap space异常 */public class GCOverLimit { public static void main(String[] args) { int i = 0; List<String> lists = new ArrayList<>(); try { while (true) { // Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded lists.add(String.valueOf(i++).intern()); } } catch (Exception e) { e.printStackTrace(); } }}
- OOM :direct buffer memory
在NIO程序中,经常需要使用ByteBuffer来读取或者写入数据,这是一种基于通道(channel)和缓冲区(Buffer)的IO方式。他可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆里面的DirectByteBuffer对象作为这块内存引用进行操作。这样能够在Java堆和Native堆中来回复制数据:
- ByteBuffer.allocate(capacity):第一种方式是分配JVM堆内存,属于GC管辖范围,由于需要拷贝,多以速度较慢;
- ByteBuffer.allocateDirect(capacity):这种方式是分配OS本地内存,不属于GC管辖范围,由于不需要内存拷贝所以速度相对较快;
但是如果不断分配本地内存,堆内存很少使用,南无JVM就不需要执行GC,DirectBuffer对象们就不会被收,这时候堆内存充足,但是本地可能已经使用完毕,再次尝试分配本地内存就会出现OOM,程序直接崩溃:
/** * 演示Direct Buffer Memory错误 * -Xms5m -Xmx5m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m */public class DirectBufferMemory { public static void main(String[] args) { // 默认应该是1/4系统内存 System.out.println("配置的堆外内存为:" + (sun.misc.VM.maxDirectMemory()) / (double) 1024 / 1024 + "MB"); // Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory ByteBuffer.allocateDirect(6 * 1024 * 1024); }}
- OOM:unable to create new native thread:
高并发请求服务器,经常出现如下异常:java.lang.OutOfMemoryErroe:unable to create new native thread,准确的讲该native thread异常与对应的平台有关。
/** * 演示OOM:unable to create new native thread * 注意:在Windows上运行这个程序容易出现假死现象!!! */public class UnableCreateNewNativeThread { public static void main(String[] args) { for (int i = 0; ; ++i) { System.out.println("+++++++" + i + "+++++++"); // Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread new Thread(() -> { try { Thread.sleep(Integer.MAX_VALUE); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); } }}
- 导致原因:
- 应用创建了太多的线程,超过了系统承载极限;
- 对应的服务器不允许你的线程创建过多的线程,linux默认允许单个线程可以创建1024个线程
- 解决方案:
- 想办法降低你的进程创建线程的数量,分析程序是否真的需要创建那么多的线程;
- 如果应用真的需要创建很多线程,需要修改linux默认配置,扩大Linux默认限制;
- 修改native thread的上限:
# vim /etc/security/limits.d/90-nproc.conf:* soft nproc 1024root soft nproc unlimited
- OOM :metaspace:
Metaspace是Java8及其以后版本中使用的,用以代替永久代。Metaspace是方法区在HotSpot中的实现,它与永久带最大的区别是:Metaspace并不在JVM内存中而是直接使用本地内存。也即是说在Java8中,class metadata(the virtual machines internal presentation of Java class),被存储在焦作Meatspace的Native Memory中。
- 永久代(Metaspace)存储的信息:
- JVM加载的类信息
- 常量池
- 静态变量
- 即时编译后的代码
- 查看默认MetaspaceSize:默认应该是物理内存的1/4:
# windows:java -XX:+PrintFlagsInitial | findstr -i metaspacesize# linux: java -XX:+PrintFlagsInitial | grep -i metaspacesize
因为Metaspace中存储了类信息,所以不断产生新的类,就会不断向Metaspace中写入数据,就有可能导致Metaspace区域溢出。
14、 垃圾回收器
GC算法(引用计数、复制、标记清楚、标记整理)是内存回收的方法论,垃圾回收器是方法论的落地实现。目前没有完美的垃圾回收器,也没有万能的垃圾回收器,只能根据具体的应用选择合适的垃圾回收器。
14.1 四大垃圾回收器
- 串行垃圾回收器(Serial):为单线程环境设置并且只使用一个线程进行垃圾回收,会暂停所有的而用户线程,不适合服务器环境;
- 并行垃圾回收器(Parallel):Serial垃圾回收器的多线程版本,会开启多个线程进行垃圾回收,仍然会暂停所有的用户线程,速度较快,适用于科学计算或者大数据处理;
- 并发垃圾回收器(CMS):用户线程和垃圾会输线程同时执行(不一定是并行,可能是交替执行),不需要暂停用户线程,适用于对响应时间要求严格的应用;
- G1垃圾回收器⭐️:将堆内存分割成不同的区域(Region)然后进行垃圾回收,并不存在明显的新生代和老年代;
Serial和Parallel垃圾回收器都会产生STW(Stop The World)问题,CMS并不会产生该问题,但是CMS的GC过程可能更加复杂,导致抢占应用程序的CPU资源。
14.2 默认垃圾回收器、配置
- 查看默认的垃圾回收器:java -XX:+PrintCOmmandLineFlags -version,Java 8默认使用ParallelGC
- JVM默认的垃圾回收器(7种,有一种已经废弃):
- UseSerial <-------->
UseSerialOldGC - UseParallelGC <--------> UseParallelOldGC
- UseParNewGC <--------> UseConcMarkSweepGC
- UseG1GC
- UseSerial <-------->
对于一个正在运行的Java程序,可以通过jps找到Java的pid,然后jinfo flag UseXxxGC pid来查看当前运行的程序是否开启了指定的垃圾回收器。
- JVM垃圾回收器日志中参数说明:
- DefNew:Default New Generation
- Tenured:老年代
- ParNew:Parallel New Generation
- PsYoungGen:Parallel Scavenge
- ParOldGen:Parallel Old Generation
- JVM中CS模式:
- 32位Windows操作系统,无论硬件如何默认使用Client的JVM模式;
- 32位其他操作系统,2G内存同时拥有两个CPU及以上的硬件资源使用的是Server模式,否则Client模式;
- 64位操作系统只有Server模式
14.3 垃圾回收器详细说明
串行垃圾回收器(Serial/Serial Copying)
一个单线程的垃圾回收器并且只使用单个线程进行垃圾回收在进行垃圾回收时,必须暂停其他所有的工作线程(STW)直到它的垃圾回收结束。串行垃圾回收最稳定高效,对于限定单个CPU的环境来说,没有线程交互的开销可以获得最高的单线程垃圾回收效率。因此,Serial垃圾收集器依然是JVM运行在Client模式下默认的新生代垃圾收集器。
- 对应JVM参数: -XX:+USeSerialGC
- 说明:开启SerialGC后,默认使用的配套GC是:Serial(Young区使用)+Serial Old(Old区使用)的收集器组合表示新生代和老年代都会使用串行垃圾回收器,新生代使用复制算法,老年代使用标记-整理算法。
- 配置运行说明:DefNew&Tenured
并行垃圾回收器(ParNew)
使用多个线程进行垃圾回收,是Serial收集器新生代的并行多线程版本,在进行垃圾回收时仍然会出现STW问题直至它的回收工作结束,他是很多JVM运行在Server模式下的新生代默认垃圾回收器。
- 对应JVM参数:-XX:+UseParNewGC
- 说明:开启PartNewGC后,默认使用的配套GC是:ParNew(Young区使用)+Serial Old(Old区使用)的垃圾收集器组合,相对于Serial来说只会影响新生代,不影响老年代。新生代使用复制算法,老年代使用标记-整理办法。但是,ParNew&Serial Old在Java 8已结不被推荐使用:Using the ParNewGc young collector with Serial old collectoe is deprecated and will likely be removerd in future release.更加推荐的组合是:PartNew&CMS
- 备注:-XX:ParallelGCThread参数可以限定回收线程的数量,默认开启和CPU数目相同的线程数
- 配置运行说明:ParNew&Tenured
并行收集器(Parallel Scavenge)
Parallel Scavenge 收集器类似于ParNew,也是一个新生代垃圾并行多线程收集器,使用复制算法,俗称吞吐量(CPU运行用户代码时间/(运行用户代码时间 + 垃圾收集时间))优先收集器,总的来书,相对于ParNew收集器而言:Parallel Scavenge是在新生代和老年代都是并行多线程的垃圾回收器,他是Java8默认的垃圾回收器。
高吞吐量意味着高效利用CPU的时间,它多用于在后台运算而不需要太多的交互任务。除此之外,相对于ParNew收集器而言,PS收集器存在一个自适应调节策略:JVM会根据当前系统的运行情况收集性能监控信息,动态调节这些参数以提供最合适的停顿时间(-XX:maxPauseGCMillis)或最大吞吐量。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uyrjvVqJ-1627354061726)(D:\360downloads\nut\我的坚果云\20191218120423570.png)]
- 对应JVM参数:-XX:+UseParallelGC或-XX:+UseParallelOldGC可以相互激活;
- 说明:开启该参数后,新生代使用复制算法,老年代使用标记0整理算法;
- 备注:-XX:ParallelGCThreads=N表示开启多少个GC线程,如果CPU核数>8,N=5/8,否则N=CPU核数;
- 配置运行说明:PSYoungGen&ParOldGen
Parallel Old收集器:
Parallel Old收集器是PS的老年代版本,使用多线程的标记-整理算法,Parallel Old是JDK1.6才开始提供的。在JDK1.6之前,新生代使用PS收集器只能在老年代配合Serial Old收集器,只能保证在新生代的吞吐量有限,无法保证整体的吞吐量。
Parallel Old正是为了在老年代同样提供吞吐量优先的垃圾回收器,如果系统对吞吐量要求较高,在JDK1.8及以后版本都是使用Parallel Scavenge&Parallel Old垃圾收集器组合。
- 对应JVM参数:—XX:+UseParallelOldGC,和-XX:+UseParallelGC相互激活;
并发标记清除收集器(CMS):
CMS收集器是标记-清楚GC算法的落地实现,是一种获得最短停顿时间为目标的收集器,适应于互联网站或B/S系统的服务器上,这类应用尤其重视服务器的响应速度,希望系统停顿时间最短CMS非常适合堆内存大,CPU核数多的服务器应用,也是G1收集器出现之前大型应用的首选收集器。
- 对应JVM参数:-XX:+UseConcMarkSweepGC,同时会自动生成开启-XX:+UseParallelGC,开启该参数后,Serial Old将作为CMS出错后的备用收集器;
- 收集过程:
- 初始标记(CMS concurrent mark):进行GC Roots跟踪过程,和用户线程一起工作,没有STW过程,主要是标记过程,会标记全部对象;
- 并发标记(CMS concurrent mark):进行GC Roots跟踪过程,和用户线程一起工作,没有STW过程,主要是标记过程,会标记全部对象;
- 重新标记(CMS remark):为了修正在并发标记期间,因为用户程序继续运行而导致标记产生变多的那一部分对象的标记记录,仍然存在STW过程。因为并发标记时,用户线程依然可以运行,因此在正式清理之前,再一次修正工作。
- 优缺点:
- 并发收集停顿低,它本身就是以最短停顿时间为目标的垃圾回收器;
- 并发执行对CPU资源压力较大:由于并发执行,CMS在垃圾回收时会与应用线程同时增加对堆内存的占用,也就是说,CMS必须要在老年代堆内存用尽之前完成垃圾会输,否则CMS回收失败,将会触发担保机制。此时Serial Old收集器会作为后备收集器,以STW的方式进行一次GC,造成较大的停顿时间;
- CMS收集器是标记-清除算法的落地实现,就标记-清除算法而言本身存在内存碎片的问题,老年代的空间会随着应用时长被准备用尽,最后不得不通过担保机制对堆内存进行一次压缩,CMS提供了参数:-XX:CMSFullGCsBeforeCompaction默认0,表示每次都进行内存整理来指定多少次CMS收集之后,再进行一次压缩的FUll GC;
- 配置运行说明:ParNew&CMS&Serial Old
Serial Old收集器:
Serial Old收集器是Serial收集器的老年代版本,同样是单线程收集器,使用标记-整理算法,该收集器主要在Client模式下老年代使用。在Server模式下,Serial Old收集器的主要作用有:
- 在JDK1.6之前,在老年代和新生代Parallel Scavenge配合使用;
- 作为ParNew&CMS的后备收集器;
注意:在JAVA8中不能使用JVM参数-XX:+UseSerialOldGC来开启Serial Old收集器。
G1收集器:
G1收集器是一种面向服务器端的垃圾回收器,应用在多处理器和大容量内存环境中,在实现高吞吐量的同时,尽可能满足垃圾收集暂停时间的要求,用于取代CMS垃圾收集器,在JDK9中,G1是默认的垃圾回收器。
在G1收集器中,Region之间的对象引用及其他收集器中的新生代和老年代之间的对象引用,JVM是通过Remembered Set来避免全堆扫描。G1中每个Region都有一个与之对应的Remembered Set,JVM发现程序对Reference引用的对象进行写操作时,会发生一个写中断(Write Barrier),检查Reference引用的对象是否处于不同的Region之中,(例如检查是否老年代中的对象引用了新生代中的对象),如果是,便通过CardTable把相关用信息记录到被引用对象所属的Region的Remembered Set中。当进行内存回收时,在GC根节点的枚举范围内加入Remembered Set即可保证不对全堆也不会有遗漏(注:这也说明GC Roots中的对象也可以是属于Remembered Set中的对象)。
- 特点:
- 可以和用户线程并行执行,尽量缩短STW时间;
- 分代收集:虽然G1收集器不需要配合其它收集器都能独立管理整个GC堆,但是它仍然保留新生代和老年代的概念,它将整个Java堆划分为多个大小相等的独立区域(Region),新生代和老年代也不再需要物理隔离,它们都是Region的集合,每个Region大小从1M到32M不等,每个Region可能是Eden有可能是Tenured内存区域;
- 空间整理:G1整体上是使用标记-整理算法,从局部(两个Region之间)看使用的是复制算法,这样的空间整理算法因为这G1在运行期间不会产生内存碎片,内存回收之后可以提供规整的可用内存;
- 可预测的停顿时间:G1除了追求低停顿时间之外,还建立了可预测的停顿时间模型,可以让用户明确指定一个长度为M毫秒的时间片段,消耗在垃圾收集器上的时间不得超过N毫秒。
- 原理:G1收集器将区域化的内存分割成大小不等的Region,范围在1M到32M,JVM在启动时会自动设置这些区域的大小,最多能够设置2048个区域,即最大能够支持的内存为32MB*2048=64G。在堆内存使用中,G1并不要求对象移动存储在物理连续的内存上只要逻辑上连续即可,每个分区也不会固定为某个代服务,可以按需在新生代和老年代之间切换。
在G1中还有一个特殊的区域(Hunongous)区域,如果一个对象占用的内存超过了分区容量的50%,这样的巨型对象会之间分配在老年代,但是它对垃圾回收产生负面影响。为了解决这个问题,G1专门划分了一个Humongous区域,专门存放巨型对象,如果一个H区不能装下巨型对象,南无G1会寻找连续的H分区来存储,有时为了能够找到连续的H区,不得不启动一次Full GC。
-
G1收集的运作步骤(不考虑Remembered Set):
- 初始标记:只对GC Roots能够直接关联到的对象进行标记,存在STW;
- 并发标记:对GC Roots关联的对象进行可达性分析,可以并发执行;
- 最终标记:修正并发标记期间因为应用程序继续执行而导致变化的那一部分,存在STW;
- 筛选回收:根据时间来进行价值最大化的回收;
-
G1常用参数:
- -XX:+UseG1GC
- -XX:G1HeapRegionSize=n:G1 Region的大小,值必须是2的幂,范围在1MB到32MB
- -XX:MaxGCPauseMillie=n:最大GC停顿时间大小
- -XX:ConcGCTHreads=n:并发GC使用的线程数
-
相对CMS优势:
- 不产生内存碎片
- 可以让用户指定期望的GC停顿时间,G1会根据允许的停顿时间区收集价值最高的垃圾
14.4 垃圾回收器选择
- 单CPU或最小内存,单机程序:-XX:+UseSerialGC
- 多CPU,需要最大吞吐量,如后台计算型应用:-XX:+UseParallelGC或者-XX:+UseParallelOldGC
- 多CPU,追求低停顿时间,快速响应如互联网应用:-XX:+UseConcMarkSweepGC或者-XX:+UseParNewGC
15、 生产调优
15.1 SpringBoot调优步骤
SpringBoot微服务的生产部署和调参优化步骤
- 1、IDEA开发微服务工程;
- 2、maven进行clean + package
- 3、要求微服务启动的时候,同时配置我我们的JVM/GC的调优参数
- 4、公式:java -server jvm的各种参数 -jar jar/war
java -server -Xms1024m -Xmx1024m -XX+UseG1GC -jar springboot2019-1.0-SNAPSHOT.war
15.2 调优思路和性能评估
15.2.1 整机
整机:top(查看CPU、内存使用率等,也可使用精简版系统性能命令:uptime)
- 主要查看CPU、MEM、load average(按1可详细查看CPU的每个核心状态)
15.2.2 CPU
CPU:vmstat(查看CPU使用情况)
vmstat -n 2 3
-
一般vmstat工具的使用是通过两个数字参数来完成的,第一个参数是采样的时间间隔数单位是秒,第二个参数是采样的次数;
参数含义:
-
-procs
- 1.r:运行和等待CPU时间片的进程数,原则上1核的CPU的运行队列不要超过2,整个系统的运行队列不能超过总核数的2倍,否则代表系统压力过大
- 2.b:等待资源的进程数,比如正在等待磁盘I/O、网络I/O等;
-
-cpu
-
1.us:用户进程消耗CPU时间百分比,us值高,用户进程消耗CPU时间多,如果长期大于50%,需要优化程序
-
2.sy:内核进程消耗的CPU时间百分比
-
3.说明:us+sy参考值为80%,如果us+sy大于80%,说明可能存在CPU不足
-
4:id:处于空闲的CPU百分比
-
5.wa:系统等待IO的CPU时间百分比
-
6.st:来自于一个虚拟机偷取的CPU时间的百分比
其他的查看指令:
1.查看所有CPU核信息:mpstat -P ALL 2(每两秒采样一次)
2.每个进程使用cpu的用量分解信息:pidstat -u 1-p 进程编号
-
15.2.3 内存
内存:free(查看应用程序可用内存数)
1.free :以字节为单位
2.free -g:以GB为单位
3.free -m:以MB为单位
经验值:
1.应用程序可用内存/系统物理内存>70%内存充足
2.应用程序可用内存/系统物理内存<20%内存不足,需要增加内存
3.20%<应用程序可用内存/系统内存<70%,内存基本够用
pidstat -p 进程号 -r采样间隔秒数,可查看进程占用内存的情况
15.2.4 硬盘
硬盘:df(查看磁盘剩余空间数)
15.2.5 磁盘
磁盘io:iostat(磁盘IO性能评估)
磁盘看设备分部
- rkB/s每秒读取数据量kB
- wkB/s每秒写入数据量kB
- svctm I/O请求的平均等待时间,单位毫秒;值越小,性能越好
- await I/O请求的平均等待时间,单位毫秒;值越小,性能越好
- util:一秒钟有百分之几的时间应用不同会有不同的值,但有规律遵循:长期、超大数据读写,肯定不正常,需要优化程序读写。
- svctm的值与await的值很接近,表示几乎没有I/O等待,磁盘性能越好,如果await的值远高于svctm的值,则表示I/O队列等待太长,需要优化程序或更换更快磁盘。
pidstat -p 进程号 -r 采样间隔秒数,可查看进程磁盘的使用情况
15.2.6 网络
网络IO:ifstat(查看网络占用情况)
默认本地没有,下载ifstat
wget http://gael.roualland.free.fr/ifstat/ifstat-1.1.tar.gztar xzvf ifstat-1.1.tar.gz cd ifstat-1.1./configuremake make install
查看网络I/O情况
15.3 CPU占用率高
假如生产环境出现CPU占用过高,请谈谈你的分析思路和定位。下面开始举例——
先用top命令找出cpu占比最高的,记下PID
-
第一个是控制台输出,不用管它,第二个才是我们要监控的进程(CPU占用率高);
ps -ef或者jps进一步定位,得知是一个怎么样的一个后台程序
定位到具体的线程或代码
ps -mp 进程编号 -o THREAD,tid,time
- -m:显示所有的进程
- -p:pid进程使用cpu的时间
- -o:该参数后是用户自定义的格式
将需要的线程ID转换为16进制格式(英文小写格式)
printf"%x/n":打印输出有问题的线程ID(用计算器也可以)
jspack 进程ID|grep tid(16进制线程ID小写英文 -A60)
-A60表示打印前60行
15.4 JVM分析工具
自带的JVM监控和性能分析工具是什么
- 官方文档:https://docs.oracle.com/javase/8/docs/technotes/tools/
- 自带的JVM监控和性能分析工具怎么用?
- jps:虚拟机进程状况工具
- jinfo:java配置信息工具
- jmap:内存映像工具
- jstat:统计信息监视工具
- jstack:堆栈异常跟踪工具
- jvisualvm
- jconsole
16 、GitHub
- in
XX 关键词 in:name或description或readme
- stars/fork
springboot stars >=5000
springboot fork >= 5000
- awwsome加强搜索
公式:awesome 关键字(awesome系列一般是用来收集学习、工作、书籍相关的项目)
-
高亮显示某一个代码
公式:1行:地址后面紧跟#L数字;多行:地址后面紧跟#L数字-L数字2
-
项目内搜索
英文t;https://help.github.com/en/articles/using-keyboard-shortcuts
- 搜索某个地区的大佬
location:地区
language:语言
例子:北京地区Java方向的用户 location:beijing language:java