JVM内存(堆和非堆)jdk1.8

  • 前言
  • 查看JVM参数和初始值
  • 堆和非堆内存简介
  • 基本概念
  • 堆内存分配
  • 配置示例
  • linux内存与jvm内存联系
  • 调试工具
  • 在线调试
  • gc日志可视化分析


前言

1.8同1.7比,最大的差别就是:元数据区取代了永久代。元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元数据空间并不在虚拟机中,而是使用本地内存。

查看JVM参数和初始值

java -XX:+PrintFlagsFinal -version
java -XX:+PrintFlagsInitial

堆和非堆内存简介

堆和非堆均是在java虚拟机启动时创建。
Java 虚拟机具有一个堆(Heap),堆是运行时数据区域,所有类实例和数组的内存均从此处分配。JVM8中把运行时常量池、静态变量也移到堆区进行存储。对象的堆内存由垃圾回收器的自动回收。

在JVM中堆之外的内存称为非堆内存(Non-heap memory)。由所有线程共享的方法区。方法区属于非堆内存。它存储每个类结构,如运行时常数池、字段和方法数据,以及方法和构造方法的代码。

基本概念

(堆)Heap分为3个区,Young即新生代(分为Eden区、From Survivor、To Survivor三个区域,默认比例是8:1:1),Old即老生代。
Young保存刚实例化的对象。当该区被填满时,GC会将对象移到Old区。Permanent区则负责保存反射对象。
(非堆)MetaSpace:全称是MetaSpace,即方法区。用于存放Class和Meta信息,Class在被Load的时候被放入该区域。

堆内存分配

类型

默认值

初始分配的堆内存-Xms

物理内存的1/64

最大分配的堆内存-Xmx

物理内存的1/4

-XX:PermSize设置非堆内存初始值(jdk1.7)

物理内存的1/64

XX:MaxPermSize设置最大非堆内存(jdk1.7)

物理内存的1/4

-XX:MetaspaceSize初始元空间大小(jdk1.8)

21M

-XX:MaxMetaspaceSize最大元空间大小(jdk1.8)

没有限制

Java非堆使用率高 非堆内存和堆外内存_Java非堆使用率高

  1. -Xms与-Xmx建议相同,避免每次垃圾回收完成后JVM重新分配内存。
  2. -Xmn/NewSize:年轻代大小.整个堆大小=年轻代大小 + 年老代大小 + 持久代大小.持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。Sun官方推荐配置为整个堆的3/8。
  3. -Xss:设置每个线程的堆栈大小,JDK5以后默认为1M。建议根据程序情况调整该值,如Xss128k,避免发生下列问题:k8s(kuberneters) 节点频繁Node became not ready原因排查

配置示例

吞吐量优先的并行收集器,适用于科学技术和后台处理等
java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:MaxGCPauseMillis=100 -XX:+UseAdaptiveSizePolicy

-XX:+UseParallelGC:选择垃圾收集器为并行收集器.此配置仅对年轻代有效.即上述配置下,年轻代使用并发收集,而年老代仍旧使用串行收集.
-XX:MaxGCPauseMillis=100:设置每次年轻代垃圾回收的最长时间,如果无法满足此时间,JVM会自动调整年轻代大小,以满足此值.
-XX:+UseAdaptiveSizePolicy:设置此选项后,并行收集器会自动选择年轻代区大小和相应的Survivor区比例,以达到目标系统规定的最低相应时间或者收集频率等,此值建议使用并行收集器时,一直打开.

响应时间优先的并发收集器
并发收集器主要是保证系统的响应时间,减少垃圾收集时的停顿时间.适用于应用服务器,电信领域等.
典型配置:
java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseConcMarkSweepGC -XX:CMSFullGCsBeforeCompaction=5 -XX:+UseCMSCompactAtFullCollection

-XX:+UseConcMarkSweepGC:设置年老代为并发收集.
-XX:CMSFullGCsBeforeCompaction:由于并发收集器不对内存空间进行压缩,整理,所以运行一段时间以后会产生"碎片",使得运行效率降低.此值设置运行多少次GC以后对内存空间进行压缩,整理.
-XX:+UseCMSCompactAtFullCollection:打开对年老代的压缩.可能会影响性能,但是可以消除碎片

linux内存与jvm内存联系

我们经常会碰到这样一种情况,使用dashboard或者jmap查看到heap+noheap内存与top看到的内存大小不同。

RES(Resident Set Size)是常驻内存的意思,进程实际使用的物理内存

Java非堆使用率高 非堆内存和堆外内存_堆内存_02


Java非堆使用率高 非堆内存和堆外内存_堆内存_03


通过上图我们可以看到,RES占用了1.8g,heap+noheap占用了1.3g那么其余的0.6G内存去哪了呢?

当Java程序启动后,会根据Xmx为堆预申请一块保留内存,并不会直接使用,也不会占用物理内存
然后申请(malloc之类的方法)Xms大小的虚拟内存,但是由于操作系统的内存管理是惰性的,有一个内存延迟分配的概念。malloc虽然会分配内存地址空间,但是并没有映射到实际的物理内存,只有当对该地址空间赋值时,才会真正的占用物理内存,才会影响RES的大小。

所以可能会出现进程所用内存大于当前堆+非堆的情况

比如说该Java程序在5分钟前,有一定活动,占用了1.8G堆内存(无论堆中的什么代),经过GC之后,虽然堆内存已经被回收了,堆占用很低,但GC的回收只是针对Jvm申请的这块内存区域,并不会调用操作系统释放内存。所以该进程的内存并不会释放,这时就会出现进程内存远远大于堆+非堆的情况。

至于Oracle文档上说的,Jvm可能会向操作系统释放内存,经过测试没有发现释放的情况。不过就算有主动释放的情况,也不太需要我们程序关心了。

调试工具

在线调试

arthas调试工具
https://alibaba.github.io/arthas/dashboard.html

查看堆内存
jmap -heap PID

查看一个进程有哪些内存映射
pmap -x PID

gc日志可视化分析

http://gceasy.io/

‐XX:+PrintGC 输出GC日志
‐XX:+PrintGCDetails 输出GC的详细日志
‐XX:+PrintGCTimeStamps 输出GC的时间戳(以基准时间的形式)
‐XX:+PrintGCDateStamps 输出GC的时间戳(以日期的形式,如 2013‐05‐ 04T21:53:59.234+0800)
‐XX:+PrintHeapAtGC 在进行GC的前后打印出堆的信息
‐Xloggc:../logs/gc.log 日志文件的输出路径

示例

使用两个gc日志文件,每个文件最大为1k
-XX:+PrintGCDetails  -XX:+PrintGCDateStamps  -Xloggc:gc-%t.log  -XX:+UseGCLogFileRotation  -XX:NumberOfGCLogFiles=2  -XX:GCLogFileSize=1K

参考文章:
JVM 老年代内存没办法回收了