一文带你看懂jvm虚拟机内存管理

运行时数据区

  • 程序计数器(线程私有)
    • 一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。如果线程正在执行的是一个java方法,记录的是正在执行的虚拟机字节码指令的地址;如果执行的是native方法,这个计数器值则为空。注意:此内存区域是唯一 一个在java虚拟机规范中没有规定任何OutOfMemoryError情况的区域
  • java虚拟机栈(线程私有)
    • 虚拟机栈描述的是java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧(stack frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
    • 局部变量表存放了编译期可知的各种基本数据类型、对象引用和returnAddress类型(指向了一条字节码指令的地址) 注意:其中64位长度的long和double类型的数据会占用2个局部变量空间(slot),其余数据只占用一个。
    • 栈区域的两种异常:StackOverflowError 和 OutOfMemoryError异常
  • 本地方法栈(线程私有)
    • 本地方法栈是虚拟机使用到的native方法服务
  • java堆
    • 堆是虚拟机管理的内存中最大的一块,java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建
    • java堆细分:新生代和老年代。细致一点Eden空间、from survivor空间、To survivor空间等。从内存分配的角度来看,线程共享的java堆中可能划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB)
    • -Xmx和-Xms设置虚拟机堆内存是否可扩展
  • 方法区(别名Non-Heap 非堆)
    • 用于存储已被虚拟机加载的类信息、常量、静态变量、及时编译器编译后的代码等数据表。
    • 运行时常量池是方法区的一部分。class文件中除了有类的版本、字段、方法、接口等描述信息,还有一项信息是常量池,用于存放编译器生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。
  • 直接内存
    • 在jdk1.4中新加入了nio类,引入了一种基于通道与缓冲区的i/o方式,它可以使用native函数库直接分配堆外内存,然后通过一个存储在java堆中directByteBuffer对象作为这块内存的引用进行操作。这样可以在一些场景中显著提高性能,因为避免了在java堆和native堆中来回复制数据。
    • 本机直接内存的分配不受java堆大小的限制,但是如果在设置 -Xmx等参数信息时,忽略掉直接内存,可能导致总的内存大于了物理内存限制,从而导致动态扩展时出现OutOfMemoryError异常。

几种常见的OutOfMemoryError

  • java堆溢出
    • java.lang.OutOfMemoryError : java heap space
    • 当出现堆溢出时一般手段是先通过内存映像分析工具(如:eclipse memory analyzer)对dump出来的堆转存快照进行分析。首先要清楚是内存泄漏还是内存溢出
  • 虚拟机栈和本地方法栈溢出
    • 栈容量只由 -Xss 参数设定
    • 关于虚拟机栈和本地方法栈,在java虚拟机规范中描述了两种异常:
      • 如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowerror
      • 如果虚拟机在扩展栈时无法申请到足够的空间,则抛出OutOfMemoryError
  • 方法区和运行时常量池溢出
    • VM Args:-XX:PermSize = 10M -XX:MaxPermSize=10M
    • java.lang.OutOfMemoryError : PermGen space
  • 本机直接内存溢出
    • DirectMemory容量可以通过-XX : MaxDirectMemorySize指定,如果不指定,则默认与java堆最大值一样
    • 由directMemory导致的内存溢出,一个明显的特征是在Heap Dump文件中不会看见明显的异常,如果发现OOM之后dump文件很小,而程序中又直接或间接使用了NIO,则可以考虑是不是这方面问题

内存分配和回收策略

对象优先在Eden分配

大多数情况,对象在新生代Eden去分配。当Eden区没有足够空间进行分配时,虚拟机将发起一次Minor GC。

大对象直接进入老年代

所谓大对象是指—需要大量连续内存空间的java对象(最典型的大对象就是那种很长的字符串以及数组)。虚拟机提供了一个 -XX:PretenureSizeThreshold参数,令大于这个设置值的对象直接在老年代分配。这样做的目的是避免在Eden区及两个survivor区之间发生大量的内存复制。

长期存活的对象将进入老年代

在Eden中新生的对象,经历第一次Minor GC后存在,且能被survivor容纳,将被移动到survivor中,并年龄设为1。每经过一次,年龄+1,直到达到阈值(默认15,可以通过-XX:MaxTenuringThreshold设置)

动态年龄判断

如果survivor空间中相同年龄所有对象大小总和大于survivor空间的一半,年龄大于或者等于该年龄的对象直接进入老年代,无需等年龄到达阈值。

垃圾收集算法

  • 标记-清除算法
  • 复制算法
  • 标记-整理算法
  • 分代收集算法

虚拟机性能监控与故障处理工具

jdk的命令行工具
  • jps:虚拟机进程状况工具
    • 可以列出正在运行的虚拟机进程,并显示虚拟机执行主类(Main Class,main()函数所在的类)名称以及这些进程的本地虚拟机唯一id(Local Virtual Machine Identifier , LVMID)
    • -q : 只输出LVMID,省略主类的名称
    • -m : 输出虚拟机进程启动时传递给主类main()函数的参数
    • -l : 输出主类的全名,如果进程执行的是jar包,输出jar的路径
    • -v : 输出虚拟机启动时jvm参数
  • jstat:虚拟机统计信息监视工具
    • JVM Statistics Monitoring Tool是用于监视虚拟机各种运行状态信息的命令行工具,他可以显示本地或者远程虚拟机进程中的类转载、内存、垃圾收集、jit编译等运行数据,在服务器上它是运行期定位虚拟机性能问题的首选工具
    • -gc : 监视java堆,包括Eden区,两个survivor区,老年代、永久代等的容量、已用空间、gc时间合计等信息
    • -class : 监视类装载、卸载数量、总空间以及类装载所耗费的时间
    • -gcutil : 监视内容与-gc基本相同,但输出主要关注已使用空间占总空间的百分比
  • jinfo:java配置信息工具
    • Configuration Info for Java 的作用是实时地查看和调整虚拟机各项参数。
  • jmap:java内存映射工具
    • Memory Map for Java命令用于生成堆转储快照(一般称为heapdump或dump文件)。
    • -dump:生成java堆转储快照
      • -dump:[live,]format=b,file= live子参数说明是否只dump出存活的对象
    • -heap:显示java堆详细信息,如使用哪种回收器、参数配置、分代状况等
    • -histo:显示堆中对象统计信息,包括类、实例数量、合计容量
    • -F:当虚拟机进程对-dump选项没有响应时,可使用这个选项强制生成dump快照。
  • jhat:虚拟机堆转储快照分析工具
    • sun JDK提供了jhat(JVM Heap Analysis Tool)命令与jmap搭配使用,来分析生成的堆转储快照。jhat内置了一个微型的HTTP/HTML服务器,生成dump文件的分析结果后,可以在浏览器查看。
    • 注意:一般不要在生产服务器上用该命令分析dump文件
      • 分析工具是一个耗时而且消耗硬件资源的过程
      • jhat的分析功能相对来说比较简陋
  • jstack:java堆栈跟踪工具
    • Stack Trace for Java 命令用于生成虚拟机当前时刻的线程快照(一般称为threaddump或者Javacore文件)
    • -l :除堆栈外,显示关于锁的附加信息
    • -m :如果调用本地方法的话,可以显示C/C++的堆栈
    • -F :当正常输出的请求不被响应时,强制输出线程堆栈
发布了8 篇原创文章 · 获赞 1 · 访问量 263

猜你喜欢

转载自blog.csdn.net/qq_40635011/article/details/105413114