作者:小傅哥
博客:https://bugstack.cn
沉淀、分享、成长,让自己和他人都能有所收获!😄
提升自身价值有多重要?
经过了风风雨雨,看过了男男女女。时间经过的岁月就没有永恒不变的!
在这趟车上有人下、有人上,外在别人给你点评的标签、留下的烙印,都只是这趟车上的故事。只有个人成长了、积累了、沉淀了,才有机会当自己的司机。
可能某个年龄段的你还看不懂,但如果某天你不那么忙了,要思考思考自己的路、自己的脚步。看看这些是不是你想要的,如果都是你想要的,为什么你看起来不开心?
好!加油,走向你想成为的自己!
谢飞机,小记!
,中午吃饱了开始发呆,怎么就学不来这些知识呢,它也不进脑子!
谢飞机:喂,面试官大哥,我想问个问题。
面试官:什么?
谢飞机:就是这知识它不进脑子呀!
面试官:这....
谢飞机:就是看了忘,忘了看的!
面试官:是不是没有实践?只是看了就觉得会了,收藏了就表示懂了?哪哪都不深入!?
谢飞机:好像是!那有什么办法?
面试官:也没有太好的办法,学习本身就是一件枯燥的事情。减少碎片化的时间浪费,多用在系统化的学习上会更好一些。哪怕你写写博客记录下,验证下也是好的。
说是垃圾回收,我不引用了它就回收了?什么时候回收的?咋回收的?
没有看到实际的例子,往往就很难让理科生接受这类知识。我自己也一样,最好是让我看得见。代码是对数学逻辑的具体实现,没有实现过程只看答案是没有意义的。
测试代码
public class ReferenceCountingGC { public Object instance = null; private static final int _1MB = 1024 * 1024; /** * 这个成员属性的唯一意义就是占点内存, 以便能在GC日志中看清楚是否有回收过 */ private byte[] bigSize = new byte[2 * _1MB]; public static void main(String[] args) { testGC(); } public static void testGC() { ReferenceCountingGC objA = new ReferenceCountingGC(); ReferenceCountingGC objB = new ReferenceCountingGC(); objA.instance = objB; objB.instance = objA; objA = null; objB = null; // 假设在这行发生GC, objA和objB是否能被回收? System.gc(); } }
例子来自于《深入理解Java虚拟机》中引用计数算法章节。
例子要说明的结果是,相互引用下却已经置为null的两个对象,是否会被GC回收。如果只是按照引用计数器算法来看,那么这两个对象的计数标识不会为0,也就不能被回收。但到底有没有被回收呢?
这里我们先采用 jvm 工具指令,jstat来监控。因为监控的过程需要我手敲代码,比较耗时,所以我们在调用testGC()前,睡眠会 Thread.sleep(55000);
。启动代码后执行如下指令。
E:\itstack\git\github.com\interview>jps -l 10656 88464 38372 org.itstack.interview.ReferenceCountingGC 26552 sun.tools.jps.Jps 110056 org.jetbrains.jps.cmdline.Launcher E:\itstack\git\github.com\interview>jstat -gc 38372 2000 S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT 10752.0 10752.0 0.0 0.0 65536.0 6561.4 175104.0 0.0 4480.0 770.9 384.0 75.9 0 0.000 0 0.000 0.000 10752.0 10752.0 0.0 0.0 65536.0 6561.4 175104.0 0.0 4480.0 770.9 384.0 75.9 0 0.000 0 0.000 0.000 10752.0 10752.0 0.0 0.0 65536.0 6561.4 175104.0 0.0 4480.0 770.9 384.0 75.9 0 0.000 0 0.000 0.000 10752.0 10752.0 0.0 0.0 65536.0 6561.4 175104.0 0.0 4480.0 770.9 384.0 75.9 0 0.000 0 0.000 0.000 10752.0 10752.0 0.0 0.0 65536.0 6561.4 175104.0 0.0 4480.0 770.9 384.0 75.9 0 0.000 0 0.000 0.000 10752.0 10752.0 0.0 0.0 65536.0 6561.4 175104.0 0.0 4480.0 770.9 384.0 75.9 0 0.000 0 0.000 0.000 10752.0 10752.0 0.0 0.0 65536.0 6561.4 175104.0 0.0 4480.0 770.9 384.0 75.9 0 0.000 0 0.000 0.000 10752.0 10752.0 0.0 1288.0 65536.0 0.0 175104.0 8.0 4864.0 3982.6 512.0 440.5 1 0.003 1 0.000 0.003 10752.0 10752.0 0.0 0.0 65536.0 437.3 175104.0 1125.5 4864.0 3982.6 512.0 440.5 1 0.003 1 0.012 0.015 10752.0 10752.0 0.0 0.0 65536.0 437.3 175104.0 1125.5 4864.0 3982.6 512.0 440.5 1 0.003 1 0.012 0.015
注意:观察后面三行,S1U = 1288.0
、GCT = 0.003
,说明已经在执行垃圾回收。
接下来,我们再换种方式测试。在启动的程序中,加入GC打印参数,观察GC变化结果。
-XX:+PrintGCDetails 打印每次gc的回收情况 程序运行结束后打印堆空间内存信息(包含内存溢出的情况) -XX:+PrintHeapAtGC 打印每次gc前后的内存情况 -XX:+PrintGCTimeStamps 打印每次gc的间隔的时间戳 full gc为每次对新生代老年代以及整个空间做统一的回收 系统中应该尽量避免 -XX:+TraceClassLoading 打印类加载情况 -XX:+PrintClassHistogram 打印每个类的实例的内存占用情况 -Xloggc:/Users/xiaofuge/Desktop/logs/log.log 配合上面的使用将上面的日志打印到指定文件 -XX:HeapDumpOnOutOfMemoryError 发生内存溢出将堆信息转存起来 以便分析
这回就可以把睡眠去掉了,并添加参数 -XX:+PrintGCDetails
,如下:
测试结果
[GC (System.gc()) [PSYoungGen: 9346K->936K(76288K)] 9346K->944K(251392K), 0.0008518 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC (System.gc()) [PSYoungGen: 936K->0K(76288K)] [ParOldGen: 8K->764K(175104K)] 944K->764K(251392K), [Metaspace: 3405K->3405K(1056768K)], 0.0040034 secs] [Times: user=0.08 sys=0.00, real=0.00 secs] Heap PSYoungGen total 76288K, used 1966K [0x000000076b500000, 0x0000000770a00000, 0x00000007c0000000) eden space 65536K, 3% used [0x000000076b500000,0x000000076b6eb9e0,0x000000076f500000) from space 10752K, 0% used [0x000000076f500000,0x000000076f500000,0x000000076ff80000) to space 10752K, 0% used [0x000000076ff80000,0x000000076ff80000,0x0000000770a00000) ParOldGen total 175104K, used 764K [0x00000006c1e00000, 0x00000006cc900000, 0x000000076b500000) object space 175104K, 0% used [0x00000006c1e00000,0x00000006c1ebf100,0x00000006cc900000) Metaspace used 3449K, capacity 4496K, committed 4864K, reserved 1056768K class space used 376K, capacity 388K, committed 512K, reserved 1048576K
有了这个例子,我们再接着看看JVM垃圾回收的知识框架!
垃圾收集(Garbage Collection,简称GC),最早于1960年诞生于麻省理工学院的Lisp是第一门开始使用内存动态分配和垃圾收集技术的语言。
垃圾收集器主要做的三件事:哪些内存需要回收
、什么时候回收
、怎么回收。
而从垃圾收集器的诞生到现在有半个世纪的发展,现在的内存动态分配和内存回收技术已经非常成熟,一切看起来都进入了“自动化”。但在某些时候还是需要我们去监测在高并发的场景下,是否有内存溢出、泄漏、GC时间过程等问题。所以在了解和知晓垃圾收集的相关知识对于高级程序员的成长就非常重要。
垃圾收集器的核心知识项主要包括:判断对象是否存活、垃圾收集算法、各类垃圾收集器以及垃圾回收过程。如下图;
原图下载链接:http://book.bugstack.cn/#s/6jJp2icA
从实现来看,引用计数器法(Reference Counting)虽然占用了一些额外的内存空间来进行计数,但是它的实现方案简单,判断效率高,是一个不错的算法。
也有一些比较出名的引用案例,比如:微软COM(Component Object Model) 技术、使用ActionScript 3的FlashPlayer、 Python语言等。
但是,在主流的Java虚拟机中并没有选用引用技术算法来管理内存,主要是因为这个简单的计数方式在处理一些相互依赖、循环引用等就会非常复杂。可能会存在不再使用但又不能回收的内存,造成内存泄漏
Java、C#等主流语言的内存管理子系统,都是通过可达性分析(Reachability Analysis)算法来判定对象是否存活的。
它的算法思路是通过定义一系列称为 GC Roots 根对象作为起始节点集,从这些节点出发,穷举该集合引用到的全部对象填充到该集合中(live set)。这个过程教过标记,只标记那些存活的对象 好,那么现在未被标记的对象就是可以被回收的对象了。
GC Roots 包括;
两大问题
Serial
Parallel ParNew
Parallel Scavenge
Serial Old
Parallel Old
CMS