1

带着三个问题看垃圾回收 
1.回收谁
2.什么时候回收
3.怎么回收

1.回收谁

引用计数法:给对象中添加一个引用计数器,每当有对象引用它时,计数器就加1,引用失效就减1,计数器到0的时候代表不能使用该对象,不能解决循环引用的问题

可达性分析:通过GCRoots做为起点,从这个起点向下搜索,当一个对象到GCRoots没有任何引用链相连的时候,这个对象是不可用的,可以回收。虚拟机栈(栈帧变量表中引用的对象),方法区(类的静态属性引用的对象,常量引用的对象),本地方法中JNI(Native引用的对象)
image.png

"食之无味,弃之可惜"
强引用(认可OOM,也不会回收)
软引用(系统OOM之前,这些对象被回收)
弱引用(无论内存够不够,都会回收)
虚引用(只会收到回收通知)

"最后一刻挣扎"
一个对象的死亡,至少要被标记两次,第一次看有没有必要执行该对象中的finalize方法,如果该方法被调用过或者对象没有覆盖整个方法,就没有必要执行finalize。如果执行了finalize,可以在方法里面自救,自救方案是与引用链上任何一个对象关联即可。不建议使用

方法区的回收
回收效率低,回收严谨。只有满足以下三点才会回收
1.该类的所有实例都被回收,堆中不存在任何该类的实例
2.加载该类的ClassLoader已被回收
3.该类的java.lang.Class对象没有任何地方被应用,无法通过反射来访问该类

2.什么时候回收

应用线程空闲时
内存满的时候

3.怎么回收

标记清除算法
image.png
先标记要回收的对象,在统一清除。缺陷是会产生大量不连续的内存碎片,在分配大对象时,不得不提前触发另一次垃圾收集动作

复制算法
image.png
将内存分AB两块,每次只用一块,A的内存用完了,回收的时候就将A还存活的对象放在B上,然后统一清理A。缺陷是对象多的时候浪费复制时间,对内存的开销也比较大。

标记整理算法
image.png
标记出所有要回收的对象,标记完成后,将存活的对象移动一端,然后清理掉端边界以外的内存。

分代收集算法
根据新生代和老年代的特点,使用分代收集算法
image.png
因为新生代朝生夕死,所以用复制算法,仅需要复制少量对象。
老年代存活率高,对象多,没有额外空间进行分配,就使用标记-整理算法。可以自由搭配很多种,不过大致的类型就是以下几种。

串行收集器(Serial)
image.png
-XX:+SerialGC
单线程收集器,这里的单线程不是指垃圾回收的线程只有一个,而是相对于应用程序来讲,在回收垃圾的时候要暂停应用程序(STW)
在内存不足时,串行GC设置停顿标识,当所有线程到达安全点后,应用程序暂停,开始垃圾收集。适合堆内存不高且单核的cpu使用。

并行收集器(ParNew,Parallel Scavenge)
image.png
-XX:+UseParNewGC
ParNew是Serial收集器的多线程版本,搭配老年代CMS首选。适合多核cpu。ParallelScavenge更关注吞吐量,同样需要STW,适合和前台交互少的系统,后台处理任务量大的

并发收集器(CMS)
更关注低延迟的收集器,分为以下四个阶段,适合内存大,多核cpu。缺陷:消耗内存过的大,容易引起fullGC。有碎片,为防止fullGC,默认开启碎片整理参数
image.png

初始标记:以STW的方式标记所有根对象,很快
并发标记,与程序并发执行,标记出所有根路径的可达路径
重新标记,以STW标记有可能在这期间错过的,同样很快
并发清除,将不可达对象并发回收

G1收集器
引入了 Region概念,和CMS比较像,只不过有Region的优势
image.png

观看GC日志
image.png
33.125代表虚拟机启动到现在,经过了多少秒
Full GC和GC代表停顿类型,不是为了区分新生代和老年代的,如果有Full,代表是以SWT触发的垃圾收集
DefNew,Tenured,Perm才是区域,发生在什么区域上的
3324K - > 152K(3712K) :GC前该内存区域已使用的容量 -> GC后该内存已使用的容量(总容量)

内存分配与回收策略

  • 对象优先在Eden分配,如果说Eden内存空间不足,就会发生Minor GC
  • 大对象直接进入老年代,大对象需要大量连续内存空间的Java对象,比如很长的字符串和大型数组,1、导致内存有空间,还是需要提前进行垃圾回收获取连续空间来放他们,2、会进行大量的内存复制。-XX:PretenureSizeThreshold 参数 ,大于这个数量直接在老年代分配,缺省为0 ,表示绝不会直接分配在老年代。
  • 长期存活的对象将进入老年代,默认15岁,-XX:MaxTenuringThreshold调整
  • 动态对象年龄判定,为了能更好地适应不同程序的内存状况,虚拟机并不是永远地要求对象的年龄必须达到了MaxTenuringThreshold才能晋升老年代,如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到MaxTenuringThreshold中要求的年龄
  • 空间分配担保:新生代中有大量的对象存活,survivor空间不够,当出现大量对象在MinorGC后仍然存活的情况(最极端的情况就是内存回收后新生代中所有对象都存活),就需要老年代进行分配担保,把Survivor无法容纳的对象直接进入老年代.只要老年代的连续空间大于新生代对象的总大小或者历次晋升的平均大小,就进行Minor GC,否则FullGC。

内存泄漏和内存溢出
内存泄漏是该释放的对象没有得到释放
内存溢出是撑爆了内存,对象太多了

JDK为我们提供的工具

  • jps

列出当前机器上正在运行的虚拟机进程
-p :仅仅显示VM 标示,不显示jar,class, main参数等信息.
-m:输出主函数传入的参数. 下的hello 就是在执行程序时从命令行输入的参数
-l: 输出应用程序主类完整package名称或jar完整名称.
-v: 列出jvm参数, -Xms20m -Xmx50m是启动程序指定的jvm参数

  • jstat

是用于监视虚拟机各种运行状态信息的命令行工具。它可以显示本地或者远程虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据,在没有GUI图形界面,只提供了纯文本控制台环境的服务器上,它将是运行期定位虚拟机性能问题的首选工具。
假设需要每250毫秒查询一次进程2764垃圾收集状况,一共查询20次,那命令应当是:jstat-gc 2764 250 20
常用参数:
-class (类加载器)
-compiler (JIT)
-gc (GC堆状态)
-gccapacity (各区大小)
-gccause (最近一次GC统计和原因)
-gcnew (新区统计)
-gcnewcapacity (新区大小)
-gcold (老区统计)
-gcoldcapacity (老区大小)
-gcpermcapacity (永久区大小)
-gcutil (GC统计汇总)
-printcompilation (HotSpot编译统计)

  • jmap
  • jstack
  • .........

Dog_Lee
46 声望3 粉丝