欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

JVM内存结构

程序员文章站 2022-06-07 08:02:16
...

        在实际工作中,对于有一定经验的程序员来说,jvm的调优是必须掌握的知识,在了解各种命令和调优手段之前,首先必须的清楚jvm的内存结构,因为jvm调优必须要求开放人员对内存结构有一定的了解,对于OutOfMemoryError等异常知道原因,下面我们就来介绍一下jvm的内存结构。

先来看一张图,了解下jvm内存结构的整体布局

JVM内存结构

jvm的内存结构主要分为3大块:堆内存,方法区和栈,堆内存是jvm中最大的一块由年轻代和老年代组成。

年轻代分为Eden空间From Survivor空间To Survivor空间,默认情况下年轻代按照8:1:1的比例来分配;老年代是通过调整年轻代和堆内存来间接控制的。

方法区是存储类的信息,常量和静态变量,线程共享的,为了和堆内存区分,称为Non-Heap(非堆)。

栈分为java虚拟机栈和本地方法栈,用于方法的执行;每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

下面通过一张图来了解参数控制各内存区域的大小

JVM内存结构

 

控制参数

  • -Xms设置堆的最小空间大小。

  • -Xmx设置堆的最大空间大小。

  • -Xmn设置年轻代的空间的大小

  • -XX:NewSize设置新生代最小空间大小。

  • -XX:MaxNewSize设置新生代最大空间大小。

  • -XX:PermSize设置永久代最小空间大小。

  • -XX:MaxPermSize设置永久代最大空间大小。

  • -Xss设置每个线程的堆栈大小。

-Xmn 和 -XX:Newsize 
前者是设置堆中新生代大小。后者是设置新生代初始大小
-Xmn 是将NewSize与MaxNewSize设为一致。

下面对各个区域详细介绍下

 

Java堆(Heap)

是jvm管理内存最大的一块,是线程共享的,虚拟机启动创建时,唯一目的是方法对象实例,几乎所有的对象实例都在这里分配内存。

java堆是垃圾收集器管理的主要区域,因此也被称为GC堆,垃圾回收采用的分代收集算法,所以堆分为年轻代,老年代,年轻代还可以细分为Eden空间、From Survivor空间、To Survivor空间等;根据java虚拟机规范规定,java堆可以是物理上不连续的内存空间,逻辑上连续就行;通过-Xmx和-Xms控制。

new创建的对象和数组,是存放在堆中的,用完之后靠垃圾回收机制不定期自动清除。

如果堆中没有内存完成分配,或者不可扩展,就会抛出OutOfMemoryError异常。

方法区(Method Area)

与堆一样,是线程共享的内存区域,它用于存储已经被虚拟机加载的类信息,常量,静态变量,即使编译器编译后的代码等数据

当方法区无法满足内存分配时,将抛出OutOfMemoryError异常。

 

JVM栈(JVM Stacks)

线程私有,生命周期和线程相同,虚拟机栈描述的是java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧,用于存储局部变量表,操作栈,动态链接,方法出口等信息。每一个方法被调用至执行完毕的过程,就是一个栈帧在虚拟机中从入栈到出栈的过程;栈中的数据用完就消失

局部变量存放编译器可知的各种基本数据类型(byte,int,boolean,char,short,float,double,long),对象引用。

java虚拟机规范中,如果请求的栈深度大于虚拟机所允许的深度,将抛出*Error异常;动态扩展时没有足够的内存也会抛出这个异常。(用递归的时候需要注意下,如果可能,使用尾递归)

哪儿的OutOfMemoryError

Exception in thread main”: java.lang.OutOfMemoryError: Java heap space

原因:对象不能被分配到堆内存中。

Exception in thread main”: java.lang.OutOfMemoryError: PermGen space

原因:类或者方法不能被加载到老年代。它可能出现在一个程序加载很多类的时候,比如引用了很多第三方的库。

Exception in thread main”: java.lang.OutOfMemoryError: Requested array size exceeds VM limit

原因:创建的数组大于堆内存的空间。

Exception in thread main”: java.lang.OutOfMemoryError: request <size> bytes for <reason>. Out of swap space?

原因:分配本地分配失败。JNI、本地库或者Java虚拟机都会从本地堆中分配内存空间。

Exception in thread main”: java.lang.OutOfMemoryError: <reason> <stack trace>(Native method

原因:同样是本地方法内存分配失败,只不过是JNI或者本地方法或者Java虚拟机发现

 

下面讲解下堆和栈的区别

栈是运行单位,堆是存储单位

堆(对象):

引用类型的变量,其内存分配在堆上或者常量池(字符串常量,基本数据类型常量),需要通过new等方式创建。

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

栈(基本数据类型变量,对象的引用变量):

 

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

栈内存的主要作用是存放基本数据类型和引用变量。栈的内存管理是通过栈的"后进先出"模式来实现的。(主要用来执行程序,存取速度快,大小和生存期必须确定,缺乏灵活性)

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

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

下图是堆和栈关联的图

JVM内存结构

下面来看段代码:

public static void main(String[] args) {
    Test t = new Test();
	Test t1 = new Test();
	System.out.println(t == t1);
	t = t1;
	System.out.println(t == t1);
}

输出:false  true

原因是因为t和t1变量是分别new的,对象在堆中分配的内存地址是不一样的,地址指向t,t1后也是不一样的,然后将t1赋值给t后,说白了是把t1对应的堆中的内存地址指向t,这样t对应的堆中的对象就没有引用,垃圾回收器会自动回收;t的地址变为t1,所以t和t1后面才会相等。

注:以上有些内容来自于网上,自己搜集整理,加上自己的理解。

 

相关标签: jvm