垃圾回收:
1.如何判断对象可以回收?
- 1.引用计数法。
弊端:无法解决循环引用的问题。比如两个对象相互引用。这个算法就无法解决 - 2.可达性分析算法:
根对象(GC Root):肯定不能被进行垃圾回收的,扫描,是否被根对象直接或间接引用,如果是,不能被回收;否则,将来解可以被当做垃圾。
MAT工具看看:
System Class:
Native Stack:
Thread:
Busy Monitor:正在加锁的对象
五种引用:(面试题)
- 强:实线(沿着根对象能找到他,平时一般都是强引用)都不要了,才回收
- 软:内存不足时,又没有强引用不要了,进入引用队列,里面有方法
- 弱:发生垃圾回收,就回收了,去队列
- 虚(必须配合引用队列使用):
- 终结器(必须):finalize():是个优先级很低的线程
为什么不用finalize释放资源???
工作效率低,第一次得先入队,进去后,线程优先级很低,很难被执行到,很容易造成它调用的对象所占用的内存迟迟得不到释放。
软引用应用:
SoftReference
-Xmx20m -XX:+PrintGCDetails -verbose:gc
public static void soft() {
// list --> SoftReference --> byte[]
List<SoftReference<byte[]>> list = new ArrayList<>();
for (int i = 0; i < 5; i++) {
SoftReference<byte[]> ref = new SoftReference<>(new byte[_4MB]);
System.out.println(ref.get());
list.add(ref);
System.out.println(list.size());
}
System.out.println("循环结束:" + list.size());
for (SoftReference<byte[]> ref : list) {
System.out.println(ref.get());
}
上面的执行结果,有四个null,那么如何清理无用的软引用???
软引用关联引用队列。当软引用关联的byte[]数组回收时,软引用自己会加入到queue中去。
public static void main(String[] args) {
List<SoftReference<byte[]>> list = new ArrayList<>();
// 引用队列
ReferenceQueue<byte[]> queue = new ReferenceQueue<>();
for (int i = 0; i < 5; i++) {
// 关联了引用队列, 当软引用所关联的 byte[]被回收时,软引用自己会加入到 queue 中去
SoftReference<byte[]> ref = new SoftReference<>(new byte[_4MB], queue);
System.out.println(ref.get());
list.add(ref);
System.out.println(list.size());
}
// 从队列中获取无用的 软引用对象,并移除
Reference<? extends byte[]> poll = queue.poll();
while( poll != null) {
list.remove(poll);
poll = queue.poll();
}
System.out.println("===========================");
for (SoftReference<byte[]> reference : list) {
System.out.println(reference.get());
}
2.垃圾回收算法:
- 标记清除:
沿着GCRoot的引用链,如果没有GCRoot的直接间接引用,将这些标记;然后清除,释放资源(释放是指:把这些对象占用的内存地址,放在一个空闲地址列表里,并不是作清零处理)
优点:速度快(只需要把垃圾内存起始结束地址清除)
缺点:容易产生内存碎片(如果是一个大的对象,空间不连续,依然放不下,召唤出呢个内存溢出)
-
标记整理:
在清除过程中,把可用的对象向前移动,是内存更为紧凑。
优点:解决了内存碎片
缺点:效率低了,因为牵扯到了对象的移动,它的地址牵扯,慢。 -
复制:
把内存划分为两块 FROM TO
先标记 把FROM中存活的过程中移到TO(过程中会解除内存碎片)
然后交换FROM TO
TO总是空闲的一片区域
缺点;会占用双倍的内存空间
优点:没有内存碎片
3.分代回收
新生代(伊甸园+幸存区(FROM)+幸存区(TO))
老年代
针对生命周期的不同特点,进行垃圾回收。老年代处理长时间存活的对象。新生代回收更为频繁。
Minor GC:(新生代)
伊甸园放不下了,触发新生代复制回收。
如果长期在from区存活,就把他放到老年代去。
但是老年代晋升的对象多了。新生代满了,老年代也满了,触发老年代的回收机制(Full GC)
为什么要停止别的 因为回收时,会牵扯到对象的复制,同时多个,就乱了。暂停时间比较短,因为大部分被清除了,复制的很少。(STW暂停时间)更长,因为存活时间更多,标记整理较慢。
4.GC相关
1.参数:
2.GC代码过程分析
10=8+1+1
内存溢出,线程没结束?
一个进程,对整个进程来说,还可以顶。
5.垃圾回收器
1.串行:
-XX: +UserSerialGC=Serial+SerialOld(复制(新生代)+标记整理(老年代))
单线程,堆内存小
2.吞吐量优先:单位时间内时间变短
3.响应时间优先:尽可能让SWT时间变短
2.3:多线程,内存大(适合多核cpu,适合服务器)
并行:
-XX: +UserParallelGC -XXl+UserParallel0ldGC
-XX:GCTimeRatio=ratio
-XX:MaxGCPauseMiilis=ms
-XX:ParallelGCThreads=n
并发:
concurrent
G1
Garbage First(Jdk9):同时注重吞吐量和低延迟;适合大内存(划分为多个等region);整体是标记+整理算法,两个区域间是复制
j8还不是默认的,需要个开关。
9就默认了。
优先收集垃圾最多的区。
老年代:卡表技术
如果引用了老年代,标记为脏卡。
写屏障(配合队列):重标记
字符串去重:
关注的是字符串数组:
之前那个是字符串对象:
String s1=new String("hello");
String s2=new String("hello");
发现开启了去重之后,还是false!!!
System.out.println(s1==s2); //false
System.out.println(s1.equals(s2)); //true
地址不同!!!!!
equals同
GC调优:
1.确定目标:
2.新生代调优:
如何调优?
新生代是不是越大越好??
新生代太大 老年代就少了
full gc时间更长
建议:
新生代:25-50%
主要耗费时间在复制上。新生代最后需要复制的很少。
合理的晋升阈值:
总之协调好比例!!!!