JVM内存区域
按照官方的说法:
Java 虚拟机具有一个堆,堆是运行时数据区域,所有类实例和数组的内存均从此处分配。堆是在 Java 虚拟机启动时创建的。
在JVM中堆之外的内存称为非堆内存(Non-heap memory)。
可以看出JVM主要管理两种类型的内存:堆和非堆。
简单来说堆就是Java代码可及的内存,是留给运行时使用的;非堆就是JVM留给自己用的, 所以方法区、JVM内部处理或优化所需的内存(如JIT编译后的代码缓存)、每个类结构(如运行时常数池、字段和方法数据)以及方法和构造方法的代码都在非堆内存中。
从上面的图可以看出, JVM区域总体分两类,heap区和非heap区。
heap区又分为:
- Eden Space(伊甸园)
- Survivor Space(幸存者区)
- Old Gen(老年代)
非heap区又分:
- Code Cache(代码缓存区)
- Perm Gen(永久代)
- Jvm Stack(java虚拟机栈)
- Local Method Statck(本地方法栈)
堆分布
Java进程运行过程中创建的对象存放在堆中,堆被划分成两个不同的区域:新生代 ( Young )、老年代 ( Old )。
新生代 ( Young ) 又被划分为三个区域:Eden、From Survivor、To Survivor。
- 新生代 (Eden空间,From Survivor空间,To Survivor空间)
- 老年代
堆的内存模型大致为:
默认的:
代码语言:javascript复制NewRatio = 2 //老生代占2份 1:2
SurvivorRatio = 8 //Eden占8份 也就是 Edem : from : to = 8 : 1 : 1
新生代 ( Young ) 与老年代 ( Old ) 的比例的值为 1:2 ( 该值可以通过参数 –XX:NewRatio 来指定 ),即:新生代 ( Young ) = 1/3 的堆空间大小。 老年代 ( Old ) = 2/3 的堆空间大小。
其中,新生代 ( Young ) 被细分为 Eden 和 两个 Survivor 区域,这两个 Survivor 区域分别被命名为 from 和 to,以示区分。 默认的,Edem : from : to = 8 : 1 : 1 ( 可以通过参数 –XX:SurvivorRatio 来设定 ),即: Eden = 8/10 的新生代空间大小,from = to = 1/10 的新生代空间大小。
JVM 每次只会使用 Eden 和其中的一块 Survivor 区域来为对象服务,所以无论什么时候,总是有一块 Survivor 区域是空闲着的。 因此,新生代实际可用的内存空间为 9/10 ( 即90% )的新生代空间。
新生代是 GC 收集垃圾的频繁区域。 当对象在 Eden ( 包括一个 Survivor 区域,这里假设是 from 区域 ) 出生后,在经过一次 Minor GC 后,如果对象还存活,并且能够被另外一块 Survivor 区域所容纳( 上面已经假设为 from 区域,这里应为 to 区域,即 to 区域有足够的内存空间来存储 Eden 和 from 区域中存活的对象 ),则使用复制算法将这些仍然还存活的对象复制到另外一块 Survivor 区域 ( 即 to 区域 ) 中,然后清理所使用过的 Eden 以及 Survivor 区域 ( 即 from 区域 ),并且将这些对象的年龄设置为1,以后对象在 Survivor 区每熬过一次 Minor GC,就将对象的年龄 1,当对象的年龄达到某个值时 ( 默认是 15 岁,可以通过参数 -XX:MaxTenuringThreshold 来设定 ),这些对象就会成为老年代。 但这也不是一定的,对于一些较大的对象 ( 即需要分配一块较大的连续内存空间 ) 则是直接进入到老年代。
From Survivor区域与To Survivor区域是交替切换空间,在同一时间内两者中会有一个为空。
常用命令
代码语言:javascript复制lsof -i:8902 ###查看端口8902对应应用的pid
jstat -class PID ###类加载统计
jstat -compiler PID ###编译统计
jstat -gc PID ###垃圾回收统计
jstat -gccapacity PID ### 堆内存统计
jstat -gcutil PID ###查看堆比例
jstat -gccause PID ###GC的原因
jstat -gcnew PID ###新生代垃圾回收统计
jstat -gcnewcapacity PID ###新生代内存统计
jstat -gcold PID ###老年代垃圾回收统计
jstat -gcoldcapcacity PID ###老年代内存统计
jstat -gcmetacapacity PID ###元数据空间统计
jstat -printcompilation PID ###JVM编译方法统计
jmap -histo PID ###查看类的实例
jmap -heap PID ###查看堆栈信息
jmap -dump:live,format=b,file=/tmp/m.hprof PID ###保存内存的堆栈为文件
jhat -J-Xmx2048m -port 5000 /tmp/m.hprof ###在线查看堆文件的类,速度比较慢
jcmd PID GC.run ###强制gc
查看内存占用
查看pid
代码语言:javascript复制lsof -i:8902
查看内存占用
代码语言:javascript复制jmap -heap pid
结果
Attaching to process ID 19438, please wait… Debugger attached successfully. Server compiler detected. JVM version is 25.221-b11 using thread-local object allocation. Parallel GC with 2 thread(s) Heap Configuration: MinHeapFreeRatio = 0 MaxHeapFreeRatio = 100 MaxHeapSize = 2147483648 (2048.0MB) NewSize = 89128960 (85.0MB) MaxNewSize = 715653120 (682.5MB) OldSize = 179306496 (171.0MB) NewRatio = 2 SurvivorRatio = 8 MetaspaceSize = 21807104 (20.796875MB) CompressedClassSpaceSize = 1073741824 (1024.0MB) MaxMetaspaceSize = 17592186044415 MB G1HeapRegionSize = 0 (0.0MB) Heap Usage: PS Young Generation Eden Space: capacity = 651689984 (621.5MB) used = 405279136 (386.5043029785156MB) free = 246410848 (234.99569702148438MB) 62.18894657739592% used From Space: capacity = 5242880 (5.0MB) used = 4774176 (4.553009033203125MB) free = 468704 (0.446990966796875MB) 91.0601806640625% used To Space: capacity = 14680064 (14.0MB) used = 0 (0.0MB) free = 14680064 (14.0MB) 0.0% used PS Old Generation capacity = 252182528 (240.5MB) used = 38105624 (36.340354919433594MB) free = 214076904 (204.1596450805664MB) 15.110334685835173% used 27080 interned Strings occupying 2811288 bytes.
查看某个进程的对象占用对象最大的命令:
代码语言:javascript复制jmap -histo pid | head -n 20
查看进程的可用内存占用百分比
代码语言:javascript复制jstat -gcutil pid
结果
S0 | S1 | E | O | M | CCS | YGC | YGCT | FGC | FGCT | GCT |
---|---|---|---|---|---|---|---|---|---|---|
88.75 | 0.00 | 90.80 | 17.78 | 91.23 | 87.83 | 82 | 0.805 | 3 | 0.492 | 1.297 |
- S0: From Survivor
- S1: To Survivor
- E: Eden
- O: Old generation
- M: 的值为
91.23
,即Meta区使用率也达到了91.23%
配置优化
查看JDK版本
代码语言:javascript复制java -version
推荐配置(JDK8)
-XX:MetaspaceSize=128m (元空间默认大小) -XX:MaxMetaspaceSize=128m (元空间最大大小) -Xms1024m (jvm启动时分配的内存) -Xmx1024m (jvm运行过程中分配的最大内存) -Xmn256m (新生代大小) -Xss256k (jvm启动的每个线程分配的内存大小,默认JDK1.4中是256K,JDK1.5 中是1M) -XX:SurvivorRatio=8 (新生代分区比例 8:2 默认就是这个比例) -XX: UseConcMarkSweepGC (指定使用的垃圾收集器,这里使用CMS收集器) -XX: PrintGCDetails (打印详细的GC日志)
注意点:
JDK8之后把-XX:PermSize 和 -XX:MaxPermSize移除了,取而代之的是 -XX:MetaspaceSize=128m (元空间默认大小) -XX:MaxMetaspaceSize=128m (元空间最大大小) JDK 8开始把类的元数据放到本地化的堆内存(native heap)中,这一块区域就叫Metaspace,中文名叫元空间。 使用本地化的内存有什么好处呢?最直接的表现就是java.lang.OutOfMemoryError: PermGen 空间问题将不复存在,因为默认的类的元数据分配只受本地内存大小的限制,也就是说本地内存剩余多少,理论上Metaspace就可以有多大(貌似容量还与操作系统的虚拟内存有关?这里不太清楚),这解决了空间不足的问题。不过,让Metaspace变得无限大显然是不现实的,因此我们也要限制Metaspace的大小:使用-XX:MaxMetaspaceSize参数来指定Metaspace区域的大小。JVM默认在运行时根据需要动态地设置MaxMetaspaceSize的大小。
启动(JDK7及以下)
代码语言:javascript复制nohup java -Dfile.encoding=utf-8 -jar -Xms256m -Xmx2048m -XX:PermSize=128M -XX:MaxPermSize=256M /data/123.jar >/data/log.txt &
启动(JDK8及以上)
代码语言:javascript复制nohup java -Dfile.encoding=utf-8 -jar -Xms256m -Xmx2048m -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=256m /data/123.jar >/data/log.txt &
示例
代码语言:javascript复制java -Xmx3550m -Xms3550m -Xss128k -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MaxPermSize=16m -XX:MaxTenuringThreshold=0
- -XX:NewRatio=4 :设置新生代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)。设置为4,则新生代与年老代所占比值为1:4,新生代占整个堆栈的1/5
- -XX:SurvivorRatio=4 :设置新生代中Eden区与Survivor区的大小比值。设置为4,则两个Survivor区与一个Eden区的比值为2:4,一个Survivor区占整个新生代的1/6
- -XX:MaxPermSize=16m :设置持久代大小为16m。
- -XX:MaxTenuringThreshold=0 :设置垃圾最大年龄。如果设置为0的话,则新生代对象不经过Survivor区,直接进入年老代 。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则新生代对象会在Survivor区进行多次复制,这样可以增加对象再新生代的存活时间 ,增加在新生代即被回收的概论。
配置详解
- 堆设置
- -Xms : 初始堆大小;
- -Xmx : 最大堆大小;
- -XX:MaxnewSize: 表示新生代可被分配的内存的最大上限;当然这个值应该小于 -Xmx的值;
- -XX:NewRatio=3: 设置新生代和年老代的比值。 如:为3,表示新生代与年老代比值为1:3,新生代占整个新生代年老代和的1/4;
- -XX:SurvivorRatio=3 : 新生代中Eden区与两个Survivor区的比值。注意Survivor区有两个。 如:3,表示Eden:Survivor=3:2,一个Survivor区占整个新生代的1/5;
- -XX:NewSize=n : 设置新生代大小,应该小于 -Xms的值;
- -Xmn256m: 至于这个参数则是对新生代
-XX:newSize
、-XX:MaxnewSize
两个参数的同时配置, 也就是说如果通过-Xmn来配置新生代的内存大小,那么XX:newSize = XX:MaxnewSize = Xmn
,虽然会很方便,但需要注意的是这个参数是在JDK1.4版本以后才使用的。 - -Xss256k: jvm启动的每个线程分配的内存大小,默认JDK1.4中是256K,JDK1.5 中是1M
- 非堆设置
JDK7及以前
- -XX:PermSize=128M 表示非堆区初始内存分配大小,其缩写为permanent size(持久化内存)
- -XX:MaxPermSize=256M 表示非堆区最大内存分配大小
JDK8及以后
- -XX:MetaspaceSize=128m (元空间默认大小)
- -XX:MaxMetaspaceSize=128m (元空间最大大小)
建议
MetaspaceSize
和MaxMetaspaceSize
设置一样大;- 具体设置多大,建议稳定运行一段时间后通过
jstat -gc pid
确认且这个值大一些,对于大部分项目256m即可。
- 收集器设置
- -XX: UseSerialGC :设置串行收集器
- -XX: UseParallelGC :设置并行收集器
- -XX: UseParalledlOldGC :设置并行年老代收集器
- -XX: UseConcMarkSweepGC :设置并发收集器
- 垃圾回收统计信息
- -XX: PrintGC
- -XX: PrintGCDetails
- -XX: PrintGCTimeStamps
- -Xloggc:filename
- 并行收集器设置
- -XX:ParallelGCThreads=n :设置并行收集器收集时使用的CPU数。并行收集线程数。
- -XX:MaxGCPauseMillis=n :设置并行收集最大暂停时间
- -XX:GCTimeRatio=n :设置垃圾回收时间占程序运行时间的百分比。公式为1/(1 n)
- 并发收集器设置
- -XX: CMSIncrementalMode :设置为增量模式。适用于单CPU情况。
- -XX:ParallelGCThreads=n :设置并发收集器新生代收集方式为并行收集时,使用的CPU数。并行收集线程数。