文本已收录至我的GitHub仓库,欢迎Star:github.com/bin39232820…
种一棵树最好的时间是十年前,其次是现在
我知道很多人不玩qq了,但是怀旧一下,欢迎加入六脉神剑Java菜鸟学习群,群聊号码:549684836 鼓励大家在技术的路上写博客
前面的章节
Java与C++之间有一堵内存动态分配和垃圾收集的高墙 外面的人想进去,里面的人想出来
Java中可以作为GC Roots对象包括以下几种:
示例如下
1.2之前Java中对象只有:引用和未引用两种状态
1.2之后进行了扩充:强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)
根搜索法不可达的对象,还有两次标记的过程,进行自救。
过程:
在跟搜索算法不可达的对象,并将第一次被标记并且进行一次筛选。筛选条件是:此对象是否有必要调用finalize()方法。当对象没有覆盖finalize或方法已被虚拟机执行了,虚拟机 会认为以上两种情况没有必要执行。
如果这个对象被判定为有必要执行finalize()方法。该对象将会被F-Queue的队列中,稍后虚拟机将建立一个低优先级的Finalizer线程去执行。这里的执行指的是虚拟机会触发该 方法,但是并不承诺等待他运行结束(原因:finalizer执行很慢或死循环,导致队列中其他的对象永远在等待或内存溢出)。finalizer方法是对象逃离死亡的最后一次机会,对象只要finalizer中 拯救自己(建立自己引用)第二次标记的时候该对象就被移除回收队列。如果没有拯救,那么很快不久就被回收。但是如果对象的finalizer方法执行了,但是可能该对象还存活着。
实例代码如下:
public static FinalizerEscapeGC SAVE_HOOK = null; public void isAlive() { System.out.println("yes, i am still alive!!!"); } @Override protected void finalize() throws Throwable { super.finalize(); System.out.println("finalizer method execute"); FinalizerEscapeGC.SAVE_HOOK = this; } public static void main(String[] args) throws Exception { SAVE_HOOK = new FinalizerEscapeGC(); // 对象第一次进行成功的拯救 SAVE_HOOK = null; System.gc(); // 因为finalizer方法的优先级很低,所以暂停0.5s,以等待他运行 Thread.sleep(500); if (SAVE_HOOK != null) { SAVE_HOOK.isAlive(); } else { System.out.println("no, i am dead."); } // 下面的代码和上面的代码一样。但是对象却自救失败了。 // 对象第二次进行成功的拯救 SAVE_HOOK = null; System.gc(); // 因为finalizer方法的优先级很低,所以暂停0.5s,以等待他运行 Thread.sleep(600); if (SAVE_HOOK != null) { SAVE_HOOK.isAlive(); } else { System.out.println("no, i am dead."); } } 复制代码
判定一个类是否是无用类则比较复杂。
提供了-Xnoclassgc参数进行控制,还可以使用-verbose:class及-XX:+TraceClassLoading、-XX:+TraceClassUnLoading 其中前两个参数在product版虚拟机中已经支持,最后一个需要FastDebug版的虚拟机支持。
首先标记处所有需要回收的对象;其次在标记完成之后统一回收掉所有被标记的对象。
最基础的算法。后续的算法都是基于此,并针对其缺陷进行改进的得到的。
缺点:
图解
现代的商业虚拟机都是都是采用这种算法来回收新生代内存。新生代中对象98%都是朝生夕死。并非严格按照1:1。而是按照8:1。
1块较大的Eden和2块较小的Survivor内存;每次使用Eden和1个Survivor。(Eden:Survivor = 8:1)
这样内存中整个新生代的内存容量为(80+10=90),保证了只有10%的内存容量的浪费。 但是实际发生垃圾回收时我们无法保证98%对象都是标记死亡的,如果存活的对象的占用的内存多于剩下的10%的容量,这时则需要其他内存(老年代)进行分配担保。
到Java8为止
其中,初始标记和重新标记还需stop the world。初始标记只是标记一下GC Roots能直接关联到的对象,速度很快。并发标记阶段也就是进行GC Roots Tracing的过程,而重新标记是为了修正 因用户程序继续运行导致标记变动的那一部分对象的标记记录。重新标记这段时间远比初始标记时间长但是远比并发标记时间短。
图片
阅读
最前面的数字:33.125 100.667 代表GC发生的时间;这个是从虚拟机启动来经过的秒数
GC和Full GC说明了此次垃圾收集停顿的类型。不是用来区分新生代GC还是老年代GC。如果有Full,则表明此次GC 发生了Stop The World
[DefNew [Tenured [Perm表示GC发生的区域 Serial收集器中的新生代名为Default New Generation所以显示[DefNew;如果是Parallel收集器新生代名称为 [ParNew 意为"Parallel New Generation";如果是Parallel Scavenge收集器新生代名称叫做PSYoungGen。老年代和永久代同理,根据垃圾收集器 的名称来决定的
方括号内的“3324K --> 152K(3712K)”表示GC前的内存区域已使用容量 -> GC后内存区域已使用的容量(该内存区域总容量); 而方括号之外的3324K -> 152K(11904K)表示GC前Java堆已使用的容量->GC后Java堆已使用的容量(Java堆总容量)
0.0025925 secs表示该内存区域GC所占时间,单位是秒。
Java自动内存管理主要解决了两个问题:
大多数情况下,对象在Eden区中分配。当Eden内存不足时,虚拟机将发起一次MinorGC
提供了-XX:+PrintGCDetails日志参数。告诉虚拟机发生垃圾回收时打印内存回收日志,并在线程结束后输出各内存的分配情况
Minor GC和Full GC(Stop The world)
/** * VM args:-verbose:gc -Xms20M -Xmn10M -XX:SurvivorRatio=8 -XX:+PrintGCDetails */ public class TestAllocation { private static final int _1MB = 1024 * 1024; public static void main(String[] args) { byte[] allocation1, allocation2, allocation3, allocation4, allocation5; allocation1 = new byte[2 * _1MB]; allocation2 = new byte[2 * _1MB]; allocation3 = new byte[2 * _1MB]; allocation3 = new byte[2 * _1MB]; allocation4 = new byte[2 * _1MB]; allocation5 = new byte[4 * _1MB]; // 至少发生了MinorGC } } 复制代码
[GC (Allocation Failure) [PSYoungGen: 7651K->1016K(9216K)] 7651K->2131K(19456K), 0.0017243 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [PSYoungGen: 7402K->1000K(9216K)] 8518K->8375K(19456K), 0.0039360 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC (Ergonomics) [PSYoungGen: 1000K->0K(9216K)] [ParOldGen: 7375K->8172K(24576K)] 8375K->8172K(33792K), [Metaspace: 3455K->3455K(1056768K)], 0.0103884 secs] [Times: user=0.16 sys=0.00, real=0.01 secs] Heap PSYoungGen total 9216K, used 4373K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000) eden space 8192K, 53% used [0x00000007bf600000,0x00000007bfa457e0,0x00000007bfe00000) from space 1024K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007c0000000) to space 1024K, 0% used [0x00000007bfe00000,0x00000007bfe00000,0x00000007bff00000) ParOldGen total 24576K, used 12268K [0x00000006c2800000, 0x00000006c4000000, 0x00000007bf600000) object space 24576K, 49% used [0x00000006c2800000,0x00000006c33fb3c8,0x00000006c4000000) Metaspace used 3477K, capacity 4496K, committed 4864K, reserved 1056768K class space used 375K, capacity 388K, committed 512K, reserved 1048576K 复制代码
所谓大对象:需要大量连续内存空间的Java对象。典型的如很长的字符串或数组(上面例子中byte[]就是大对象)
大对象对虚拟机来说是坏事,尤其是那种朝生夕死的大对象。经常内存不足而导致不得不提前进行GC
虚拟机提供-XX:PretenureSizeThreshold参数,使大于此值的对象在老年代分配。好处是避免了在Eden区及两个Survivor区间发生大量的复制。新生代采用复制算法进行垃圾收集。
每个对象定义一个年龄(Age)计数器
对象在Survivor区域每熬过一次MinorGC,年龄就会增加。默认年龄为15。当年龄超过一定的阈值就会进入老年代。 通过参数:-XX:MaxTenuringThreshold设置
为了更好的适应不同的程序,虚拟机并不是永远要求对象年龄达到MaxTenuringThreshold才晋升到老年代。 如果在Survivor空间的相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代,无需MaxTenuringThreshold要求的对象
JVM的基本知识就这么多了,接下来我会给大家出一章面试题的章节,和一章根据业务实战JVM调优,大家持续关注哦
好了各位,以上就是这篇文章的全部内容了,能看到这里的人呀,都是真粉。
创作不易,各位的支持和认可,就是我创作的最大动力,我们下篇文章见
六脉神剑 | 文 【原创】如果本篇博客有任何错误,请批评指教,不胜感激 !