深入理解JVM之JVM内存区域与内存分配

JAVA虚拟机把管理的内存划分为几个不同的数据区。

Java堆-存放new的对象和数组(jvm不定时查看这个对象,如果没有引用指向这个对象就回收)

Java堆是被所有线程共享的一块内存区域,主要用于存放对象实例,Java虚拟机规范中有这样一段描述:所有的对象实例和数据都要在堆上进行分配。

为对象分配内存方式:指针碰撞法(内存完整时)、空闲列表法

        Java堆的内存并不是完整的,已分配的内存和空闲内存相互交错,JVM通过维护一个列表,记录可用的内存块信息,当分配操作发生时,从列表中找到一个足够大的内存块分配给对象实例,并更新列表上的记录。

        对象创建是一个非常频繁的行为,进行堆内存分配时还需要考虑多线程并发问题,可能出现正在给对象A分配内存,指针或记录还未更新,对象B又同时分配到原来的内存,解决这个问题有两种方案:

1、采用CAS保证数据更新操作的原子性;
2、把内存分配的行为按照线程进行划分,在不同的空间中进行,每个线程在Java堆中预先分配一个内存块,称为本地线程分配缓冲(Thread Local Allocation Buffer, TLAB);

对于堆的GC机制:

JVM区域总体分两类,heap区非heap区

其中heap区又分为:Eden Space(伊甸园)、SurvivorSpace(幸存者区)、Tenured Gen(老年代-养老区)Perm Gen(永久代)。GC(gabage collection)

非heap区又分:Code Cache(代码缓存区)、Perm Gen(永久代)、Jvm Stack(java虚拟机栈)、Local Method Statck(本地方法栈)。--在java栈中会涉及。


1、new Object() 后会放入在Eden Space,GC会定期查看该对象是否有引用,如果没有回收该内存。如果有会进入Survivor Space。

2、对象进入Survivor Space 后 GC还会定期(可以自定义)来查看引用状况,如果有的话就让其进入了Tenured Gen。如果没有直接kill掉。

3、进入到Tenured Gen的对象基本就可以很安全啦,但如果发现没有引用,GC还是会kill掉。

分区的目的:Eden Space由于对象产生的比较多并且大都是朝生夕灭的,所以直接采用标记-清理算法。而Tenured Gen生命力很强,则采用复制算法,针对不同情况使用不同算法。

通知GC销毁对象 : obj  = null ;   System.gc(); 

GC工作机制

SUN的jvm内存池被划分为以下几个部分:

1、Eden Space (heap)  内存最初从这个线程池分配给大部分对象。

2、Survivor Space (heap) 用于保存在eden space内存池中经过垃圾回收后没有被回收的对象。

3、Tenured Generation (heap用于保持已经在survivor space内存池中存在了一段时间的对象。


4、Permanent Generation (non-heap保存虚拟机自己的静态(reflective)数据,例如类(class)和方法(method)对象。Java虚拟机共享这些类数据。这个区域被分割为只读的和只写的。

5、Code Cache (non-heapHotSpot Java虚拟机包括一个用于编译和保存本地代码(native code)的内存,叫做“代码缓存区”(code cache)。

>>综上 jvm的内存回收过程是这样的:

对象在Eden Space创建,当Eden Space满了的时候,gc就把所有在Eden Space中的对象扫描一次,把所有有效的对象复制到第一个Survivor Space,同时把无效的对象所占用的空间释放。当Eden Space再次变满了的时候,就启动移动程序把Eden Space中有效的对象复制到第二个Survivor Space,同时,也将第一个Survivor Space中的有效对象复制到第二个Survivor Space。如果填充到第二个Survivor Space中的有效对象被第一个Survivor Space或Eden Space中的对象引用,那么这些对象就是长期存在的,此时这些对象将被复制到Permanent Generation。

若垃圾收集器依据这种小幅度的调整收集不能腾出足够的空间,就会运行Full GC,此时jvm gc停止所有在堆中运行的线程并执行清除动作。


Java栈-存放函数的参数值,局部变量的值等,方法执行结束后自动回收

Java栈为线程私有。

    每个线程对应一个Java栈(并为其分配一个Program Counter Register 程序计数器,VM Stack 虚拟机栈 和 Native Method Stack 本地方法栈,当线程终止时,三者(虚拟机栈,本地方法栈程序计数器)所占用的内存空间也会被释放掉。这也是为什么我把内存区域分为线程共享和非线程共享的原因,非线程共享的那三个区域的生命周期与所属线程相同,而线程共享的区域与JAVA程序运行的生命周期相同,所以这也是系统垃圾回收的场所只发生在线程共享的区域(实际上对大部分虚拟机来说知发生在Heap上)的原因。

    当线程在执行一个方法时会创建一个对应的栈帧(Stack Frame)。栈帧负责存储局部变量变量表、操作数栈、动态链接和方法返回地址等信息。每个方法的调用过程,相当于栈帧在Java栈的入栈和出栈过程。


其中局部变量表:

    存放方法参数和方法局部变量---其大小在代码编译期间已经确定。局部变量表以变量槽(Slot)为最小存储单位(每个Slot能够存放一个boolean、byte、char、shot、int、float、reference和returnAddress类型的32位数据,对于64位的数据类型long和double,虚拟机会以高位对齐的方式为其分配两个连续的Slot空间)。在方法执行时,如果是实例方法,即非static方法,局部变量表中第0位Slot默认存放对象实例的引用,在方法中可以通过关键字 this 进行访问,方法参数按照参数列表顺序,从第1位Slot开始分配,方法内部变量则按照定义顺序进行分配其余的Slot。


对应的局部变量表如下:


javac test.java —>javap -c test 命令查看方法calc的字节码


其中iload_1和iload_2分别从局部变量表中的第1位和第2位中加载数据。

方法区-(也叫:共享数据区,静态区)—— 存放全局变量,静态变量和字符串常量,和方法,不释放。
方法区和Java堆一样,是所有线程共享的内存区域,用于存放已被虚拟机加载的类信息、常量、静态变量和即时编译器编译后的代码等数据。运行时常量池是方法区的一部分,用于存放编译期间生成的各种字面常量和符号引用。

代码区-(又叫本地方法区)调用系统底层内容

存放程序中方法的二进制代码,多个对象共享一个代码空间区域。

指令计数器(程序计数器)

指令计数器是线程私有的,每个线程都有独立的指令计数器,计数器记录着虚拟机正在执行的字节码指令的地址,分支、循环、跳转、异常处理和线程恢复等操作都依赖这个计数器完成。如果线程执行的是native方法,这个计数器则为

对象的内存布局
对象在内存中布局可以分成三块区域:对象头、实例数据和对齐填充。

1、对象头

对象头包括两部分信息:运行时数据和类型指针,如果对象是一个数组,还需要一块用于记录数组长度的数据。

1.1、运行时数据包括哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向锁ID和偏向时间戳等,这部分数据在32位和64位虚拟机中的长度分别为32bit和64bit,官方称为"Mark Word"。Mark Word被设计成非固定的数据结构,以实现在有限空间内保存尽可能多的数据。

32位的虚拟机中,对象未被锁定的状态下,Mark Word的32bit中25bit存储对象的HashCode、4bit存储对象分代年龄、2bit存储锁标志位、1bit固定为0,具体如下:

其它状态(轻量级锁定、重量级锁定、GC锁定、可偏向锁)下Mark Word的存储内容如下:

1.2、对象头的类型指针指向该对象的类元数据,虚拟机通过这个指针可以确定该对象是哪个类的实例。

2、实例数据

实例数据就是在程序代码中所定义的各种类型的字段,包括从父类继承的,这部分的存储顺序会受到虚拟机分配策略和字段在源码中定义顺序的影响。
3、对齐填充

由于HotSpot的自动内存管理要求对象的起始地址必须是8字节的整数倍,即对象的大小必须是8字节的整数倍,对象头的数据正好是8的整数倍,所以当实例数据不够8字节整数倍时,需要通过对齐填充进行补全。



其他参考链接:

http://www.cnblogs.com/wangjzh/p/5258254.html

http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=29632145&id=4616836





































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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值