JVM02——JVM垃圾回收与性能调优(上)(一)

简介: 6.垃圾回收6.1 判断垃圾6.1.1 引用计数法

6.垃圾回收

6.1 判断垃圾

6.1.1 引用计数法

当一个对象被引用一次则计数+1,失去引用计数-1,当计数为0则判断为垃圾。但当对象间存在循环引用时(如下图)会无法被回收。

6.1.2 可达性分析算法

Java中使用可达性分析算法来判断垃圾。肯定不会被垃圾回收的对象为根对象,可以经由根对象直接或间接引用的对象不会被垃圾回收,反则反之。打个比喻:连在串上的葡萄就是不可以被回收的对象,散在盘中的葡萄就是可以被垃圾回收的。


哪些对象可以作为根对象呢?使用eclipse的MAT(memory analyzer)可以进行分析。这个工具比jvisual更加专业,可以找到内存泄漏。


运行如下代码。

/**
 * 演示GC Roots
 */
public class Demo2_2 {
    public static void main(String[] args) throws InterruptedException, IOException {
        List<Object> list1 = new ArrayList<>();
        list1.add("a");
        list1.add("b");
        System.out.println(1);
        System.in.read();
        list1 = null;
        System.out.println(2);
        System.in.read();
        System.out.println("end...");
    }
}


使用命令jps查看当前运行代码的进程为17332。


PS F:\资料 解密JVM\代码\jvm> jps
13472 Launcher
3296 Jps
12868 RemoteMavenServer36
17332 Demo2_2
11180

在list回收前、后分别使用jamp抓取目标进程内存的快照,转储为二进制文件,并设置live参数在抓取快照前主动触发垃圾回收。回收前的操作命令如下。

jmap -dump:format=b,live,file=gcRootDemo.bin 17332
1

使用elipse的MAT工具可以来分析内存泄漏问题,官网下载安装MAT,https://www.eclipse.org/mat/downloads.php


使用MAT工具,菜单栏file->open dump file打开刚才抓取的快照文件。打开文件时报错Invalid HPROF file header,其中一个原因为人工改变了文件的编码格式,重新抓取并不要改变编码格式。将两个文件都打开以方便对照。如下图,查看文件的GC Roots。

可以看到GC Root的具体情况,被分为了4类。

System Class是程序运行所必须的核心类。

第二类是执行本地方法时操作系统所引用的Java对象的类。

Busy Monitor是指正在加锁的对象,如果这对象被回收了,则锁无法被释放,故不会被回收

最后是活动着的线程中,局部变量所引用的对象不能被当成垃圾回收。比如下图中的ArrayList其实就是对应代码中list1被垃圾回收前所指对象,在list对象回收后的抓取的内存快照gcRootDemo2.bin中该对象不存在了。

6.2 五种引用ava中有五种引用类型。

只要沿着GC Root可以找到该对象,则不会被垃圾回收。如图中A1对象,只有当B,C对象对A1的引用都断开时,才会被垃圾回收。


6.2.2 软引用

当发生垃圾回收且内存不够时,则会对其进行进行回收。如图中A2对象,当B对象的引用断开,那么进行垃圾回收且内存不够时,A2对象将会被回收。


6.2.3 弱引用

当发生垃圾回收时,就会对其进行回收。如图中A3对象,当B对象的引用断开,那么进行垃圾回收,A3对象将会被回收。


特别的,软引用和弱引用本身也属于对象,可以配合引用队列进行使用。

6.2.4 虚引用

虚引用与终结器引用必须配合引用对象进行使用。如前文中提到的ByteBuffer对象,会创建一个虚引用Cleaner,并且会将ByteBufer所分配的直接内存传递给Cleaner引用。当ByteBufferber对象被垃圾回收后,Cleaner会进入引用队列。ReferenceHandler会定期扫描引用队列中新入队的对象,当Cleaner被扫描到就会执行其clean()方法,调用Unsafe对象的freememory()将直接内存释放。

6.2.5 终结器引用

所有对象都继承自Object,而Object中有一个finalize()方法,对象可以重写finalize()方法,在对象进行垃圾回收时该方法将被调用。但是对象已经没有强引用了,finalize()方法怎么被调用呢?其实就是通过终结器引用实现的。在B对象断开A4的强引用后,终结器引用会被加入引用队列,由一个优先级很低的finalizeHandler进行扫描,当扫描到引用队列中的终结器引用后,会执行其所引用的A4对象的finalize()方法。由于finalize()方法不会被立刻执行,而是先进行入队,并且负责扫描的finalizeHandler优先级低,可能导致finalize()迟迟得不到执行,因此不推荐使用它进行资源回收.

6.3 软引用应用

配置运行下列代码,显然会报OutOfMemoryError。

/**
 * 演示软引用
 * -Xmx20m 
 */
public class Demo2_3 {
    private static final int _4MB = 4 * 1024 * 1024;
    public static void main(String[] args) throws IOException {
       List<byte[]> list = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            list.add(new byte[_4MB]);
        }
    }
 }

而以上常见在实际编程中其实是常见的,比如在读取图片内容时。软引用可以解决这种内存占用的问题。


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());
        }
    }

以上代码中,list和SoftReference是强引用,但是SoftReference和byte[]是软引用。打印结果如下。

[B@7f31245a
1
[B@6d6f6e28
2
[B@135fbaa4
3
[B@45ee12a7
4
[B@330bedb4
5
循环结束:5
null
null
null
null
[B@330bedb4

观察到在循环结束前可以调用到bye[]数组,但是循环结束前四个数组已经变成了null。


添加虚拟机参数:-XX:+PrintGCDetails -verbose:gc,打印垃圾回收的细节与详细参数,然后再次运行查看垃圾回收完整过程。


[B@7f31245a
1
[B@6d6f6e28
2
[B@135fbaa4
3
[GC (Allocation Failure) [PSYoungGen: 2209K->488K(6144K)] 14497K->13130K(19968K), 0.0024761 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[B@45ee12a7
4
[GC (Allocation Failure) --[PSYoungGen: 4696K->4696K(6144K)] 17338K->17502K(19968K), 0.0020330 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Ergonomics) [PSYoungGen: 4696K->4466K(6144K)] [ParOldGen: 12806K->12675K(13824K)] 17502K->17142K(19968K), [Metaspace: 3331K->3331K(1056768K)], 0.0100720 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) --[PSYoungGen: 4466K->4466K(6144K)] 17142K->17158K(19968K), 0.0024079 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 4466K->0K(6144K)] [ParOldGen: 12691K->740K(8704K)] 17158K->740K(14848K), [Metaspace: 3331K->3331K(1056768K)], 0.0118198 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[B@330bedb4
5

循环结束:5

null
null
null
null
[B@330bedb4
Heap
 PSYoungGen      total 6144K, used 4377K [0x00000000ff980000, 0x0000000100000000, 0x0000000100000000)
  eden space 5632K, 77% used [0x00000000ff980000,0x00000000ffdc64f0,0x00000000fff00000)
  from space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
  to   space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
 ParOldGen       total 8704K, used 740K [0x00000000fec00000, 0x00000000ff480000, 0x00000000ff980000)
  object space 8704K, 8% used [0x00000000fec00000,0x00000000fecb90f0,0x00000000ff480000)
 Metaspace       used 3352K, capacity 4500K, committed 4864K, reserved 1056768K
  class space    used 359K, capacity 388K, committed 512K, reserved 1048576K

观察到在第三个循环结束后,内存已经快不足,进行了新生代回收。在第四个循环结束后,又进行了一次新生代的回收,但是效果不理想(4696K->4696K),于是触发了一次Full GC。由于进行垃圾回收且内存仍然不足,又触发了一次新的垃圾回收,将软引用所引用的对象释放。像缓存的图片等不重要的对象,可以通过软引用来引用,当内存空间不足时就会回收它们。


同时我们也注意到,前四个软引用所指的对象已经是null了,没有必要再把这四个软引用保留在list集合中。可以配合引用队列来完成软引用的回收。

相关文章
|
1月前
|
监控 算法 Java
Java虚拟机(JVM)垃圾回收机制深度剖析与优化策略####
本文作为一篇技术性文章,深入探讨了Java虚拟机(JVM)中垃圾回收的工作原理,详细分析了标记-清除、复制算法、标记-压缩及分代收集等主流垃圾回收算法的特点和适用场景。通过实际案例,展示了不同GC(Garbage Collector)算法在应用中的表现差异,并针对大型应用提出了一系列优化策略,包括选择合适的GC算法、调整堆内存大小、并行与并发GC调优等,旨在帮助开发者更好地理解和优化Java应用的性能。 ####
42 0
|
12天前
|
算法 网络协议 Java
【JVM】——GC垃圾回收机制(图解通俗易懂)
GC垃圾回收,标识出垃圾(计数机制、可达性分析)内存释放机制(标记清除、复制算法、标记整理、分代回收)
|
1月前
|
监控 架构师 Java
Java虚拟机调优的艺术:从入门到精通####
本文作为一篇深入浅出的技术指南,旨在为Java开发者揭示JVM调优的神秘面纱,通过剖析其背后的原理、分享实战经验与最佳实践,引领读者踏上从调优新手到高手的进阶之路。不同于传统的摘要概述,本文将以一场虚拟的对话形式,模拟一位经验丰富的架构师向初学者传授JVM调优的心法,激发学习兴趣,同时概括性地介绍文章将探讨的核心议题——性能监控、垃圾回收优化、内存管理及常见问题解决策略。 ####
|
1月前
|
存储 监控 算法
Java虚拟机(JVM)垃圾回收机制深度解析与优化策略####
本文旨在深入探讨Java虚拟机(JVM)的垃圾回收机制,揭示其工作原理、常见算法及参数调优方法。通过剖析垃圾回收的生命周期、内存区域划分以及GC日志分析,为开发者提供一套实用的JVM垃圾回收优化指南,助力提升Java应用的性能与稳定性。 ####
|
2月前
|
机器学习/深度学习 监控 算法
Java虚拟机(JVM)的垃圾回收机制深度剖析####
本文深入探讨Java虚拟机(JVM)的垃圾回收机制,揭示其工作原理、常见算法、性能调优策略及未来趋势。通过实例解析,为开发者提供优化Java应用性能的思路与方法。 ####
56 1
|
2月前
|
监控 Java 编译器
Java虚拟机调优指南####
本文深入探讨了Java虚拟机(JVM)调优的精髓,从内存管理、垃圾回收到性能监控等多个维度出发,为开发者提供了一系列实用的调优策略。通过优化配置与参数调整,旨在帮助读者提升Java应用的运行效率和稳定性,确保其在高并发、大数据量场景下依然能够保持高效运作。 ####
37 1
|
2月前
|
算法 Java
JVM有哪些垃圾回收算法?
(1)标记清除算法: 标记不需要回收的对象,然后清除没有标记的对象,会造成许多内存碎片。 (2)复制算法: 将内存分为两块,只使用一块,进行垃圾回收时,先将存活的对象复制到另一块区域,然后清空之前的区域。用在新生代 (3)标记整理算法: 与标记清除算法类似,但是在标记之后,将存活对象向一端移动,然后清除边界外的垃圾对象。用在老年代
26 0
|
2月前
|
缓存 Prometheus 监控
Elasticsearch集群JVM调优设置合适的堆内存大小
Elasticsearch集群JVM调优设置合适的堆内存大小
393 1
|
12天前
|
存储 Java 程序员
【JVM】——JVM运行机制、类加载机制、内存划分
JVM运行机制,堆栈,程序计数器,元数据区,JVM加载机制,双亲委派模型
|
1月前
|
存储 监控 算法
深入探索Java虚拟机(JVM)的内存管理机制
本文旨在为读者提供对Java虚拟机(JVM)内存管理机制的深入理解。通过详细解析JVM的内存结构、垃圾回收算法以及性能优化策略,本文不仅揭示了Java程序高效运行背后的原理,还为开发者提供了优化应用程序性能的实用技巧。不同于常规摘要仅概述文章大意,本文摘要将简要介绍JVM内存管理的关键点,为读者提供一个清晰的学习路线图。