根据 JVM 规范,JVM 运行时内存共分为虚拟机栈、堆、元空间、程序计数器、本地方法栈五个部分。还有一部分内存叫直接内存,属于操作系统的本地内存,也是可以直接操作的。
线程私有:程序计数器,虚拟机栈,本地方法栈。
线程共享:堆、元空间、直接内存
元空间(Metaspace),JDK 8 之前 HotSpot 虚拟机使用永久代来实现的方法区(方便内存管理), JDK 8 废弃了永久代,将原来永久代的字符串常量池、静态变量、类型信息等全部移到了元空间中。元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。
虚拟机栈(JVM Stacks),每个线程有一个私有的栈,随着线程的创建而创建。栈里面存着的是一种叫“栈帧”的东西,每个方法会创建一个栈帧,栈帧中存放了局部变量表(基本数据类型和对象引用)、操作数栈、方法出口等信息。栈的大小可以固定也可以动态扩展。
本地方法栈(Native Method Stack),与虚拟机栈类似,区别是虚拟机栈执行java方法,本地方法站执行native方法。在虚拟机规范中对本地方法栈中方法使用的语言、使用方法与数据结构没有强制规定,因此虚拟机可以自由实现它。
程序计数器(Program Counter Register),程序计数器可以看成是当前线程所执行的字节码的行号指示器。在任何一个确定的时刻,一个处理器(对于多内核来说是一个内核)都只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要一个独立的程序计数器,我们称这类内存区域为“线程私有”内存。
堆内存(Heap),堆内存是 JVM 所有线程共享的部分,在虚拟机启动的时候就已经创建。所有的对象和数组都在堆上进行分配。这部分空间可通过 GC 进行回收。当申请不到空间时会抛出 OutOfMemoryError。堆是JVM内存占用最大,管理最复杂的一个区域。其唯一的用途就是存放对象实例:所有的对象实例及数组都在堆上进行分配。jdk1.8后,字符串常量池从永久代中剥离出来,存放在堆中。
直接内存(Direct Memory),直接内存并不是虚拟机运行时数据区的一部分,也不是Java 虚拟机规范中农定义的内存区域。在JDK1.4 中新加入了NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O 方式,它可以使用native 函数库直接分配堆外内存,然后通脱一个存储在Java堆中的DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在Java堆和Native堆中来回复制数据。
引用的概念
垃圾收集算法 - 分代收集理论
https://juejin.im/post/5e197cc0e51d451c774dc56f
Java8 GC 默认使用的是 Parallel Scavenge (新生代) 和 Parallel Old (老年代)。
在启动命令中增加 -XX:+PrintGCDetails
输出详细GC日志。
/** * VM Args: -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 * 堆内存溢出 */ public class JvmDemo1 { public static void main(String[] args) { Random random = new Random(); List<Long> list = new ArrayList<>(); while (true) { list.add(random.nextLong()); } } }
[GC (Allocation Failure) [PSYoungGen: 8097K->1008K(9216K)] 11954K->10351K(19456K), 0.0091685 secs] [Times: user=0.02 sys=0.01, real=0.01 secs] [Full GC (Ergonomics) [PSYoungGen: 1008K->495K(9216K)] [ParOldGen: 9343K->9793K(10240K)] 10351K->10288K(19456K), [Metaspace: 3224K->3224K(1056768K)], 0.1013312 secs] [Times: user=0.16 sys=0.00, real=0.10 secs]
GC:表明进行了一次垃圾回收,前面没有Full修饰,表明这是一次Minor GC ,注意它不表示只GC新生代,并且现有的不管是新生代还是老年代都会STW。 Allocation Failure:表明本次引起GC的原因是因为在年轻代中没有足够的空间能够存储新的数据了。
默认元空间大小128M,最大元空间大小256M,初始化堆大小2G,最大堆大小5G,新生代512M,每个线程分配内存大小1M。eden空间和survivor空间的分配比率8:2,使用标记复制算法。
root@xxx:/usr/lib/jvm/java-8-oracle/bin# ./jps -v 1 xxx.jar -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=256m -Xms2048m -Xmx5048m -Xmn512m -Xss1024k -XX:SurvivorRatio=8 -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails
format 指定输出格式,live 指明是活着的对象,file 指定文件名。方便后面通过分析工具分析。
jmap -dump:live,format=b,file=dump.hprof pid
root@xxx:/usr/lib/jvm/java-8-oracle/bin# ./jstat -gcutil 1 S0 S1 E O M CCS YGC YGCT FGC FGCT GCT 0.00 72.37 40.12 3.59 96.52 94.68 113 4.433 0 0.000 4.433
S0 survivo0
S1 survivo1
E Eden空间
O 老年代
M 元空间使用率
CCS 压缩使用比例
YGC 新生代 GC 次数
YGCT 新生代 GC 耗时
FGC Full GC 次数
FGCT Full GC 耗时
GCT GC 耗时
jmap -heap pid
Attaching to process ID 3764, please wait... Debugger attached successfully. Server compiler detected. JVM version is 25.171-b11 using thread-local object allocation. Parallel GC with 8 thread(s) //采用Parallel GC Heap Configuration: MinHeapFreeRatio = 0 //JVM最小空闲比率 可由-XX:MinHeapFreeRatio=<n>参数设置, jvm heap 在使用率小于 n 时 ,heap 进行收缩 MaxHeapFreeRatio = 100 //JVM最大空闲比率 可由-XX:MaxHeapFreeRatio=<n>参数设置, jvm heap 在使用率大于 n 时 ,heap 进行扩张 MaxHeapSize = 2095054848 (1998.0MB) //JVM堆的最大大小 可由-XX:MaxHeapSize=<n>参数设置 NewSize = 44040192 (42.0MB) //JVM新生代的默认大小 可由-XX:NewSize=<n>参数设置 MaxNewSize = 698351616 (666.0MB) //JVM新生代的最大大小 可由-XX:MaxNewSize=<n>参数设置 OldSize = 88080384 (84.0MB) //JVM老生代的默认大小 可由-XX:OldSize=<n>参数设置 NewRatio = 2 //新生代:老生代(的大小)=1:2 可由-XX:NewRatio=<n>参数指定New Generation与Old Generation heap size的比例。 SurvivorRatio = 8 //survivor:eden = 1:8,即survivor space是新生代大小的1/(8+2)[因为有两个survivor区域] 可由-XX:SurvivorRatio=<n>参数设置 MetaspaceSize = 21807104 (20.796875MB) //元空间的默认大小,超过此值就会触发Full GC 可由-XX:MetaspaceSize=<n>参数设置 CompressedClassSpaceSize = 1073741824 (1024.0MB) //类指针压缩空间的默认大小 可由-XX:CompressedClassSpaceSize=<n>参数设置 MaxMetaspaceSize = 17592186044415 MB //元空间的最大大小 可由-XX:MaxMetaspaceSize=<n>参数设置 G1HeapRegionSize = 0 (0.0MB) //使用G1垃圾收集器的时候,堆被分割的大小 可由-XX:G1HeapRegionSize=<n>参数设置 Heap Usage: PS Young Generation //新生代区域分配情况 Eden Space: //Eden区域分配情况 capacity = 89653248 (85.5MB) used = 8946488 (8.532035827636719MB) free = 80706760 (76.96796417236328MB) 9.978989272089729% used From Space: //其中一个Survivor区域分配情况 capacity = 42467328 (40.5MB) used = 15497496 (14.779563903808594MB) free = 26969832 (25.720436096191406MB) 36.49275037977431% used To Space: //另一个Survivor区域分配情况 capacity = 42991616 (41.0MB) used = 0 (0.0MB) free = 42991616 (41.0MB) 0.0% used PS Old Generation //老生代区域分配情况 capacity = 154664960 (147.5MB) used = 98556712 (93.99100494384766MB) free = 56108248 (53.508995056152344MB) 63.722715216167906% used 1819 interned Strings occupying 163384 bytes.
目的:对JVM内存的系统级的调优主要的目的是减少GC的频率和Full GC的次数,减少系统停顿时间。
步骤: