Java运行时内存分配和垃圾回收机制介绍

目录

一、Java运行时内存分配

二、垃圾回收算法

三、获取Java进程的实时内存

一、Java运行时内存分配

Java内存池通常分为以下几个部分:

Java运行时内存分配和垃圾回收机制介绍_第1张图片

1. 堆内存(Heap Memory):最大的内存池,用于存储所有对象实例和数组。堆内存是可扩展的,它的大小可以通过启动JVM时的参数进行调整。
2. 方法区(Method Area):用于存储类信息、常量、静态变量和即时编译器编译后的代码等数据的内存区域。方法区也是可扩展的,所以也可能抛出OutofMemoryError;
3. 本地方法栈(Native Method Stack):本地方法栈是Java虚拟机执行本地方法(Native Method)时使用的内存区域。本地方法是使用本地语言(如C、C++等)编写的方法,需要在运行时通过JNI接口调用。本地方法栈也是可扩展的。可能抛出StackOverflowError和OutofMemoryError;
4. Java栈(Java Stack):Java栈是用于存储线程执行时的栈帧(Stack Frame)的内存区域。每个线程都有一个独立的Java栈,用于存储线程执行过程中的局部变量和操作数栈等数据。Java栈的大小可以通过启动JVM时的参数进行调整。当栈的深度超过规定深度时,可能抛出StackOverflowError和OutofMemoryError;
5. PC寄存器(Program Counter Register):PC寄存器是用于存储线程执行时的指令地址的内存区域。每个线程都有一个独立的PC寄存器,用于存储当前正在执行的指令地址。

二、垃圾回收算法

对于新生代的内存,这些对象大多挺不过第一次的垃圾回收,所以使用的是标记——复制回收机制:

Java运行时内存分配和垃圾回收机制介绍_第2张图片


即通常把新生代的区域,划分为Eden : Survivor(图中的form): Survivor 大小比例8:1:1,在回收时会把Eden和一块Survivor中仍在存在的对象复制到另一块Survivor中,然后进行清理,如果剩余的对象过多,一块Survivor放不下,就可能用到老年区的内存。

新生代内存的这种标记——复制算法,只适合对象的存活率比较低的情况,而且始终有部分内存是没有使用的,比较占内存,老年代的回收使用了标记——整理算法,对要回收的对象进行标记(反过来也可),然后直接移动不用回收的对象到老生代内存的开始区域,最后清理边界外的对象即可。

Java运行时内存分配和垃圾回收机制介绍_第3张图片

虽然这种算法占用的内存少,但是在移动对象时,还要更新引用存活对象的地方,这个时候会暂停应用程序。
那如何判定对象是否存活?Java中也有引用,但并不是利用引用计数来判断对象是否回收,而是利用可达性分析,也就是图论上的从根节点(GC_Roots)出发,如果某个节点(对象)不可达(没有被引用到),就认为该节点是可回收的,也就是非存活的。
常见的可作为GC_Roots的对象有:
1、在虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如各个线程被调用的方法堆栈中使用到的 
参数、局部变量、临时变量等。 
2、在方法区中类静态属性引用的对象,譬如Java类的引用类型静态变量。 
3、在方法区中常量引用的对象,譬如字符串常量池(String Table)里的引用。·在本地方法栈中JNI(即通常所说的Native方法)引用的对象。 
4、Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象(比如 
NullPointExcepiton、OutOfMemoryError)等,还有系统类加载器。 
5、所有被同步锁(synchronized关键字)持有的对象。 
6、反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等。

三、获取Java进程的实时内存

常用的获取Java运行时内存的接口都封装在Runtime中,如果想用命令行工具查看运行时Java整个进程(PS:整个进程,不仅仅是堆内存)的内存占用情况,可以使用
jcmd pid GC.heap_info
输出如下:

pid:
 PSYoungGen      total 545280K, used 47470K [0x00000000d5580000, 0x00000000ff700000, 0x0000000100000000)
  eden space 406016K, 11% used [0x00000000d5580000,0x00000000d83dba90,0x00000000ee200000)
  from space 139264K, 0% used [0x00000000f6f00000,0x00000000f6f00000,0x00000000ff700000)
  to   space 141824K, 0% used [0x00000000ee200000,0x00000000ee200000,0x00000000f6c80000)
 ParOldGen       total 699392K, used 56443K [0x0000000080000000, 0x00000000aab00000, 0x00000000d5580000)
  object space 699392K, 8% used [0x0000000080000000,0x000000008371eff8,0x00000000aab00000)
 Metaspace       used 92947K, capacity 96139K, committed 96896K, reserved 1134592K
  class space    used 10340K, capacity 10954K, committed 11136K, reserved 1048576K

其中输出项的含义为:
以JDK 1.8为例,PSYoungGen 表示的是新生代,并且使用 Parallel Scavenge 收集器来 GC,ParOldGen 表示老年代,并且使用 Parallel old 收集器来 GC。
其中 eden、from 和 to 区域属于新生代(Young)区域,这三个区域大小的比例默认为 8:1:1,整个新生代区域和老年代区域的比例为 1:2。
Metaspace和class space不属于堆,它使用的是直接从机器上分配的内存。

Runtime.getRuntime().maxMemory() = -Xmx设置的大小- 2* PS Survivor Space;

Runtime.getRuntime().totalMemory() = PSYoungGen.total + ParOldGen.total 也就是堆的总内存

Runtime.getRuntime().freeMemory() = PSYoungGen.total - PSYoungGen.used + ParOldGen.total - ParOldGen.used 也就是分配的内存中未使用的量;

ManagementFactory.getMemoryMXBean().getNonHeapMemoryUsage().getMax() : 非堆的内存最大值 ,等于Metaspace.reserved + class space.reserved

ManagementFactory.getMemoryMXBean().getNonHeapMemoryUsage().getUsed():非堆区域使用内存的值,等于Metaspace.used + class space.used

MemoryPoolMXBean.getUsage().getMax():  class space的最大内存, 等于class space.reserved

MemoryPoolMXBean.getUsage().getUsed(): class space的使用量, 等于class space.used。

你可能感兴趣的:(Java,jvm,java,算法)