Java虚拟机内存与垃圾回收总结

来源:互联网 发布:私处美白 知乎 编辑:程序博客网 时间:2024/06/15 18:53

1. 运行时内存划分

1.1. 程序计数器

  • 字节码行号指示器,用于读取下一条需要执行的字节码指令。
  • 对Java方法记录虚拟机字节码指令地址;对Native方法记录值为空。
  • 线程私有,各线程互不影响。

1.2. 虚拟机栈

  • Java方法执行过程所创建,每调用一个方法就会创建一个栈帧并将之入栈,方法结束后会将栈帧出栈。
  • 栈帧存放局部变量表(编译期分配,包括基本数据类型、对象引用),操作数栈,动态链接,方法出口。
  • 线程私有,各线程互不影响。
  • 可以抛出两个异常:StackOverFlowError(请求的栈深度过大)和OutOfMemoryError(动态扩展无法申请到足够内存)。

1.3. 本地方法栈

  • Native方法执行过程所创建,与虚拟机栈类似。
  • Sun HotSpot虚拟机将虚拟机栈和本地方法栈合并。

1.4. Java堆

  • 内存中最大的一块,用于存放对象实例和数组,垃圾收集器主要管理的区域。
  • 虚拟机启动时即创建,所有线程共享,但会有线程私有的分配缓冲区。
  • 可以抛出异常OutOfMemoryError(动态扩展无法申请到足够内存)。
    1/3EdenFrom SurvivorTo Survivor8/101/101/102/3

1.5. 方法区

  • 存放类信息,运行时常量池,静态变量,即时编译代码等,Sun HotSpot虚拟机用永久代实现方法区。
  • 垃圾回收较少出现,目标仅有常量池和类型卸载。
  • Class文件常量池所存放编译期的字面量(String变量,final常量)和符号引用(类名,成员变量名,方法名),在类加载后进入运行时常量池。
  • 所有线程共享。
  • 可以抛出异常OutOfMemoryError(动态扩展无法申请到足够内存)。

另外:直接内存
并不是运行时内存的一部分,是NIO类利用Native方法分配的堆外内存,在堆上有DirectByteBuffer对象引用了直接内存,可以避免Java堆和Native堆来回复制。
不受Java堆大小的限制,但是会可以抛出异常OutOfMemoryError


2. 内存溢出异常

2.1. Java堆溢出

  • 对象实例到达最大堆容量限制,产生java.lang.OutOfMemoryError: Java heap space异常。
  • 解决策略:如果是内存泄漏,分析泄漏对象到GC Roots引用路径,找到垃圾收集器不能自动回收的原因;如果是内存溢出,说明虚拟机内存参数分配过小,或者代码中有些对象生命周期过长。
  • JVM相关参数:-Xms-Xmx

2.2. 虚拟机栈和本地方法栈溢出

  • 一个线程请求的栈深度过大,产生java.lang.StackOverflowError异常。
  • 创建过多线程,为线程分配的栈会导致内存不足,产生java.lang.OutOfMemoryError: unable to create new native thread异常。
  • JVM相关参数:-Xss

2.3. 方法区和运行时常量池溢出

  • 运行时常量池是方法区的一部分,由永久代实现。如果编译时的字面量过多或产生大量Class类(反射)会导致运行时常量池产生java.lang.OutOfMemoryError: PermGen space异常。
  • String.intern()方法可以在程序运行时操作运行时常量池,即当运行时常量池中已经存放了等于该String对象的字符串,则返回运行时常量池中的引用,否则要先将该String对象的字符串添加入运行时常量池,再返回该String对象的字符串的引用。如果不断创建不同的字符串并调用String.intern()方法就会溢出。
  • JVM相关参数:-XX:PermSize-XX:MaxPermSize

2.4. 直接内存溢出

  • 利用反射获取Unsafe的实例,并通过Unsafe.allocateMemory()大量分配内存,产生java.lang.OutOfMemoryError异常。
  • JVM相关参数:-XX: MaxDirectMemorySize(默认和-Xmx一样)

3. 垃圾收集

3.1. 需要回收的内存

程序计数器、虚拟机栈、本地方法栈随线程而生灭,不需要回收。Java堆和方法区在运行时才知道需要分配哪些内存,需要回收。

3.1.1. 引用计数法(JVM不使用)

给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的需要回收。缺点是无法解决循环引用问题。

3.1.2. 可达性分析法(JVM使用)

从根(GC Roots)对象作为起始点,开始向下搜索,搜索所走过的路径称为“引用链”,当一个对象到GC Roots没有任何引用链相连,则证明此对象是不可用的需要回收。
可以用作GC Roots的对象有

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象
  • 方法区中的静态对象
  • 方法区中的常量对象(常量池中)
  • 本地方法栈中JNI(Native方法)引用的对象

3.1.3. 引用分类

  • 强引用:普遍存在,垃圾回收器绝不会回收,即使程序异常终止
  • 软引用(SoftReference类):内存足够时不会回收;内存不足而在溢出之前,就会对这些对象进行二次回收,如果内存依旧不足才抛出异常。可以实现内存敏感的高速缓存
  • 弱引用(WeakReference类):只能生存到下一次垃圾回收之前,届时不管内存足够与否,都会回收
  • 虚引用(PhantomReference类):和没有任何引用一样不能获取对象实例,在任何时候都可能被回收,仅跟踪对象被回收的活动

3.1.4. 方法区回收

主要针对废弃常量和无用的类,但是不使用了不一定就要回收,因为方法区(永久代)回收效率远低于Java堆。

3.2. 垃圾收集的时机和方式

3.2.1. 标记-清除算法

  • 先标记(通过根节点,标记所有从根节点开始的不可达对象),后清除(统一回收被标记的对象)。
  • 缺点:标记和清除需要遍历效率不高,标记清除后会产生大量不连续的碎片。

3.2.2. 复制算法(新生代)

  • 将原有的内存空间分为相等的两块,每次只使用其中一块,当内存不足时,将其中存活对象复制到未使用的内存块中,然后清除正在使用的内存块中的所有对象。适用于对象存活率低的情况,对应于新生代的Minor GC。
  • 优点:无内存碎片,按顺序分配内存实现简单高效。
  • 缺点:空间浪费。
  • 改进:将内存分为三块,一块大的Eden(8/10)和两块小的Survivor(1/10),每次使用Eden和其中一块Survivor,且优先分配在Eden。回收时将Eden和Survivor中存活对象复制到另外一块Survivor,最后清理使用的Eden和Survivor。当Survivor不够用时,需要依赖于老年代进行分配担保,使大对象直接进入老年代。

3.2.3. 标记-整理算法(老年代)

  • 先标记(通过根节点,标记所有从根节点开始的可达对象),后整理(所有的可达对象移动到内存的一端并清理边界外内存)。适用于对象存活率高的情况,对应于老年代的Full GC。
  • 优点:无内存碎片。
  • 缺点:标记移动效率不高。

三种方法的比较
效率:复制算法 > 标记-整理算法 > 标记-清除算法。
内存整齐度:复制算法 = 标记-整理算法 > 标记-清除算法。
内存利用率:标记-整理算法 = 标记-清除算法 > 复制算法。


4. 垃圾收集器

4.1. Serial收集器

  • 单线程的复制算法收集器,且进行垃圾收集时必须暂停其他所有的工作线程(Stop-The-World)。
  • 应用:client模式下的默认新生代收集器。
  • 优点:没有线程交互的开销,有最高的单线程收集效率。

4.2. ParNew收集器

  • 多线程的复制算法收集器,其余和Serial收集器类似。
  • 应用:server模式下的首选新生代收集器。
  • 优点:单线程上收集效率不高,但是可以利用多CPU资源。

4.3. Parallel Scavenge收集器

  • 多线程的复制算法收集器,和ParNew收集器类似,但是可以控制CPU吞吐量(用户代码时间/总时间),提高CPU利用率。
  • 应用:适合后台运算不需要太多交互的新生代收集器。
  • 优点:可以提高吞吐量,但是可能会增加停顿时间。因为吞吐量提高,GC的频率会减少,每次GC的停顿就会随之增长。可以自适应动态调整吞吐量和停顿时间。

4.4. Serial Old收集器

  • 单线程的标记-整理算法收集器,可以和各种新生代搭配使用的老年代收集器。
  • 应用:client模式下的默认老年代收集器,server模式下作为CMS收集器的备选方案。

4.5. Parallel Old收集器

  • 多线程的标记-整理算法收集器。
  • 应用:可以和Parallel Scavenge收集器搭配的老年代收集器。
  • 优点:可以利用多CPU资源。

4.6. CMS收集器

  • 多线程的标记-清理算法收集器。包括4个步骤:初始标记,并发标记,重新标记,并发清除。并发标记和并发清除操作可以和用户线程一起工作。
  • 应用:可以获取最短回收时间的老年代收集器。
  • 缺点:1. 并发阶段占用了线程使吞吐量降低;2. 无法处理浮动垃圾(用户程序新产生的垃圾)会导致收集失败,从而利用Saerial Old收集器再次收集;3. 标记-清理产生内存碎片,整理是不能并发的。

4.7. G1收集器

  • 可以独立管理新生代和老年代的收集器。
  • 优点:1. 并发收集减少停顿;2. 分代收集;3. 融合了标记-整理和复制算法,不会有内存碎片;4. 可预测的停顿时间。

5. JVM参数

参数 含义 默认值 -Xms 初始堆大小 物理内存的1/64 -Xmx 最大堆大小 物理内存的1/4 -Xmn 新生代大小 堆的3/8 -XX:NewSize 设置新生代大小 -XX:MaxNewSize 新生代最大值 -XX:PermSize 设置永久代初始值 物理内存的1/64 -XX:MaxPermSize 设置永久代最大值 物理内存的1/4 -Xss 每个线程栈大小 小应用128k够用 大应用建议256k -XX:NewRatio 老年代与新生代的比值(除去永久代) -XX:SurvivorRatio Eden与一个Survivor的比值
0 0
原创粉丝点击