更多参考:
一篇简单易懂的原理文章,让你把JVM玩弄与手掌之中
关于JVM的类型和模式
jvm系列 (一) ---jvm内存区域与溢出
JVM常量池浅析 --- 很棒
JVM 的 工作原理,层次结构 以及 GC工作原理
JVM内存分配及GC
Java8内存模型—永久代(PermGen)和元空间(Metaspace)
JVM第一篇:一个Java内存泄漏的排查案例
一、JVM运行时数据区(注意jdk1.6/1.7/1.8的区别)
- 程序计数器: 每个线程享有一个程序计数器,当前线程执行字节码的行号指示器。分支、循环、跳转、异常处理和线程恢复等功能都依赖于程序计数器。
- 虚拟机栈: stack属于线程私有,生命周期与线程相同。虚拟机栈描述了java方法执行的内存模型。用于java方法。
- 本地方法栈: 用于native方法的stack。
- 堆: 堆是所有线程共享的内存区域,用于存放所有的对象实例和数组,是垃圾回收管理的主要区域。
- 方法区: 方法区用于存储已被虚拟机加载的类信息、常量、静态变量、编译器编译后的代码等数据。运行时常量池是方法区的一部分,用于存放编译器生成的各种字面量和符号引用。
可见,垃圾回收主要针对于方法区和堆内存;不针对虚拟机栈、本地方法栈和程序计数器,因为它们的生命周期与栈相同,随生随灭。
-
jdk1.8的JVM运行时数据区域概览
1.8同1.7比,最大的差别就是:元数据区取代了永久代。元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元数据空间并不在虚拟机中,而是使用本地内存。
二、java内存模型
JVM内存分布模型包括了堆和方法区,方法区称为永久代(permanent generation),堆空间分为新生代(young generation)和老年代(old generation)。新生代则又可以分为Eden和Survivor两个区域。如图:
- Eden Space (heap): 对象第一次申请内存所处的区域。
- Survivor Space (heap): Eden Space的对象被垃圾收集一次,移动到Survivor Space。
- Tenured Generation (heap): 在Survivor中存在了一段时间后,被移动到老年代。
- Permanent Generation (non-heap): 方法区,存储着类信息、常量、静态变量、编译器编译后的代码等数据。
三、jvm参数设置
- -Xmx:最大堆大小
- -Xms:初始堆大小,即堆的最小内存
- -Xmn:年轻代大小
- -Xss:设置每个线程的堆栈大小
- -XX:NewRatio:设置年轻代(包括Eden和两个Survivor区)与年老代的比值
- -XXSurvivorRatio:年轻代中Eden区与Survivor区的大小比值(eden:from/to,eden跟from或to单个的比值,如3,表示3:1:1)
- -XX:MaxNewSize:年轻代大小
- -XX:MaxPermSize:永久代大小
- -XX:MaxTenuringThreshold:设置对象从年轻代进入老年代的最大年龄
堆空间内存分配(默认情况下)
- 老年代 : 三分之二的堆空间
- 年轻代 : 三分之一的堆空间
eden区: 8/10 的年轻代空间
survivor0 : 1/10 的年轻代空间
survivor1 : 1/10 的年轻代空间
-server -Xms1800m -Xmx1800m -Xmn680m -Xss256k -XX:MetaspaceSize=340m -XX:MaxMetaspaceSize=340m -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:CMSFullGCsBeforeCompaction=5 -XX:+UseCMSCompactAtFullCollection -XX:+CMSClassUnloadingEnabled -XX:+DisableExplicitGC -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Dcom.sun.management.jmxremote.port=9981 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/home/admin/logs
四、内存溢出、内存泄漏
- 基本概念
- 内存溢出:
简单地说内存溢出就是指程序运行过程中申请的内存大于系统能够提供的内存,没有足够的内存空间供其使用,导致无法申请到足够的内存,于是就发生了内存溢出,出现out of memory;- 内存泄漏:
内存泄漏指程序运行过程中分配内存给临时变量,用完之后却没有被GC回收,始终占用着内存,既不能被使用也不能分配给其他程序,于是就发生了内存泄漏,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。- memory leak会最终会导致out of memory!
- 内存溢出的常见情况
- 1、java.lang.OutOfMemoryError: PermGen space (持久带溢出)
我们知道jvm通过持久带实现了java虚拟机规范中的方法区,而运行时常量池就是保存在方法区中的,因此发生这种溢出可能是运行时常量池溢出,或是由于程序中使用了大量的jar或class,使得方法区中保存的class对象没有被及时回收或者class信息占用的内存超过了配置的大小。- 2、java.lang.OutOfMemoryError: Java heap space (堆溢出)
发生这种溢出的原因一般是创建的对象太多,在进行垃圾回收之前对象数量达到了最大堆的容量限制。
解决这个区域异常的方法一般是通过内存映像分析工具对Dump出来的堆转储快照进行分析,看到底是内存溢出还是内存泄漏。如果是内存泄漏,可进一步通过工具查看泄漏对象到GC Roots的引用链,定位出泄漏代码的位置,修改程序或算法;如果不存在泄漏,就是说内存中的对象确实都还必须存活,那就应该检查虚拟机的堆参数-Xmx(最大堆大小)和-Xms(初始堆大小),与机器物理内存对比看是否可以调大。- 3、虚拟机栈和本地方法栈溢出
如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError。
如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError。
- 三、内存泄漏
1、常发性内存泄漏。发生内存泄漏的代码会被多次执行到,每次被执行的时候都会导致一块内存泄漏。
2、偶发性内存泄漏。发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生。常发性和偶发性是相对的。对于特定的环境,偶发性的也许就变成了常发性的。所以测试环境和测试方法对检测内存泄漏至关重要。
3、一次性内存泄漏。发生内存泄漏的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块仅且一块内存发生泄漏。比如,在类的构造函数中分配内存,在析构函数中却没有释放该内存,所以内存泄漏只会发生一次。
4、隐式内存泄漏。程序在运行过程中不停的分配内存,但是直到结束的时候才释放内存。严格的说这里并没有发生内存泄漏,因为最终程序释放了所有申请的内存。但是对于一个服务器程序,需要运行几天,几周甚至几个月,不及时释放内存也可能导致最终耗尽系统的所有内存。所以,我们称这类内存泄漏为隐式内存泄漏。
-
避免内存泄漏的几点建议:
1、尽早释放无用对象的引用。
2、避免在循环中创建对象。
3、使用字符串处理时避免使用String,应使用StringBuffer。
4、尽量少使用静态变量,因为静态变量存放在永久代,基本不参与垃圾回收。
网友评论