JVM 内存区域

java运行时数据区域


1.程序计数器
程序计数器是一块较小的内存空间,它可以看做是当前线程所执行的字节码的行号指示器。
程序计数器是线程独立的,每个线程拥有独立的程序计数器。
程序计数器内存区域是唯一一个在java虚拟机规范中没有规定任何OutOfMemoryError情况的区域

2.java虚拟机栈
a.线程私有,与线程的生命周期相同。
b.每个方法执行时都创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息
局部变量表:存放编译期可知的各种基本数据类型、对象引用(reference类型)和returnAddress类型(指向了一条字节码指令的地址)。

64位长度的long和double类型的数据会占用2个局部变量空间,其余的数据类型占用1个。
c.有两种异常:如果线程请求的栈深度大于虚拟机所允许的深度,抛出StackOverflowError异常;
如果虚拟机栈可以动态扩展,扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常。

3.本地方法栈

功能与java虚拟机栈相似,区别:虚拟机栈为虚拟机执行java方法服务,而本地方法栈则为虚拟机使用到的Native方法服务。

4.Java堆

a.被所有线程共享的一块内存区域,用于存放对象实例。
b.Java堆是垃圾收集器管理的主要区域,因此很多时候也被称为“GC堆”
c.Java堆中可以细分为:新生代和老年代。
d.线程共享的Java堆中可能划分出多个线程私有的分配缓冲区(TLAB)。

e.Java虚拟机规范规定:Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可。

Java堆区的划分:Eden Space(伊甸园)、Survivor Space(幸存者区)、Tenured Gen(老年代-养老区)。
HotSpot虚拟机GC算法采用分代收集算法:

1、一个人(对象)出来(new 出来)后会在Eden Space(伊甸园)无忧无虑的生活,直到GC到来打破了他们平静的生活。

GC会逐一问清楚每个对象的情况,有没有钱(此对象的引用)啊,因为GC想赚钱呀,有钱的才可以敲诈嘛。

然后富人就会进入Survivor Space(幸存者区),穷人的就直接kill掉。

2、并不是进入Survivor Space(幸存者区)后就保证人身是安全的,但至少可以活段时间。GC会定期(可以自定义)

会对这些人进行敲诈,亿万富翁每次都给钱,GC很满意,就让其进入了Genured Gen(养老区)。万元户经不住几次敲诈

就没钱了,GC看没有啥价值啦,就直接kill掉了。


3、进入到养老区的人基本就可以保证人身安全啦,但是亿万富豪有的也会挥霍成穷光蛋,只要钱没了,GC还是kill掉。

分区的目的:新生区由于对象产生的比较多并且大都是朝生夕灭的,所以直接采用标记-清理算法。而养老区生命力很强,

则采用复制算法,针对不同情况使用不同算法。

非heap区域中Perm Gen中放着类、方法的定义,jvm Stack区域放着方法参数、局域变量等的引用,方法执行顺序按照栈的先入后出方式。

以上转自:http://lhc1986.iteye.com/blog/1421832
以下转自:http://www.cnblogs.com/xhr8334/archive/2011/12/01/2270994.html

5.方法区


a.线程共享的,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
b.通常称为“永久代”。

6.运行时常量池

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

7.直接内存

GC工作机制

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

Eden Space (heap)

内存最初从这个线程池分配给大部分对象。

Survivor Space (heap)

用于保存在eden space内存池中经过垃圾回收后没有被回收的对象。

Tenured Generation (heap)

用于保持已经在survivor space内存池中存在了一段时间的对象。

Permanent Generation (non-heap)

保存虚拟机自己的静态(reflective)数据,例如类(class)和方法(method)对象。Java虚拟机共享这些类数据。

这个区域被分割为只读的和只写的。

Code Cache (non-heap)

HotSpot 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停止所有在堆中运行的线程并执行清除动作。

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

虚拟机对象


1.对象的创建
虚拟机遇到new指令时,
a.类加载:
检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,那必须先执行相应的类加载过程

b.分配内存:为新生对象分配内存,等同于把一块确定大小的内存从Java堆中划分出来。
分配策略:
如果Java堆中内存是绝对规整的,所有用过的内存都放在一边,空闲的内存放到另一边,中间放着一个指针作为分界点的指示器,那所分配内存就仅仅是把那个指针向空闲空间那边挪动一段与对象大小相同的距离。这种分配方式称为“指针碰撞”。
如果Java堆中的内存并不是规整的,已使用的内存和空闲的内存相互交错,虚拟机就必须维护一个列表,记录上哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录,这种分配方式称为“空闲列表”。

注:在划分可用空间的时候,可能出现线程安全问题。由于Java堆是线程共享的,因此在创建多个对象的时候会出现线程安全的问题。解决方法:对分配内存空间的动作进行同步处理;把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在Java堆中预先分配一小块内存,称为本地线程分配缓冲(TLAB)。虚拟机是否使用TLAB,可以通过  -XX:+/-UseTLAB参数来设定。

c.初始化:将分配到的内存空间都初始化为零值。

d.执行<init>方法:执行<init>方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全出来。


2.对象的内存布局

对象在内存中存储的布局可以分为3块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding).

注:对齐填充不是必然存在的。HotSpot VM的自动内存管理系统要求对象起始地址必须是8字节的整数倍,如果对象实例数据部分没有对齐时,就需要通过对齐填充来补全。

3.对象的访问定位

Java程序需要通过栈上的reference数据来操作堆上的具体对象。由于reference类型在Java虚拟机规范中规定了一个纸箱对象的引用,并没有定义这个引用应该通过何种方式去定位。
目前主流的访问方式有使用句柄和直接指针两种:

如果使用句柄访问的话,那么Java堆中将会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据和类型数据各自的具体地址信息。
如果使用直接指针访问,那么Java堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,而reference中存储的直接就是对象地址。

两者各有优势:
句柄访问的最大好处就是reference中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的数据指针,而reference本身不需要修改。

使用直接指针访问方式最大的好处就是更快。

原文地址:https://www.cnblogs.com/lyf906522290/p/8043369.html