JVM1-自动内存管理机制

2019-02-26  本文已影响0人  Gelato_

java与C++之间有一堵由内存动态分配垃圾收集技术所围成的高墙,墙外的人想进去,墙里面的人想出来。

一、JVM把内存分为若干个不同的区域,有的区随着JVM进程的启动而存在,有些区域则依赖用户线程的启动和结束而建立销毁。JVM的运行时数据区域包括以下几个部分:1、程序计数器;2、java虚拟机栈;3、本地方法栈;4、java堆;5、方法区。另外还有1、运行时常量池;2、直接内存。

1、程序计数器是一块较小的内存空间,它是当前线程所执行的字节码行号指示器,字节码解释器就是通过改变这个计数器的值来选取下一条的指令,比如:分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖计数器。每条线程都有一个独立的计数器,所以这块内存是线程私有的区域。如果线程正在执行java方法,那么计数器记录的是字节码指令的地址,如果正在执行Native方法,则为空。此区域不存在OutOfMemoryError

2、java虚拟机栈(Stack)也是线程私有的,他的生命周期和线程相同,改区域是java方法的内存模型,方法在执行时会创建一个栈帧(有局部变量表、操作数栈、动态链接、方法出栈等信息)。如果线程请求的深度太大就抛StackOverFlowError异常,如果扩展时无法申请到足够的内存就抛OutOfMemoryError异常;

3、本地方法栈和虚拟机栈非常类似,只不过虚拟机栈是为java方法(也就是字节码)服务的,而本地方法栈是为Native方法服务的。抛出异常情况也和虚拟机栈一样;

4、java堆(heap)是所有线程共享的一块区域,也是JVM管理的内存中最大的一块,在虚拟机启动时就已经创建。此区域的唯一目的就是存放对象实例。所有的对象实例以及数组都在堆上分配。也是垃圾回收的主要区域。内部可细分为:新生代和老年代。更细一点的划分就是:Eden、From Survive、To Survive等。扩展时申请不到足够的内存就抛OutOfMemoryError异常。

5、方法区(Non-Heap)也是所有线程共享的的区域,它用于储存已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。HotSpot虚拟机把堆中的永久代作为方法区的(JDK1.7后把字符串常量池移除永久代),对于其他虚拟机是不存在永久代概念的。扩展时申请不到足够的内存就抛OutOfMemoryError异常。

6、运行时常量池是方法区的一部分,存放Class文件中的常量池和运行期间产生的常量(String类的intern()方法)。扩展时申请不到足够的内存就抛OutOfMemoryError异常。

7、直接内存并不是运行时数据区的一部分,也不是虚拟机管理的内存区域。本地内存不足也会导致OutOfMemoryError异常。但是linux系统也可能触发OOM机制kill掉内存占用最大的一个进程。

二、对象的内存布局可以分为三块区域:1、对象头(Header);2、实例数据(Instance Data);3、对齐填充(Padding)。

1、对象头包括两部分,第一部分用于储存对象自身的运行时数据如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。另一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个实例是哪个对象的实例。

2、实例数据是对象真正储存的数据,默认分配策略将相同长度的数据分配到一起;

3、对齐填充只是起到占位符的作用,不满8位的补齐。

 

三、对象的访问是通过栈上的reference数据来操作堆上的具体对象或者实例的,访问方式分为两种:1、句柄池;2、直接指针。

1、句柄访问方式,java堆会划分一块区域作为句柄池,reference指向句柄池中的对象地址,句柄池中包含了对象实例数据与类型数据各自的地址。在对象被移动时reference指向的地址不需要改变。

2、直接访问reference中储存对象实例地址。访问速度稍快,开销小。HotSpot使用这种方式来访问对象。

上一篇下一篇

猜你喜欢

热点阅读