Java内存回收

148 阅读5分钟

Java内存区域

方法区、元空间

各个线程共享的内存区域,用于储蓄已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。 方法区有时也被称为永久代。在 JDK1.8 及之后取消了永久代,取而代之的是元空间。在永久代中做调优是十分困难的,且效果不明显。永久代的空间大小受制于 JVM 本身内存限制,而元空间是直接使用机器的内存,只受系统内存限制。元空间默认最大大小为无限制。 一些参数:

-XX:PermSize=N //方法区初始大小
-XX:MaxPermSize=N //方法区的最大大小

-XX:MetaspaceSize=N //元空间的初始大小
-XX:MaxMetaspaceSize=N //原空间的最大大小

堆是 Java 虚拟机所管理的内存中最大的一块,是所有线程所共享的,此内存区域的唯一目的就是存放对象实例。堆是垃圾收集器管理的主要区域。在 HotSpot 中,大多数情况下内存被分为新生代和老年代,默认分配比例为 1:2。在新生代中又被分为一个 Eden 和两个 Survivor ,分配比例为 8:1:1。新生代中对象的年龄在经历一次 Minor GC 后年龄会+1,当年龄达到15(默认值)后会进入老年代。 一些参数:

-XX:InitialHeapSize=N //堆初始大小
-Xms1024m //简写值
-XX:MaxHeapSize=N //堆的最大值
-Xmx1024m //简写值

-XX:MaxTenuringThreshold=15 //新生代进入老年代的年龄

栈区

线程私有,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。包含程序计数器、虚拟机栈和本地方法栈。

垃圾收集算法

哪些对象需要回收?

  1. 引用计数算法 给对象一个引用计数器,每当有一个地方引用它时,计数器就加 1,当引用失效时就减 1。任何计数器为 0 的对象都是不被使用的对象。 缺点:难以解决循环引用问题。
  2. 可达性分析算法 通过一系列的“GC Roots”作为起点,从这些节点开始向下搜索,搜索所达到的路径称为引用链。当一个对象不在任何引用链中,则此对象是不被使用的对象。

什么时候回收?可达性分析算法 中从 GC Roots 搜索时,必须保证引用的一致性,以使对象的引用关系不再发生变化。这点就导致了 GC 必须停止所有的执行线程(Stop The World)。 在 HotSpot 中使用 OopMap 来记录调用信息。在代码中有 OopMap 记录的地方称为 SafePoint。当 GC 发生时,需要让所有线程先跑到 SafePoint 再执行 GC 操作。

如何回收?

  1. 标记-清除算法 同名字一样,这个方式分为“标记”和“清除”两个阶段,首先对不被使用的对象添加一个标记,之后对所有标记到的对象进行统一回收。 缺点:
    • 标记和清除两个阶段的效率都不高
    • 在回收之后会产生大量不连续的内存碎片,导致以后难以储存较大的对象
  2. 复制算法 将对象分为两块,当一块对象用完了,就将还在使用的对象复制到另一块对象上去。较 标记-清除算法 有更高的效率 缺点:每次只能使用一块内存,使内存的利用率变低了。
  3. 标记-整理算法 前半部分和 标记-清除算法 一样,但后续将所有存活的对象移向一端,清除了内存碎片。
  4. 分代收集算法 根据对象的不同存活周期,一般把对象分为新生代和老年代,根据各个年代的特点采用不同的收集算法。 对于新生代,每次都有大量对象死去,故采用复制算法。 对于老年代,对象存活率高,采用 标记-清除标记-整理 算法。

垃圾收集器

如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。

  1. Serial 一个最基本、发展历史最悠久的收集器。采用单线程的收集方式,且在收集时必须暂停其他所有的工作线程,直到收集结束。在 Client 模式下有较好效果。
  2. ParNew ParNew 收集器就是 Serial 的多线程版本,能与 CMS 配合工作。
  3. Parallel Scavenge 吞吐量优先的收集器。(吞吐量=用户代码运行时间/(用户代码运行时间+垃圾收集时间))
  4. Serial Old Serial 的老年代版本
  5. Parallel Old Parallel Scavenge 的老年代版本,JDK1.7、JDK1.8 中以 Parallel Scavenge + Parallel Old 为默认的新生代、老年代回收器。
  6. CMS 以最短回收停顿时间为目的,对 CPU 资源敏感
  7. G1 JDK1.9 中的默认垃圾收集器,G1 的主要关注点在于达到可控的停顿时间,在这个基础上尽可能提高吞吐量。G1 中每个块也会充当 Eden、Survivor、Old 三种角色,但是它们不是固定的,这使得内存使用更加地灵活。

内存分配策略

在 HotSpot 中,大多数情况下内存被分为新生代和老年代,默认分配比例为 1:2。在新生代中又被分为一个 Eden 和两个 Survivor ,分配比例为 8:1:1。 一个新的对象一般会在新生代 Eden 区中分配。当 Eden 区没有足够空间进行分配时,将发起一次 Minor GC。 对于大对象(大量连续内存空间的Java对象),会直接进入老年代。 长期存活的对象(默认熬过 15 次 Minor GC),会进入老年代。 如果在 Survivor 中相同年龄的对象超过了 Survivor 的一般,这些对象将会直接进入老年代。

JDK命令行

一些用于监视虚拟机状态和故障处理的命令

命令 作用
jps 显示系统内所有虚拟机进程
jstat 用于收集虚拟机各方面运行数据
jinfo 显示虚拟机配置信息
jmap 生成虚拟机内存转储快照(heapdump文件)
jhat 用于分析 heapdump 文件
jstack 显示虚拟机线程快照