您的位置:首页 > 其它

JVM内存模型

2017-05-21 11:32 211 查看
1.JVM 内存模型图



首先我们熟悉一下一个一般性的 Java 程序的工作过程。一个 Java 源程序文件,会被编译为字节码文件(以 class 为扩展名),每个java程序都需要运行在自己的JVM上,然后告知 JVM 程序的运行入口,再被 JVM 通过字节码解释器加载运行。那么程序开始运行后,都是如何涉及到各内存区域的呢?
概括地说来,JVM初始运行的时候都会分配好 Method Area(方法区) 和Heap(堆) ,而JVM 每遇到一个线程,就为其分配一个 Program
Counter Register(程序计数器)
 ,   VM Stack(虚拟机栈)和Native Method Stack  (本地方法栈), 当线程终止时,三者(虚拟机栈,本地方法栈和程序计数器)所占用的内存空间也会被释放掉。这也是为什么我把内存区域分为线程共享和非线程共享的原因,非线程共享的那三个区域的生命周期与所属线程相同,而线程共享的区域与JAVA程序运行的生命周期相同,所以这也是系统垃圾回收的场所只发生在线程共享的区域(实际上对大部分虚拟机来说知发生在Heap上)的原因。

⑴程序计数器(线程隔离)
唯一一个没有规定任何OutOfMemoryError区域。是一个比较小的内存区域,用于指示当前线程锁执行的字节码到了第几行。

⑵虚拟机栈(就是我们常说的栈)

生命周期和线程相同,每个方法执行的同时创建一个线帧用于存储局部变量表,操作数栈,动态链接,方法出口等信息。方法调用到执行完成就对应一个栈帧在虚拟机栈中入栈到出栈的过程。存放了编译器可知的各种基本数据类型,对象的引用和returnAddress类型(指向了一条字节码指令的地址)。栈的优势是,存取速度比堆要快,仅次于寄存器,栈数据可以共享(所谓的数据共享,就是你放在那里,他不变,但是你递增的话,他指向新的地址值,并没有改变原来的常量池里面的数据本身。况且,常量池的数据可以同时由多个引用指向他,所以并没有影响·)。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。

⑶本地方法栈

与虚拟机栈相似,虚拟机栈为虚拟机执行java方法服务,本地方法栈为虚拟机使用的Native方法服务。

⑷JAVA堆
所有对象的实例以及数组都要在堆上分配。现在随着JIT编译器的方法这句话也不那么绝对了。堆是垃圾收集管理的主要区域。JAVA堆可以细分为新生代和老生代,具体可以看垃圾回收机制的博客。堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,因为它是在运行时动态分配内存的,Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢,用堆的效率非常低。

⑸方法区
用于存储已被虚拟机加载的类信息,常量,静态变量,即使编译器编译后的代码等数据。



这里再补充一个概念:直接内存
直接内存(Direct
Memory)并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域,和上面几个没啥关系,但是这部分内存也被频繁地使用,会收到机器本身内存的限制,可能导致OutOfMemoryError异常出现

2.堆的内存配置参数

-Xmx:最大堆大小
-Xms:初始堆大小
-Xmn:年轻代大小
-XXSurvivorRatio:年轻代中Eden区与Survivor区的大小比值

-Xmx10240m -Xms10240m -Xmn5120m
-XXSurvivorRatio=3

年轻代5120m, Eden:Survivor=3,Survivor区大小=1024m(Survivor区有两个,即将年轻代分为5份,每个Survivor区占一份),总大小为2048m。
-Xms初始堆大小即最小内存值为10240m

3.不同类型数据存放位置
⑴.堆

主要存放引用类型的变量,需要通过new等方式来创建,另外基本数据类型成员变量也存放于堆。

堆内存主要作用是存放运行时创建(new)的对象。

(主要用于存放对象,存取速度慢,可以运行时动态分配内存,生存期不需要提前确定)

在堆中产生了一个数组或对象后,还可以在栈中定义一个特殊的变量,让栈中这个变量的取值等于数组或对象在堆内存中的首地址,栈中的这个变量就成了数组或对象的引用变量。

⑵.栈

基本数据类型的局部变量(int、short、long、byte、float、double、boolean、char等)以及对象的引用变量,其内存分配在栈上,变量出了作用域就会自动释放。

栈内存的主要作用是存放基本数据类型和引用变量。栈的内存管理是通过栈的"后进先出"模式来实现的。

(主要用来执行程序,存取速度快,大小和生存期必须确定,缺乏灵活性)

⑶方法区
Java类的静态成员变量是放在方法区中,JDK7以上版本,静态域存储于定义类型的Class对象中,Class对象如同堆中其他对象一样,存在于GC堆中

class 鸭子{
private int age;
private int weight;
public 鸭子(int age,int weight){
this.age=age;
this.weight=weight;
}
}

public class Test{
public static void main(String[] args){
//int i=0;      //基本数据类型变量
鸭子 duck =new 鸭子(1,1000);   //duck是对象的引用变量,存在栈;鸭子(1,1000)是实际的对象,存在堆。
}
}
图解:



PS:

JVM:

是基于堆栈的虚拟机,每个Java程序在一个独立的JVM实例上运行,每个JVM实例对应一个堆,

同个java程序内的多线程运行在同个JVM实例上,多个线程之间共享堆内存(多线访问堆时,要实现数据的同步)。

为什么又栈内存和堆内存之分?

当一个方法执行时,每个方法都会建立自己的内存栈,在这个方法定义的变量将会放到这块栈内存里,随着方法的结束,这个方法的内存栈也将自动销毁。(不需要GC回收)

因此,所有在方法中定义的局部变量放在栈内存中;

当我们在程序中创建一个对象时,这个对象会被保存到运行时数据区中,以便反复利用(复用,因为创建对象的成本通常较大),这个运行时数据区就是堆内存。堆内存中的对象不会随着方法的结束而销毁,即使方法结束后,这个对象还可能被另外一个引用变量所引用(在方法的参数传递时很常见),则这个对象依然不会被销毁。只有当一个对象没有任何引用变量去引用它时,系统的垃圾回收器(GC)才会在合适的时候回收它。

为什么上面说创建对象的开销(成本)比较大?

来看看如何创建对象、创建对象的过程:

创建对象的根本途径是构造器,通过new关键字来调用某个类的构造器即可创建这个类的实例。但对象不是完全由构造器来负责创建的,实际上,当程序员调用构造器时,系统会先为该对象分配内存空间,并为这个对象执行默认初始化,这个对象已经产生了---这些是在构造器执行之前就完成的,也就是说,当系统开始执行构造器的执行体之前,系统已经创建了一个对象,只是这对象还不能被外部程序访问,只能在构造器中通过this来引用。当构造器的执行体执行结束后,这个对象作为构造器的返回值被返回,通常还会赋给另外一个引用类型的变量,从而让外部程序可以访问。(当然可以通过设置构造器的访问权限private,阻止其他类创建该类的实例)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: