我是靠谱客的博主 活力野狼,最近开发中收集的这篇文章主要介绍Java虚拟机内存与垃圾回收总结1. 运行时内存划分2. 内存溢出异常3. 垃圾收集4. 垃圾收集器5. JVM参数,觉得挺不错的,现在分享给大家,希望可以做个参考。
概述
1. 运行时内存划分
1.1. 程序计数器
- 字节码行号指示器,用于读取下一条需要执行的字节码指令。
- 对Java方法记录虚拟机字节码指令地址;对Native方法记录值为空。
- 线程私有,各线程互不影响。
1.2. 虚拟机栈
- Java方法执行过程所创建,每调用一个方法就会创建一个栈帧并将之入栈,方法结束后会将栈帧出栈。
- 栈帧存放局部变量表(编译期分配,包括基本数据类型、对象引用),操作数栈,动态链接,方法出口。
- 线程私有,各线程互不影响。
- 可以抛出两个异常:
StackOverFlowError
(请求的栈深度过大)和OutOfMemoryError
(动态扩展无法申请到足够内存)。
1.3. 本地方法栈
- Native方法执行过程所创建,与虚拟机栈类似。
- Sun HotSpot虚拟机将虚拟机栈和本地方法栈合并。
1.4. Java堆
- 内存中最大的一块,用于存放对象实例和数组,垃圾收集器主要管理的区域。
- 虚拟机启动时即创建,所有线程共享,但会有线程私有的分配缓冲区。
- 可以抛出异常
OutOfMemoryError
(动态扩展无法申请到足够内存)。
堆 { 新 生 代 1 / 3 { E d e n 8 / 10 F r o m S u r v i v o r 1 / 10 T o S u r v i v o r 1 / 10 老 年 代 2 / 3 堆left{begin{array}{ll} 新生代&1/3left{begin{array}{ll} Eden&8/10\ From Survivor&1/10\ To Survivor&1/10\ end{array}right.\ 老年代&2/3 end{array}right. 堆⎩⎪⎪⎨⎪⎪⎧新生代老年代1/3⎩⎨⎧EdenFrom 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的比值 |
最后
以上就是活力野狼为你收集整理的Java虚拟机内存与垃圾回收总结1. 运行时内存划分2. 内存溢出异常3. 垃圾收集4. 垃圾收集器5. JVM参数的全部内容,希望文章能够帮你解决Java虚拟机内存与垃圾回收总结1. 运行时内存划分2. 内存溢出异常3. 垃圾收集4. 垃圾收集器5. JVM参数所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复