Board logo

标题: 深入理解 Java 虚拟机(10) [打印本页]

作者: look_w    时间: 2018-12-24 17:00     标题: 深入理解 Java 虚拟机(10)

垃圾收集器垃圾收集器是内存回收算法的具体实现,Java 虚拟机规范中对垃圾收集器应该如何实现并没有任何规定,因此不同厂商、不同版本的虚拟机所提供的垃圾收集器都可能会有很大的差别。Sun HotSpot 虚拟机 1.6 版包含了如下收集器:Serial、ParNew、Parallel Scavenge、CMS、Serial Old、Parallel Old。这些收集器以不同的组合形式配合工作来完成不同分代区的垃圾收集工作。
垃圾回收分析
在用代码分析之前,我们对内存的分配策略明确以下三点:
对垃圾回收策略说明以下两点:
下面我们来看如下代码:
public class SlotGc{      public static void main(String[] args){          byte[] holder = new byte[32*1024*1024];          System.gc();      }  }  代码很简单,就是向内存中填充了 32MB 的数据,然后通过虚拟机进行垃圾收集。在 javac 编译后,我们执行如下指令:java -verbose:gc SlotGc 来查看垃圾收集的结果,得到如下输出信息:
[GC 208K->134K(5056K), 0.0017306 secs][Full GC 134K->134K(5056K), 0.0121194 secs][Full GC 32902K->32902K(37828K), 0.0094149 sec注意第三行,“->”之前的数据表示垃圾回收前堆中存活对象所占用的内存大小,“->”之后的数据表示垃圾回收堆中存活对象所占用的内存大小,括号中的数据表示堆内存的总容量,0.0094149 sec 表示垃圾回收所用的时间。
从结果中可以看出,System.gc()运行后并没有回收掉这 32MB 的内存,这应该是意料之中的结果,因为变量holder 还处在作用域内,虚拟机自然不会回收掉 holder 引用的对象所占用的内存。
我们把代码修改如下:
public class SlotGc{      public static void main(String[] args){          {          byte[] holder = new byte[32*1024*1024];          }          System.gc();      }  }  加入花括号后,holder 的作用域被限制在了花括号之内,因此,在执行System.gc()时,holder 引用已经不能再被访问,逻辑上来讲,这次应该会回收掉 holder 引用的对象所占的内存。但查看垃圾回收情况时,输出信息如下:
[GC 208K->134K(5056K), 0.0017100 secs][Full GC 134K->134K(5056K), 0.0125887 secs][Full GC 32902K->32902K(37828K), 0.0089226 secs]很明显,这 32MB 的数据并没有被回收。下面我们再做如下修改:
public class SlotGc{      public static void main(String[] args){          {          byte[] holder = new byte[32*1024*1024];          holder = null;          }          System.gc();      }  }  这次得到的垃圾回收信息如下:
[GC 208K->134K(5056K), 0.0017194 secs][Full GC 134K->134K(5056K), 0.0124656 secs][Full GC 32902K->134K(37828K), 0.0091637 secs]说明这次 holder 引用的对象所占的内存被回收了。我们慢慢来分析。
首先明确一点:holder 能否被回收的根本原因是局部变量表中的 Slot 是否还存有关于 holder 数组对象的引用。
在第一次修改中,虽然在 holder 作用域之外进行回收,但是在此之后,没有对局部变量表的读写操作,holder 所占用的 Slot 还没有被其他变量所复用(回忆 Java 内存区域与内存溢出一文中关于 Slot 的讲解),所以作为 GC Roots 一部分的局部变量表仍保持者对它的关联。这种关联没有被及时打断,因此 GC 收集器不会将 holder 引用的对象内存回收掉。 在第二次修改中,在 GC 收集器工作前,手动将 holder 设置为 null 值,就把 holder 所占用的局部变量表中的 Slot 清空了,因此,这次 GC 收集器工作时将 holder 之前引用的对象内存回收掉了。
当然,我们也可以用其他方法来将 holder 引用的对象内存回收掉,只要复用 holder 所占用的 slot 即可,比如在 holder 作用域之外执行一次读写操作。
为对象赋 null 值并不是控制变量回收的最好方法,以恰当的变量作用域来控制变量回收时间才是最优雅的解决办法。另外,赋 null 值的操作在经过虚拟机 JIT 编译器优化后会被消除掉,经过 JIT 编译后,System.gc()执行时就可以正确地回收掉内存,而无需赋 null 值。
性能调优Java 虚拟机的内存管理与垃圾收集是虚拟机结构体系中最重要的组成部分,对程序(尤其服务器端)的性能和稳定性有着非常重要的影响。性能调优需要具体情况具体分析,而且实际分析时可能需要考虑的方面很多,这里仅就一些简单常用的情况作简要介绍。
1、线程堆栈:可通过 -Xss 调整大小,内存不足时抛出 StackOverflowError(纵向无法分配,即无法分配新的栈帧)或 OutOfMemoryError(横向无法分配,即无法建立新的线程)。
2、Socket 缓冲区:每个 Socket 连接都有 Receive 和 Send 两个缓冲区,分别占用大约 37KB 和 25KB 的内存。如果无法分配,可能会抛出 IOException:Too many open files 异常。关于 Socket 缓冲区的详细介绍参见我的 Java 网络编程系列中深入剖析 Socket 的几篇文章。
3、JNI 代码:如果代码中使用了JNI调用本地库,那本地库使用的内存也不在堆中。
4、虚拟机和 GC:虚拟机和 GC 的代码执行也要消耗一定的内存。




欢迎光临 电子技术论坛_中国专业的电子工程师学习交流社区-中电网技术论坛 (http://bbs.eccn.com/) Powered by Discuz! 7.0.0