运行时数据区与

jvm运行时数据区域包括堆,本地方法栈,虚拟机栈,程序计数器,方法区,其中堆内存和方法区是线程共享的,虚拟机栈,本地方法栈,程序计数器是线程私有的。

1.程序计数器:程序计数器是一块较小的内存空间,它的作用相当于是当前线程执行到字节码的行号指示器,确定下一条需要执行的字节码指令。Java的多线程是通过轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器只会处理一个线程所执行的指令。为了线程切换后能够回到上次执行的位置,每个线程需要一个独立的程序计数器,各个线程之间的计数器互不影响如果线程正在执行的是一个java方法,则计数器记录的是正在执行的虚拟机字节码指令的地址,如果正在执行的是native方法,则计数器值为空。
2.虚拟机栈:虚拟机栈也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法执行的时候都会同步创建一个栈帧用于存放局部变量表,操作数栈,动态链接,方法出口等信息。每个放法被调用到执行完毕的时候都对应一个栈帧在虚拟机栈中从入栈到出栈的过程。局部变量表存放了编译器可知的各种Java虚拟机基本数据类型,对象引用和returnAddress类型。这个方法需要在栈帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。

java虚拟机栈有两种异常:

​ 如果线程请求的栈深度大于虚拟机所允许的深度,则抛出StackOverflowError异常;
如果虚拟机栈可以动态扩展的,当扩展时无法申请到足够的内存时会抛出OutOfMemoryError异常。
3.本地方法栈:本地方法栈与虚拟机栈所发挥的作用是相似的,区别在于虚拟机栈为虚拟机执行java方法的服务,本地方法栈则是为虚拟机使用到native方法服务。
4.堆:Java堆是虚拟机所管理的内存中最大的一块,在虚拟机启动时创建。此内存的唯一目的就是存放对象实例,几乎所有的对象实例和数组都在这里分配内存。(随着JIT编译器的发展,在栈上也有可能分配)java堆是垃圾收集器管理的主要区域,在物理上可以使不连续的 内存空间,但在逻辑上是联系的。
如果再堆中没有内存完成实例的分配,并且堆也无法在扩展的时候,将会抛出OutOfMemoryError异常
5.方法区:方法区与Java堆一样是线程共享的内存区域。它用于存储已被虚拟机加载的类型信息、常量、静态常量、即时编译器编译后的代码缓存等数据。这块区域很少进行垃圾回收,甚至可以不实现垃圾收集,主要是针对常量池的回收和对类型的卸载。当方法区无法分配内存的时候,将抛出OutOfMemoryError异常。
5.2:运行时常量池:运行时常量池是方法区的一部分,Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池表,用于存放编译期生成的各种字面量与符号引用,这部分内容将在类加载之后存放到方法区的运行时常量池中。运行时常量池相对于Class文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定只有编译器才能产生,也就是说,并非预置入Class文件中常量池的内容才能进入运行时常量池,运行期间也可以将新的常量放入池中,这种特性被开发人员利用的较多的是String类的intern()方法。
6.直接内存:直接内存并不是运行时数据区域的一部分,也不是《Java虚拟机规范》的内存区域。但是这部分内存也被频繁的使用,而且也可能导致OutOfMemoryError异常。
在jdk1.4之后新加入了NIO(new input/output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存然后通过
一个存储在Java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。这样能够在一些场景中显著提高性能,因为避免了在java堆和本地堆中来回复制数据。显然
本地直接内存的分配不会受到Java堆大小的限制,但是会受到本机总内存的限制。

对象的内存布局:
在HotSpot虚拟机里,对象在堆内存中的存储布局分为三个部分:对象头、实例数据和对齐填充。
1.HotSpot虚拟机对象头部分包括两类信息。第一类是用来存储对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,
对象头的另一部分是类型指针。即对象指向它的类型元数据的指针,Java虚拟机通过这个指针来确定对象是哪个类的实例。
**2.实例数据部分是对象真正存储的有效信息,**即我们在程序代码里面所定义的各种类型的字段内容,无论是从父类继承下来的,还是在子类中定义的字段都必须记录起来。
这部分的存储顺序会受到虚拟机分配策略参数(-XX:FieldsAllocationStyle参数)和字段在Java源码中定义顺序的影响。
**3.对象的第三部分是对象填充,**这并不是必然存在的,也没有特别的含义,它仅仅起着一个占位符的作用。由于HotSpot虚拟机的自动内存管理系统要求对象起始地址必须是
8字节的整数倍,换句话说就是对任何对象的大小必须是8字节的整数倍。对象头部分已经被精心设计成了正好是8字节的倍数,因此如果对象示例数据部分没有对齐的话,就需
要通过对象填充来不全。

Java 堆的结构

JVM的堆是运行时数据区,所有类的实例和数组都是在堆上分配内存。它在JVM启动的时候被创建。对象所占的堆内存是由自动内存管理系统也就是垃圾收集器回收。堆内存是由存活和死亡的对象组成的。存活的对象是应用可以访问的,不会被垃圾回收。死亡的对象是应用不可访问尚且还没有被垃圾收集器回收掉的对象。一直到垃圾收集器把这些对象回收掉之前,他们会一直占据堆内存空间。永久代是用于存放静态文件,如Java类、方法等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如Hibernate 等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类,永久代中一般包含:

类的方法(字节码…)

类名(Sring对象)

.class文件读到的常量信息

class对象相关的对象列表和类型列表 (e.g., 方法对象的array).

JVM创建的内部对象

JIT编译器优化用的信息

*虚拟机中的共划分为三个代:*

年轻代(Young Generation)、年老代(Old Generation)和持久代(Permanent Generation)。其中持久代主要存放的是Java类的类信息,与垃圾收集要收集的Java对象关系不大。年轻代和年老代的划分是对垃圾收集影响比较大的。

*年轻代:*

所有新生成的对象首先都是放在年轻代的。年轻代的目标就是尽可能快速的收集掉那些生

命周期短的对象。年轻代分三个区。一个Eden区,两个 Survivor区(一般而言)。大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到Survivor区(两个中的一个),当这个 Survivor区满时,此区的存活对象将被复制到另外一个Survivor区,当这个Survivor去也满了的时候,从第一个Survivor区复制过来的并且此时还存活的对象,将被复制“年老区(Tenured)”。需要注意,Survivor的两个区是对称的,没先后关系,所以同一个区中可能同时存在从Eden复制过来对象,和从前一个Survivor复制过来的对象,而复制到年老区的只有从第一个Survivor去过来的对象。而且,Survivor区总有一个是空的。同时,根据程序需要,Survivor区是可以配置为多个的(多于两个),这样可以增加对象在年轻代中的存在时间,减少被放到年老代的可能。

*年老代:*

在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。

*持久代:*

用于存放静态文件,如今Java类、方法等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如Hibernate 等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。持久代大小通过-XX:MaxPermSize=进行设置。

*注意:*

JDK1.8中,永久代已经从java堆中移除,String直接存放在堆中,类的元数据存储在meta space中,meta space占用外部内存,不占用堆内存。可以说,在java8的新版本中,持久代已经更名为了元空间(meta space)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值