JVM内存模型以及内存分配示例

JVM内存模型总体架构图

JVM内存模型以及内存分配示例_第1张图片

通过另一种形式画出来更好理解

JVM内存模型以及内存分配示例_第2张图片

JAVA堆

线程共享的,存放所有对象实例和数组。垃圾回收的主要区域。所有通过new创建的对象的内存都在堆中分配,堆的大小可以通过-Xms(初始堆大小)和-Xmx(最大堆大小)来控制。堆被划分为新生代和旧生代,新生代又被进一步划分为Eden和Survivor区,最后Survivor由From Space和To Space组成,结构图如下所示:

JVM内存模型以及内存分配示例_第3张图片

  • 新生代。新建的对象都是用新生代分配内存,Eden空间不足的时候,会把存活的对象转移到Survivor中,新生代大小可以由-Xmn来控制,也可以用-XX:SurvivorRatio来控制Eden和Survivor的比例

  • 旧生代。用于存放新生代中经过多次垃圾回收仍然存活的对象

  • 持久带(Permanent Space)实现方法区,主要存放所有已加载的类信息,方法信息,常量池等等。可通过-XX:PermSize和-XX:MaxPermSize来指定持久带初始化值和最大值。Permanent Space并不等同于方法区,只不过是Hotspot JVM用Permanent Space来实现方法区而已,有些虚拟机没有Per

    -Xmx:最大堆内存,如:-Xmx512m

    -Xms:初始时堆内存,如:-Xms256m

    -XX:MaxNewSize:最大年轻区内存

    -XX:NewSize:初始时年轻区内存.通常为 Xmx 的 1/3 或 1/4。新生代 = Eden + 2 个 Survivor 空间。实际可用空间为 = Eden + 1 个 Survivor,即 90%

    -XX:MaxPermSize:最大持久带内存

    -XX:PermSize:初始时持久带内存

    -XX:+PrintGCDetails。打印 GC 信息

     -XX:NewRatio 新生代与老年代的比例,如 –XX:NewRatio=2,则新生代占整个堆空间的1/3,老年代占2/3

     -XX:SurvivorRatio 新生代中 Eden 与 Survivor 的比值。默认值为 8。即 Eden 占新生代空间的 8/10,另外两个 Survivor 各占 1/10

    manent Space而用其他机制来实现方法区。

线程私有的,与线程在同一时间创建。管理JAVA方法执行的内存模型。每个方法执行时都会创建一个桢栈来存储方法的的变量表、操作数栈、动态链接方法、返回值、返回地址等信息。栈的大小决定了方法调用的可达深度(递归多少层次,或嵌套调用多少层其他方法,-Xss参数可以设置虚拟机栈大小)。栈的大小可以是固定的,或者是动态扩展的。如果请求的栈深度大于最大可用深度,则抛出stackOverflowError;如果栈是可动态扩展的,但没有内存空间支持扩展,则抛出OutofMemoryError。
使用jclasslib工具可以查看class类文件的结构。下图为栈帧结构图 每个线程执行每个方法的时候都会在栈中申请一个栈帧,每个栈帧包括局部变量区和操作数栈,用于存放此次方法调用过程中的临时变量、参数和中间结果。

-xss:设置每个线程的堆栈大小. JDK1.5+ 每个线程堆栈大小为 1M,一般来说如果栈不是很深的话, 1M 是绝对够用了的。

JVM内存模型以及内存分配示例_第4张图片

本地方法栈

和虚拟机栈功能相似,但管理的不是JAVA方法,是本地方法,本地方法是用C实现的 ,用于支持native方法的执行,存储了每个native方法调用的状态

方法区

线程共享的,用于存放被虚拟机加载的类的元数据信息:如常量、静态变量、即时编译器编译后的代码。也成为永久代。如果hotspot虚拟机确定一个类的定义信息不会被使用,也会将其回收。回收的基本条件至少有:所有该类的实例被回收,而且装载该类的ClassLoader被回收。存放了要加载的类信息、静态变量、final类型的常量、属性和方法信息。JVM用持久代(Permanet Generation)来存放方法区,可通过-XX:PermSize和-XX:MaxPermSize来指定最小值和最大值

程序计数器
多线程时,当线程数超过CPU数量或CPU内核数量,线程之间就要根据时间片轮询抢夺CPU时间资源。因此每个线程有要有一个独立的程序计数器,记录下一条要运行的指令。线程私有的内存区域。如果执行的是JAVA方法,计数器记录正在执行的java字节码地址,如果执行的是native方法,则计数器为空。

JVM运行原理 示例

package com;

public class JVMTestCase {

	public static String className = "JVMTestCase";

	private int var = 5;

	// 常规静态方法
	public static String runStaticMethod() {
		return className;
	}

	// 非静态方法
	public int runNonStaticMethod(int parameter) {
		int methodVar = this.var * parameter;
		return methodVar;
	}
	
	public static void main(String args){
		
		JVMTestCase.runStaticMethod();
		
		
		JVMTestCase jct = new JVMTestCase();
		jct.runNonStaticMethod(3);
		
		
	}

}

看 JVM 是如何运行的,也就是输入 java JVMShow 后,我们来看 JVM 是如何处理的:
      第 1 步 、向操作系统申请空闲内存。JVM 对操作系统说“给我 64M(随便模拟数据,并不是真实数据) 空闲内存”,于是,JVM 向操作系统申请空闲内存作系统就查找自己的内存分配表,找了段 64M 的内存写上“Java 占用”标签,然后把内存段的起始地址和终止地址给 JVM,JVM 准备加载类文件。
      第 2 步,分配内存内存。JVM 分配内存。JVM 获得到 64M 内存,就开始得瑟了,首先给 heap 分个内存,然后给栈内存也分配好。
     第 3 步,文件检查和分析class 文件。若发现有错误即返回错误。
      第 4 步,加载类。加载类。由于没有指定加载器,JVM 默认使用 bootstrap 加载器,就把 rt.jar 下的所有类都加载到了堆类存的Method Area,JVMShow 也被加载到内存中。我们来看看Method Area区域,如下图:( 这时候包含了 main 方法和 runStaticMethod方法的符号引用,因为它们都是静态方法,在类加载的时候就会加载
JVM内存模型以及内存分配示例_第5张图片
Heap 是空,Stack 是空,因为还没有对象的新建和线程被执行。
       第 5 步、执行方法。执行 main 方法。执行启动一个线程,开始执行 main 方法,在 main 执行完毕前,方法区如下图所示:
public final static String ClASS_CONST = "I'm a Const";  
JVM内存模型以及内存分配示例_第6张图片
     在 Method Area 加入了 CLASS_CONST 常量,它是在第一次被访问时产生的(runStaticMethod方法内部)。
 
     堆内存中有两个对象 object 和 showcase 对象,如下图所示:(执行了JVMShowcase showcase=new JVMShowcase();  )
JVM内存模型以及内存分配示例_第7张图片
为什么会有 Object 对象呢?是因为它是 JVMShowcase 的父类, JVM 是先初始化父类,然后再初始化子类,甭管有多少个父类都初始化。
 
在栈内存中有三个栈帧,如下图所示:
JVM内存模型以及内存分配示例_第8张图片
于此同时,还创建了一个程序计数器指向下一条要执行的语句。
 
第 6 步,释放内存。释放内存。运行结束,JVM 向操作系统发送消息,说“内存用完了,我还给你”,运行结束。



你可能感兴趣的:(java,JVM)