网络知识 娱乐 JDK8内存结构

JDK8内存结构

JDK8的内存结构主要包括程序计数器(Program Counter Register)、虚拟机栈(Java Virtual Machine Stacks)、本地方法栈(Native Method Stacks)、(Java Heap)、元空间(Metaspace)。

其中又被划分为老年代(Old Generation)、年轻代(Young Generation),其中年轻代又被划分为一个Eden区和两个Survivor区

一边说着,一边拿起笔在纸上画了起来:

JDK8内存结构

画完以后,我又说:JDK8的内存调优主要针对的是堆和元空间。内存调优时常用到JVM参数有这些:

-server

JVM的server模式, 在多CPU服务器中性能可以得到更好地发挥。JDK的64位版本只支持server模式,因此在这种情况下,选项是隐式的。

-Xmx

指定堆所分配内存的最大值,等同于-XX:MaxHeapSize。不附加字母时,单位为byte,必须是1024的倍数,并且大于2MB;附加字母k或K时,表示单位为KB;附加字母m或M时,表示单位为MB;附加字母g或G时,表示单位为G。

下面的例子是使用不同的单位把堆所分配内存的最大值设置为1GB:

-Xmx1Gn-Xmx1024Mn-Xmx1048576Kn-Xmx1073741824n

-Xms

指定堆所分配内存的初始值,不附加字母时,单位为byte,必须是1024的倍数,并且大于1MB;附加字母k或K时,表示单位为KB;附加字母m或M时,表示单位为MB;附加字母g或G时,表示单位为G。如果不设置这个初始值,那么初始值将被设置为老年代和年轻代分配内存的大小的总和。

下面的例子是使用不同的单位把堆所分配的初始值设置为4GB:

-Xms4Gn-Xms4096Mn-Xms4194304Kn-Xms4294967296n

对于生产环境的部署,-Xms和-Xmx通常设置为相同的值。

-Xmn

指定堆的年轻代分配内存的初始值和最大值,不附加字母时,单位为byte;附加字母k或K时,表示单位为KB;附加字母m或M时,表示单位为MB;附加字母g或G时,表示单位为G。

堆的年轻代区域用于存放新生对象。与其他区域相比,在这个区域执行垃圾回收的频率更高。如果年轻代的内存太小,那么将执行许多次垃圾回收。如果年轻代的内存太大,那么执行完整的垃圾回收可能需要很长时间才能完成。一般建议把年轻代的大小保持在整个堆大小的1/2到1/4之间。

下面的例子是使用不同的单位把年轻代所分配内存的初始值和最大值设置为2GB:

-Xmn2Gn-Xmn2048Mn-Xmn2097152Kn-Xmn2147483648n

除了使用-Xmn选项设置年轻代的初始值和最大值,还可以使用-XX:NewSize设置年轻代的初始值,使用-XX:MaxNewSize设置年轻代的最大值。

-XX:NewRatio

指定老年代和年轻代空间大小的比率。默认为2,即老年代和年轻代空间大小的比率为2:1,年轻代占整个堆内存空间大小的1/3。下面的例子是把老年代和年轻代空间大小的比率设置为1:

-XX:NewRatio=1n

另外,年轻代分配内存设置的优先级如下:

  1. 高优先级: -XX:NewSize/-XX:MaxNewSize
  2. 中优先级: -Xmn
  3. 低优先级: -XX:NewRatio

-XX:SurvivorRatio

指定Eden区和一个Survivor区的空间大小的比率。默认为8,即Eden区和一个Survivor区的空间大小为8:1,因为一共有两个Survivor区,所以Eden区占年轻代内存大小的80%。下面的例子是把Eden区和一个Survivor区的空间大小的比率设置为4:

-XX:SurvivorRatio=4n

-XX:MetaspaceSize

指定元空间第一次触发垃圾回收的内存大小的阈值。当元空间内存占用不断增大,直到达到这个阈值时,就会触发一次垃圾回收。所以,适当的增大这个阈值,会减少垃圾回收的次数。默认值根据平台而定,一般情况下大约20.8MB。下面的例子是把元空间第一次触发垃圾回收的内存大小设置为256MB:

-XX:MetaspaceSize=256Mn

有一些小伙伴对这个参数有误解,造成不必要的麻烦。重申一下:-XX:MetaspaceSize不是元空间内存大小的初始值,不是元空间内存大小的初始值,不是元空间内存大小的初始值,重要的事情说三遍。

-XX:MaxMetaspaceSize

指定元空间所分配内存的最大值,默认是没有限制,取决于系统的可用内存量,理论上可以占满整个系统的内存。为了避免这种惨剧,影响系统上的其他应用,需要适当设置它的大小。下面的例子是把元空间所分配内存的最大值设置为512MB:

-XX:MaxMetaspaceSize=512Mn

面试官微笑地说:这些常用的内存调优参数总结的不错,可以结合这些参数写一个内存调优实例吗?

被面试官夸奖一下,我按捺住心中的喜悦说:当然可以。

内存调优实例

尽可能把堆内存的空间设置大一些,以减少垃圾回收的次数。假设服务器上的可用内存还有12GB,那么先指定堆所分配内存的最大值和初始值为8GB。一般情况下,年轻代内存大小需在整个堆大小的1/2到1/4之间,那么就指定年轻代内存大小为3GB。再把Eden区和一个Survivor区的空间大小的比率设置为4。元空间第一次触发垃圾回收的内存大小的阈值设置为256MB,一般情况下足够用。元空间所分配内存的最大值设置为512MB,为了避免极端情况下占用大量内存。另外,还需要明确指定JVM以server模式启动。

内存调优的参数基本敲定,用它启动一个名为one-more-study-0.0.1-SNAPSHOT.jar的jar文件:

java -server -Xmx8G -Xms8G -Xmn3G -XX:SurvivorRatio=4 -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=512M -jar one-more-study-0.0.1-SNAPSHOT.jarn

如果执行jmap -heap命令查看对应Java进程的内存配置和使用情况,应该是这样的:

Attaching to process ID 31828, please wait...nDebugger attached successfully.nServer compiler detected.nJVM version is 25.251-b08nnusing thread-local object allocation.nParallel GC with 8 thread(s)nnHeap Configuration: #堆的内存配置n MinHeapFreeRatio = 0n MaxHeapFreeRatio = 100n # 堆内存的最大值n MaxHeapSize = 8589934592 (8192.0MB) n # 年轻代内存的大小n NewSize = 3221225472 (3072.0MB) n # 年轻代内存的最大值n MaxNewSize = 3221225472 (3072.0MB) n # 老年代内存的大小n OldSize = 5368709120 (5120.0MB) n # 老年代和年轻代空间大小的比率n # 因为设置Xmn参数,该设置未生效n NewRatio = 2n #Eden区和一个Survivor区的空间大小的比率n SurvivorRatio = 4 n # 元空间第一次触发垃圾回收的内存大小n MetaspaceSize = 268435456 (256.0MB)n # 元空间内存的最大值n MaxMetaspaceSize = 536870912 (512.0MB)n G1HeapRegionSize = 0 (0.0MB)nnHeap Usage: # 堆的使用情况nPS Young GenerationnEden Space: # Eden区内存的使用情况n capacity = 2147483648 (2048.0MB)n used = 901945720 (860.16MB)n free = 1245537928 (1187.83MB)n 42.000120505690575% usednFrom Space: # Survivor的From区内存的使用情况n capacity = 536870912 (512.0MB)n used = 0 (0.0MB)n free = 536870912 (512.0MB)n 0.0% usednTo Space: # Survivor的To区内存的使用情况n capacity = 536870912 (512.0MB)n used = 0 (0.0MB)n free = 536870912 (512.0MB)n 0.0% usednPS Old Generation # 老年代内存的使用情况n capacity = 5368709120 (5120.0MB)n used = 0 (0.0MB)n free = 5368709120 (5120.0MB)n 0.0% usednn12047 interned Strings occupying 1045744 bytes.n