一、分代收集理论
1.绝大部分的对象都是朝生夕死 —新生代
2.对象熬过多次垃圾回收,越难回收 —老年代
应该这两类对象分开
cms 才会单独收集老年代
其他很多时候收集老年代是Full GC(新生代、老年代、方法区)
二、复制算法(Copying)
回收新生代
特点:
1.实现简单、运行高效
2.内存复制、没有内存碎片
3.利用率只有一半
Eden区的来源:
1.Appel式回收
2.提高空间利用率和空间分配担保
三、标记-清除算法(Mark-Sweep)
特点:
1.执行效率不稳定
2.内存碎片导致提前GC
内存碎片:堆空间不是连续的
在jvm中对象一定要占用连续空间
90%都是要清理的话 那么每个都要标记和清除就效率低
10%要清楚那么久效率还可以
所以适合老年代
四、标记-整理算法(Mark-Compact)
整理==压缩
清除之后,还要移动
涉及到对象的移动,效率偏低
老年代大部分是存活的 大部分是需要移动的 ,直接引用,还涉及到更新
用户线程还需要暂停
整体效率偏低
特点:
1.对象移动
2.引用更新
3.用户线程暂停
4.没有内存碎片
五、JVM中常见的垃圾收集器
单线程垃圾收集器
多线程并行垃圾收集器
多线程并发垃圾收集器
新生代和老年代回收器是配对的
G1可以跨越代
涉及到单线程多线程
从历史开始讲起:
Jvm 刚诞生的时候就只有两个 (开始就是单线程 ,内存不大的)
内存越来越大,诞生多线程
简单的垃圾回收器工作示意图
单线程收集
多线程收集
并行收集
要暂停所有的用户(业务)线程
不暂停的话,永远收集不完 ,永远收集不干净
在之后—>多线程并发垃圾回收器
部分时间可以并发,但不是全程
出现的第一个并发垃圾收集器是CMS(并发的标记清除垃圾回收器)
并行:就是多线程的垃圾收集器,启动多个线程
并发:垃圾回收器的线程和用户的线程可以同时工作
多线程,还是要暂停,那就不太好 —卡顿
所以推出CMS(只针对老年代)
CMS垃圾回收器工作示意图
Concurrent Mark Sweep
标记清除算法
1.初始标记 --暂停
2.并发标记 --同时进行
3.重新标记 --暂停
4.并发清除 --同时进行
CMS的缺点:
1.CPU敏感
2.浮动垃圾
3.内存碎片
CMS只针对老年代
根可达标记,很快
把标记阶段分成三步:
第一步:初始标记(暂停所有的线程) GcROOTS直接标连的对象 (速度很快)
第二步:并发标记(用户线程继续跑)(耗时长)(主要目的让一个时间长的并发)(根下的一一标记)
第三步:重新标记(暂停所有的线程)(耗时短)
清理的时候也要走并发
最后清理完后重置线程,让用户线程跑
CMS优点事跨时代的:不用完全暂停了
最大响应时间: CMS卡顿的时间比较短
如果cpu核心数小的话,影响比较大
并发清理时会产生浮动垃圾,等下一次清理,这次已经标记完了
可以老年代达到80%就进行回收,剩下20%放浮动垃圾
浮动垃圾过多了咋办?切换成 serial old
CMS弊端:因为浮动垃圾切换成单线程的serial old,回收效率很低
Java后端服务器,美其名为维护,就是重启,重启之后就没有碎片了,比serial old要好得多
主流还是CMS,考虑用户的体验
Android NDK 垃圾回收器花样百多 ,核心思想掌握CMS就可以了
G1
Garbage First
1.追求停顿时间
2.Region区
3.筛选回收
4.可预测停顿
5.算法——复制和标记整理
在G1中新生代和老年代一起分堆,不再是单纯的分代划分
有筛选回收,优先回收有价值的垃圾
humongous 大对象区 ,512kb 就是大对象
Eden、Survivor是复制算法
老年代和大对象采用标记整理
不走全部回收,走筛选回收
JVM 最小暂停时间,超过1000ms 就会骂人。。。
配参数可预测停顿(并不是绝对)
根据很多经验研究
CMS和G1平衡点,6~8个G,堆空间大于8G用G1的效率高一些,堆空间小追求效率用CMS
六、Stop The World现象
什么是STW:业务线程要等GC做完之后才能跑
为什么要STW:不然清理不干净
STW的危害:卡
七、常量池与String
常量池(方法区):
1.静态常量池
2.运行时常量池
字符串常量池(常量池的一部分)
静态常量池:
1.字面量: String i=“puppy”(puppy就是字面量)
2.符号引用:string 这个类 java.lang.string
3.类的、方法的信息
运行时常量池:
类加载-运行时数据区-方法区(逻辑区域)
实体,符号引用—>直接饮用(hash值)
String 的创建分配内存地址
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
......
}
public final class String 不能再被继承
private final char value[]; final 对象的不可变性
String只要创建就不可变
//JVM首先会检查该对象是否在字符串常量池中,如果在,就返回该对象引用,否则新的字符串将在常量池中被创建。
//这种方式可以减少同一个值的字符串对象的重复创建,节约内存。
String str ="abc";
//首先在编译类文件时,"abc"常量字符串将会放入到常量结构中,在类加载时,“abc"将会在常量池中创建;
//其次,在调用 new 时,JVM 命令将会调用 String 的构造函数,同时引用常量池中的"abc” 字符串,
// 在堆内存中创建一个 String 对象;最后,str1 将引用 String 对象。
String str1 =new String("abc");
//这里就跟第一步类似。
Location location = new Location();
location.setCity("深圳");
location.setRegion("南山");
//new Sting() 会在堆内存中创建一个a的String对象,
// “puppy"将会在常量池中创建
// 在调用intern方法之后,会去常量池中查找是否有等于该字符串对象的引用,有就返回引用。
String a =new String("puppy").intern();
//调用 new Sting() 会在堆内存中创建一个b的String 对象,。
//在调用 intern 方法之后,会去常量池中查找是否有等于该字符串对象的引用,有就返回引用。
String b = new String("puppy").intern();
//所以 a 和 b 引用的是同一个对象。
if(a==b) {
System.out.print("a==b");
}else{
System.out.print("a!=b");
}
不加intern则不相等,创建了两个对象
加了intern会去常量池中查找是否有等于该字符串对象的引用,有就返回引用.