JVM运行时内存管理之线程共享
接上JVM运行时内存管理之线程私有继续分享JVM运行时内存管理中线程共享部分:
一、堆
对于Java应用程序来说, Java堆(Java Heap) 是虚拟机所管理的内存中最大的一块。 Java堆是被所 有线程共享的一块内存区域, 在虚拟机启动时创建。 此内存区域的唯一目的就是存放对象实例, Java 世界里“几乎”所有的对象实例都在这里分配内存。
1、堆大小设置
内存大小-Xmx/-Xms,使用示例: -Xmx20m -Xms5m
在测试代码中新增如下语句,申请内存分配:
2、堆的分类
JVM中存储java对象可以被分为两类:
1)年轻代(Young Gen):年轻代主要存放新创建的对象,内存大小相对会比较小,垃圾回收会比较频繁。年轻代分成1个Eden Space和2个Suvivor Space(from 和to)。
2)年老代(Tenured Gen):年老代主要存放JVM认为生命周期比较长的对象(经过几次的Young Gen的垃圾回收后仍然存在),内存大小相对会比较大,垃圾回收也相对没有那么频繁。
通过命令jstat(可以参考JVM调优维护之常用命令)可以看到如下图信息:jstat -gc pid
S0C: Current survivor space 0 capacity (KB).
S1C: Current survivor space 1 capacity (KB).
S0U: Survivor space 0 utilization (KB).
S1U: Survivor space 1 utilization (KB).
EC: Current eden space capacity (KB).
EU: Eden space utilization (KB).
OC: Current old space capacity (KB).
OU: Old space utilization (KB).
MC: Metaspace Committed Size (KB).
MU: Metaspace utilization (KB).
CCSC: Compressed class committed size (KB).
CCSU: Compressed class space used (KB).
YGC: Number of young generation garbage collection (GC) events.
YGCT: Young generation garbage collection time.
FGC: Number of full GC events.
FGCT: Full garbage collection time.
GCT: Total garbage collection time.
其中新生代:
老年代:
配置新生代和老年代堆结构占比:
默认 -XX:NewRatio=2 , 标识新生代占1 , 老年代占2 ,新生代占整个堆的1/3
修改占比 -XX:NewPatio=4 , 标识新生代占1 , 老年代占4 , 新生代占整个堆的1/5
Eden空间和另外两个Survivor空间占比分别为8:1:1,可以通过操作选项 -XX:SurvivorRatio 调整这个空间比例。 比如 -XX:SurvivorRatio=8。
几乎所有的java对象都在Eden区创建, 但80%的对象生命周期都很短,创建出来就会被销毁。
3、堆分配——分配对象的流程:
4、堆GC
上面分析堆的分类,总是出现gc。这是因为Java 中的堆也是 GC 收集垃圾的主要区域(参考https://docs.oracle.com/en/graalvm/jdk/17/docs/reference-manual/native-image/optimizations-and-performance/MemoryManagement/)。
二、Metaspace
其参数设置如下:
-XX:MetaspaceSize,初始空间大小,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。
-XX:MaxMetaspaceSize,最大空间,默认是没有限制的。如果没有使用该参数来设置类的元数据的大小,其最大可利用空间是整个系统内存的可用空间。JVM也可以增加本地内存空间来满足类元数据信息的存储。但是如果没有设置最大值,则可能存在bug导致Metaspace的空间在不停的扩展,会导致机器的内存不足;进而可能出现swap内存被耗尽;最终导致进程直接被系统直接kill掉。如果设置了该参数,当Metaspace剩余空间不足,会抛出:java.lang.OutOfMemoryError: Metaspace space
-XX:MinMetaspaceFreeRatio,在GC之后,最小的Metaspace剩余空间容量的百分比,减少为分配空间所导致的垃圾收集
-XX:MaxMetaspaceFreeRatio,在GC之后,最大的Metaspace剩余空间容量的百分比,减少为释放空间所导致的垃圾收集
查看该区值:
最大空间是不是很大?因为其占用的是操作系统本地内存(而非JVM设置的内存大小),最大可利用空间是整个系统内存的可用空间。
还可以通过jstat命令,如下:jstat -gcmetacapacity
如上图可以看到元数据区的分类,如下:
MCMN: Minimum metaspace capacity (KB).
MCMX: Maximum metaspace capacity (KB).
MC: Metaspace Committed Size (KB).
CCSMN: Compressed class space minimum capacity (KB).
CCSMX: Compressed class space maximum capacity (KB).
YGC: Number of young generation GC events.
FGC: Number of full GC events.
FGCT: Full garbage collection time.
GCT: Total garbage collection time.
元数据区存放什么内容呢?
运行时常量池
方法元信息,JVM必须保存所有方法的以下信息,同域信息一样包括声明顺序如下:
1. 方法名称方法的返回类型(或void)
2. 方法参数的数量和类型(按顺序)
3. 方法的修饰符public、private、protected、static、final、synchronized、native,、abstract的一个子集
4. 方法的字节码bytecodes、操作数栈、局部变量表及大小( abstract和native方法除外)
5. 异常表( abstract和 native方法除外)。每个异常处理的开始位置、结束位置、代码处理在程序计数器中的偏移地址、被捕获的异常类的常量池索引。
类元信息:
对每个加载的类型(类Class、接口 interface、枚举enum、注解 annotation),JVM必须在方法区中存储以下类型信息:
① 这个类型的完整有效名称(全名 = 包名.类名)
② 这个类型直接父类的完整有效名(对于 interface或是java.lang. Object,都没有父类)
③ 这个类型的修饰符( public, abstract,final的某个子集)
④ 这个类型直接接口的一个有序列表
域信息,即为类的属性,成员变量。JVM必须在方法区中保存类所有的成员变量相关信息及声明顺序,域的相关信息包括:域名称、域类型、域修饰符(pυblic、private、protected、static、final、volatile、transient的某个子集)。
三、常量池
上述提到了运行时常量池,那么两者有何关系?
常量池:存放编译期间生成的各种字面量与符号引用;运行时常量池:常量池表在运行时的表现形式
编译后的字节码文件中包含了类型信息、域信息、方法信息等。通过ClassLoader将字节码文件的常量池中的信息加载到内存中,存储在了Metaspace的运行时常量池中。