JVM:Java内存区域

来源:互联网 发布:数据助理是做什么的 编辑:程序博客网 时间:2024/06/05 10:30

1. Java内存区域

JVM内存,运行时数据区
这里写图片描述

1.1 程序计数器(Program Counter Register)

它是一块较小的内存空间,它的作用可以看做是当先线程所执行的字节码的信号指示器。
每一条JVM线程都有自己的PC寄存器,各条线程之间互不影响,独立存储,这类内存区域被称为“线程私有”内存
在任意时刻,一条JVM线程只会执行一个方法的代码。该方法称为该线程的当前方法(Current Method)
如果该方法是java方法,那PC寄存器保存JVM正在执行的字节码指令的地址
如果该方法是native,那PC寄存器的值是undefined。
此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。

1.2 Java虚拟机栈(Java Virtual Machine Stack)线程隔离

与PC寄存器一样,Java虚拟机栈也是线程私有的。每一个JVM线程都有自己的java虚拟机栈,这个栈与线程同时创建,它的生命周期与线程相同。
虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
局部变量表(主要存放一些基本类型的变量数据(int,short,long,byte,float,double,boolean,char)和对象引用。在编译期分配其内存空间大小,运行期大小不再改变。)
JVM stack 可以被实现成固定大小,也可以根据计算动态扩展。
如果采用固定大小的JVM stack设计,那么每一条线程的JVM Stack容量应该在线程创建时独立地选定。JVM实现应该提供调节JVM Stack初始容量的手段;如果采用动态扩展和收缩的JVM Stack方式,应该提供调节最大、最小容量的手段。
如果线程请求的栈深度大于虚拟机所允许的深度将抛出StackOverflowError;
如果JVM Stack可以动态扩展,但是在尝试扩展时无法申请到足够的内存时抛出OutOfMemoryError。

1.3 Java堆(Java Heap)线程共享

虚拟机管理的内存中最大的一块,同时也是被所有线程所共享的,它在虚拟机启动时创建,存在的意义就是 存放对象实例,几乎所有的对象实例以及数组都要在这里分配内存。这里面的对象被自动管理,也就是俗称的GC(Garbage Collector)所管理。
Java堆是垃圾收集器管理的主要区域,也被称为GC堆。所有对象都在这里被分配。
Java堆的容量可以是固定大小,也可以随着需求动态扩展(-Xms和-Xmx),并在不需要过多空间时自动收缩。
Java堆所使用的内存不需要保证是物理连续的,只要逻辑上是连续的即可。
JVM实现应当提供给程序员调节Java 堆初始容量的手段,对于可动态扩展和收缩的堆来说,则应当提供调节其最大和最小容量的手段。
如果堆中没有内存完成实例分配并且堆也无法扩展,就会抛OutOfMemoryError。

1.4 方法区(Method Area)线程共享**

跟堆一样是被各个线程共享的内存区域,用于存储以被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然这个区域被虚拟机规范把方法区描述为堆的一个逻辑部分,但是它的别名叫非堆,用来与堆做一下区别。

  • 方法区在虚拟机启动的时候创建
  • 方法区的大小可以选择固定,也可以选择自由伸缩。
  • 方法区在实际内存中可以是不连续的。
public class Hello {    public static void main(String[] args) {        public Hello h = new Hello();        //JVM将Hello类信息加载到方法区,new Hello()实例保存在堆区,h引用保存在栈区      }}

1.5 运行时常量池

这个可是方法区的一部分.Class文件中除了有类的版本,字段,方法,接口等描述形象外,还有一项信息是常量池,用于存放编译器生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池存放。

2 JVM中的对象

2.1 对象的创建

当JVM遇到一条new指令的时候,首先检查这个指令的参数时候能在常量池中定位到一个了的符号引用,并检查这个类是否已经加载。(没有则加载)。
对象创建的过程:

  • 分配内存,并将分配到的内存空间都初始化为0值。
  • 对对象进行设置,如这个对象是哪个了的实例,如何才能找到类的元数据想你想,哈希码等。这些信息存放到对象头中(对象头接下来有讲)。
  • 执行方法,对对象进行初始化

2.2 对象内存布局

可以分为3个部分:

  • 对象头
  • 实例数据(这个也就是我们所能使用的)
  • 对齐填充数据,用于内存对齐

* 对象头 *
包括两个部分:

  • 1.用于存储对象自身的运行时数据,如哈希码,GC分代年龄,线程持有的锁等等。
  • 2.类型指针,对象指向它的类元数据的指正,虚拟机通过这个指针来确定这个对象是哪个类的示例。并不是所有的虚拟机对象都必须在对象数据上保留类型指针,也就是说,查找对象的元数据类型并不一定要经过对象本身。如果对象是Java数据,还需要存储一个length信息。

3.内存异常

3.1 Java堆溢出:OutOfMemoryError

不断创建对象又持有有些对象,导致无法GC,在对象数量达到最大堆的容量限制之后就会产生内存溢出异常。

3.2 虚拟机栈和本地方法栈溢出:StackOverflowError

当内存无法分配的时候,JVM都会抛出StackOverflowError。

操作系统分给JVM的内存大小是有限制的,32位Windows 限制为2GB。2GB内存(系统限制的最大内存) - XmX(最大堆容量) - MaxPerSize(最大方法区容量)。剩下的内存就由虚拟机栈和本地方法栈瓜分。
对于多线程来说,每个线程分配到的栈容量越大,可以建立的线程数量自然越少,容易把内存消耗殆尽。对于过多线程导致的内存溢出,在不能减少线程数的情况下,只能通过减少最大堆和减少栈容量来换取更多的线程。

3.3 方法区和运行时常量区溢出

由于类的GC条件苛刻,当类过多时,会出现这种情况。如Spring,Hibernate,,多会对类进行增强处理,导致类的信息量更大。

原创粉丝点击