GC 详解:
口诀:关于垃圾回收:分代收集算法 ——不同的区域使用不同的算法
Young代:GC频繁区域
Old代:GC次数较少
Perm代:不会产生GC
一个对象的历程!
JVM在进行GC时,并非每次都是对三个区域进行扫描,大部分时候都是指的新生代!
两个类型:
普通GC:只针对新生代 -- GC
全局GC:主要是针对老年代,偶尔伴随新生代 -- Full GC
GC算法
- 复制算法
- 标记清除
- 标记整理
- GC引用计数 (了解,基本不用)
GC引用计数(了解)
闲谈:GC可达性算法
复制算法
年轻代中使用的就是复制算法
优点:没有标记和清除的过程,效率高,没有内存碎片
缺点:需要浪费双倍的内存空间
Eden区,对象存活率极低,据官方统计,99%的对象在使用一次后,引用失效,推荐使用复制算法
标记清除算法
老年代一般使用这个,但是会和标记整理算法一起使用
优点:不需要额外空间
缺点:两次扫描耗时,会产生内存碎片,不连续
标记清除整理算法
减少了标记清楚算法的缺点:没有内存碎片,但耗时较为严重
那什么时候考虑使用这个算法?
在我们这个要使用的算法的空间中,假设这个空间中很少,不经常发生GC,那么可以考虑使用这个算法!
GC算法总结
内存效率:复制算法 > 标记清楚算法 > 标记清楚整理算法 (时间复杂度)
内存整齐度:复制算法 = 标记清除整理算法 > 标记清除算法(内存碎片)
内存利用率:标记清除整理算法 = 标记清除算法 > 复制算法
从效率来说,复制算法最好,但是空间浪费较多!为了兼顾所有指标,标记清除整理会平滑一点,但是效率不尽人意!
难道就没有一种最优的算法吗?
答案:没有!分代收集算法:不同的区域使用不同的算法!没有最好的,只有最适合的。
JVM参数
JVM三种参数类型:
标配参数、X参数(参数)、XX参数;
标配参数:
- -version
- -help
- -showversion
X参数(了解):
- -Xint #解释执行
- -Xcomp #第一次使用就编译成 本地 的代码
- -Xmixed #混合模式(java默认)
(XX参数之布尔型)重点:jinfo -flag
jps -l #查看当前运行的Java线程号
jinfo -flag PrintGCDetails 11111 #查看此线程号的Java程序的某个参数是否开启
- +PrintGCDetails #开启打印GC日志 “ +/- ”加减号分别表示开启关闭
XX参数之 key = value型:
- -XX:MetaspaceSize=128m #元空间大小
- -XX:MaxTenuringThresho=15 #进入老年区的存货年限
------------------------------------------------------
- -XX:PrintFlagInital(了解) #查看Java环境初始默认值,“ = ”表示默认值,“ := ”表示修改过
经典面试题:-Xms、-Xmx、怎么解释呢?考察你到底研究过没有!
1. -Xms 初始堆的大小,等价:-XX:InitialHeapSize
2. -Xmx 最大堆的大小,等价:-XX:MaxHeapSize
你常用的项目,发布后配置过JVM调优参数吗?
- -Xms
- -Xmx
- -Xss
- -Xmn
- -XX:MetaspsaceSize #设置元空间的大小,这个在本地内存中
- -XX:+PrintGCDetails #打印GC
- -XX:SurvivoRatio #设置新生代中s0/s1空间的比例
- uintx SurvivorRatio = 8 Eden : s0 : s1 = 8 : 1 : 1
- -XX:NewRatio #设置年轻代与老年代的占比
- NewRatio = 2 新生代 :老年代 = 1 : 2 默认新生代是整个堆的1/3
- MaxTenyringThreshold = 15 #进入老年区的存活阈值
关于对 OOM 的认识
1 // -Xms8m -Xmx8m -XX:+PrintGCDetails 2 // java.lang.StackOverflowError 堆栈溢出错误 3 public class OOM_Demo1 { 4 public static void main(String[] args) { 5 a(); 6 } 7 public static void a(){ 8 a(); 9 } 10 }
1 // -Xms8m -Xmx8m -XX:+PrintGCDetails 2 // java.lang.OutOfMemoryError: Java heap space java堆空间错误 3 public class OOM_Demo2 { 4 public static void main(String[] args) { 5 String str = "hello"; 6 while (true) { 7 str += str + 8 new Random(999999999) + 9 new Random(999999999); 10 } 11 } 12 }
1 // -Xms10m -Xmx10m -XX:MaxDirectMemorySize=5m -XX:+PrintGCDetails 2 // java.lang.OutOfMemoryError: GC overhead limit exceeded 3 // GC回收时间过长也会导致OOM 4 // CPU占用一直100%,GC但是没有什么效果! 5 public class OOM_Demo3 { 6 public static void main(String[] args) throws Throwable { 7 int i = 0; 8 List<String> list = new ArrayList<String>(); 9 10 try { 11 while (true){ 12 list.add(String.valueOf(++i).intern()); 13 } 14 } catch (Throwable e) { 15 System.out.println("i=>"+i); 16 e.printStackTrace(); 17 throw e; 18 } 19 } 20 }
1 // -Xms10m -Xmx10m -XX:MaxDirectMemorySize=5m -XX:+PrintGCDetails 2 // java.lang.OutOfMemoryError: Direct buffer memory 3 // 基础缓冲区的错误 4 public class OOM_Demo4 { 5 public static void main(String[] args) throws InterruptedException { 6 System.out.println("配置的MaxDirectMemorySize"+ 7 VM.maxDirectMemory()/1024/(double)1024+"MB"); 8 TimeUnit.SECONDS.sleep(2L); 9 10 // 故意破环 11 // ByteBuffer.allocate(); 分配JVM的堆内存,属于GC管辖 12 // ByteBuffer.allocateDirect(); 分配本地OS内存,不属于GC管辖 13 ByteBuffer byteBuffer = ByteBuffer.allocateDirect(6*1024*1024); 14 } 15 }
用虚拟机执行,慎用自己电脑执行——会死机
1 // java.lang.OutOfMemoryError: unable to create native Thread 2 // 高并发 , unable to create native Thread这个错误更多的时候和平台有关! 3 // 1、应用创建的线程太多! 4 // 2、服务器不允许你创建这么多线程! 5 public class TDemo { 6 public static void main(String[] args) { 7 for (int i = 1; ; i++) { 8 System.out.println("i=>"+i); 9 new Thread(()->{ 10 try { 11 Thread.sleep(Integer.MAX_VALUE); 12 } catch (InterruptedException e) { 13 e.printStackTrace(); 14 } 15 },""+i).start(); 16 } 17 } 18 }
1 // -XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m 2 // java.lang.OutOfMemoryError: Metaspace 元空间报错 3 /* 4 java8 之后使用元空间代替永久代;本地内存! 5 1、虚拟机加载类信息 6 2、常量池 7 3、静态变量 8 4、编译后的代码 9 ..... 10 模拟元空间溢出、不断的生成类即可! 11 */ 12 public class OomDemo { 13 14 static class OOMTest{} 15 16 public static void main(String[] args) throws Throwable { 17 18 int i = 0; // 模拟计数器 19 20 try { 21 while (true){ 22 i++; 23 // 不断的加载对象! Spring的 cglib; 24 Enhancer enhancer = new Enhancer(); 25 enhancer.setSuperclass(OOMTest.class); 26 enhancer.setUseCache(false); 27 enhancer.setCallback(new MethodInterceptor() { 28 @Override 29 public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { 30 return method.invoke(o,args); 31 } 32 }); 33 enhancer.create(); 34 } 35 } catch (Throwable e) { 36 System.out.println("i=>"+i); 37 e.printStackTrace(); 38 } 39 40 } 41 }
垃圾收集器(难点!了解即可)
4种垃圾回收器:
- 串行(STW:Stop the World)单线程
- STW:意思是执行GC的时候所有线程会停止运行,等待GC完成
- 并行(多线程工作,也会导致STW)
- 并发(在回收的同时,可以正常执行线程,并行处理,但是如果是单核CPU,只能交替执行!)
- G1(将堆内存分割成不同的区域,然后并发的对其进行垃圾回收)
java -XX:+PrintCommandLineFlags -version #查
看默认的垃圾回收器
Java主要的GC回收器有哪些?(曾经7种,现在6种)
- DefNew:默认的新一代(Serial串行)
- Tenured:老年代(Serial Old)
- ParNew:并行新一代(并行ParNew)
- PSYoungGen:并行清除年轻代(Parallel Scavcegn)
- ParOldGen:并行老年区
不同的GC回收器的机制不同,使用标准,如何选择:
- -XX:UseSerialGC #单CPU,单机程序,内存小
- -XX:+UseParallelGC #多CPU,大吞吐量,后台计算
- -XX:+UseParNewGC #多CPU,但是不希望有时间停顿,快速响应
垃圾回收器设计原则:尽量少而快的执行GC!
G1回收器
-XX:+UseG1GC #使用G1GC
-XX:MaxGCPauseMillis=100 #最大的GC停顿时间单位:毫秒,JVM尽可能的保证停顿小于这个时间
优点:
- 没有内存碎片!
- 可以精准控制垃圾回收时间