文章目录
垃圾回收机制
什么是垃圾回收机制?
关于对象内存释放的这一机制就叫做垃圾回收机制。
GC(garbage collector) 垃圾收集器
GC不定时清理堆内存中不可达对象。
可达与不可达:
-
可达表示对象可以用,不可达表示对象已经没有经常使用了。
-
GC线程发现对象没有在任何地方被引用,就会认为这个对象不可达。
-
不可达对象不一定100%被GC回收掉。
-
只要被引用了,都是可达对象
finalize()和System.gc()
finalize()是Object类中的方法,finalize()作用是gc回收之前会调用一次
当垃圾回收确定不再有对该对象的引用时,垃圾回收器在对象上调用。 子类会重写finalize方法,以操作系统资源或执行其他清理。
System.gc()手动回收垃圾,注:使用System.gc()手动回收垃圾时不一定100%回收不可达对象。
public class GCDemo {
public static void main(String[] args) {
GCDemo t1 = new GCDemo();
t1 = null;
// 手动回收垃圾
System.gc();
}
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("垃圾回收机制...");
}
}
垃圾回收简要过程
不可达对象不会马上被直接回收,至少要经过两次标记过程。
第一次被标记过的对象,会检查改对象是否重写了finalize方法,如果重写了该方法,则将其放入一个F-Query队列,否则,直接将对象加入“即将回收”集合。
在第二次标记之前,队列中所有对象逐个执行finalize方法,但是不保证该队列所有对象的finalized方法都被执行,这时因为JVM创建了一个低优先级的线程去执行词队列中的方法,很可能在没有遍历完之前,就被剥夺了运行权力。
运行finalized方法是避免自己被清理的最后手段,对象在执行finalize中,对象重新与GC Roots引用链相连,则会在第二次标记过程中将此对象从F-Query队列中清除,避免在这次回收中被清除,恢复成一个正常的对象,但这好事显然不能无限发生。对于曾经执行过一次finalize方法对象,之后如果再被标记,则不会再执行finalize方法,只能等待被清除的命运。
内存溢出与内存泄漏
内存溢出和内存泄漏的区别?
-
内存溢出(Out Of Memory,简称OOM)是指应用系统中存在无法回收的内存或使用的内存过多,最终使得程序运行要用到的内存大于能提供的最大内存。
-
内存泄漏(Memory Leak)是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。也就是说对象已经没有应程序使用了,但是垃圾回收机制无法移除他们。
java中常见的内存泄漏成因是定义了过多的常量,堆内存中占据大量资源却无法释放。
垃圾回收机制算法
引用计数算法
什么是引用计数法
堆内存里每个对象有15次计数,当GC回收并对象不可达时,该对象计数减一,直减至0将其清除掉,若GC回收时并对象可达,该计数加一。
引用标记法,每个对象对会有一个标记,默认15次,gc回收时,对象不可达标记-1,对象可达标记+1,当标记为0时,回收该对象。
缺点
无法检测出循环引用,如父对象有一个子对象的引用,子对象反过来引用父对象,这么他们的引用计数始终不可能为0,而且每次加减十分消耗内存。但是这种循环引用场景十分少见。
JVM并没有使用引用计数来管理内存,其中最重要的原因就是它很难解决对象之间的相互引用关系
复制算法
什么是复制算法
复制算法只使用在新生代中(s0、s1大小相等)
新建的对象首先存放在eden区中,如果eden区中对象使用频繁会晋升,从eden区到s0或s1区。
当一个对象在s0区,不在经常使用了,此时s0区还有其他可达的对象,所有可达对象都会被移至s1区,再把整个s0区清除掉。此时如果又有新对象从eden区晋升,它会放到s1区中,保证s0区或者s1区的一个区域始终为空。
如果此时s1区域中又有不可达的对象,那么gc会将所有可达的对象拷贝至s0区,再把整个s1区清除掉。
优缺点
优点:连续型,不会产生碎片化(不会产生残留)
缺点:
浪费了一半的内存。
如果对象的存活率很高,假设是100%存活,那么我们需要将所有对象都复制一遍,并将所有引用地址重置一遍。复制这一工作所花费的时间,在对象存活率达到一定程度时,将会变的不可忽视。
标记清除
什么是标记清除算法
有标记0表示可达,1表示不可达,新建对象后会将对象放至堆内存并标记0表示可达。
当有对象不可达时,将标记改为1,GC启动时将标记为1的对象删除,但是此对象所占的空间保留,不会进行内存空间的整理。是不连续删除。
标记压缩
什么是标记压缩算法
标记清除算法是不连续删除,会导致碎片化空间,于是有人提出了标记压缩算法对其改进。
保留了标记清除的本质功能,有标记0表示可达,1表示不可达
在删除不可达对象时,对堆内存空间进行排序,不可达对象放在前面,可达对象放在后面,再将不可达对象一块删除。
是连续型的删除,不产生碎片。
作用区域
使用在老年代,如果使用在新生代,那么gc可能会使劲地回收不可达的对象,导致对象一不用就被清除掉,回收次数增加,垃圾回收机制频繁执行会降低程序执行效率,而老年代不可达对象概率较小,所以标记清除/压缩一般用于老年代
分代收集
什么是分代收集算法
堆内存中分为新生代和老年代,新生代的对象如果使用频繁会逐渐晋升到s0/s1区最后到老年代。
大体流程如下:
-
新生代采用复制收集算法
-
从新生代到老年代,新生代s区(from/to space)中年龄达到15的对象,移入老年代,同时老年代为s区作担保,s区内存已满而不能存储的对象将存储在老年代中
-
老年代因为对象的存活率高(复制的代价就要高),也没有担保空间,所以采用标记清除/压缩法
为什么垃圾回收机制频繁执行会降低程序执行效率?
当垃圾回收机制进行时,其他所有线程会被停顿。
为什么要将其他所有线程停顿?
停顿的目的是为了停下所有的应用线程,只有这样的线程才不会有新垃圾产生。停顿保证了系统状态在某一个瞬间的一致性,也有利于更好的标记垃圾。
写后边角料
内存结构之后一般就问GC了,GC中常考算法,说说大致的原理,各自作用地方不同
标记清除:老年代
标记压缩:老年代
引用计数:*
复制算法:新生代 s0、s1
分代算法:分代算法中有新生代和老年代算法,常分别采用复制算法和标记压缩算法
说说垃圾回收机制和内存溢出与内存泄漏的区别
垃圾收集器
什么是垃圾收集器
如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。
Java虚拟机规范中对垃圾收集器应该如何实现并没有任何规定,因此不同的厂商、版本的虚拟机所提供的垃圾收集器都可能会有很大差别,并且一般都会提供参数供用户根据自己的应用特点和要求组合出各个年代所使用的收集器。
收集器有cms,G1等等,见下图
上图展示了7种作用于不同分代的收集器,如果两个收集器之间存在连线,就说明它们可以搭配使用。
虚拟机所处的区域,则表示它是属于新生代收集器还是老年代收集器。
串行和并行收集器
并行收集器,使用在多线程,有多条路径并行执行,效率高,服务器一般使用并行收集器,可以通过-XX:+UseParallelGC 命令使用并行收集器。
串行收集器:单线程执行,回收期间暂停所有应用线程执行,client模式下默认的收集,可以通过-XX:UseSerialGC 命令强制指定。
使用并行收集器无非就是调参数。
命令:
收集器设置:
-XX:+UseSerialGC :设置串行收集器
-XX:+UseParallelGC :设置并行收集器
-XX:+UseParalledlOldGC :设置并行年老代收集器
-XX:+UseConcMarkSweepGC :设置并发收集器
垃圾回收统计信息
-XX:+PrintHeapAtGC GC的heap详情
-XX:+PrintGCDetails GC详情
-XX:+PrintGCTimeStamps 打印GC时间信息
-XX:+PrintTenuringDistribution 打印年龄信息等
-XX:+HandlePromotionFailure 老年代分配担保(true or false)
并行收集器设置
-XX:ParallelGCThreads=n :设置并行收集器收集时使用的CPU数。并行收集线程数。
-XX:MaxGCPauseMillis=n :设置并行收集最大暂停时间
-XX:GCTimeRatio=n :设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)
并发收集器设置
-XX:+CMSIncrementalMode :设置为增量模式。适用于单CPU情况。
-XX:ParallelGCThreads=n :设置并发收集器年轻代收集方式为并行收集时,使用的CPU数。并行收集线程数。
堆设置
-Xms :初始堆大小
-Xmx :最大堆大小
-XX:NewSize=n :设置年轻代大小
-XX:NewRatio=n: 设置年轻代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4
-XX:SurvivorRatio=n :年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5
-XX:MaxPermSize=n :设置持久代大小