JVM学习笔记(二)之内存管理

三、对象的内存布局

内容包括三部分:对象头、实例数据、对齐填充

对象头:1、存储自身运行时数据,如哈希码,GC分代年龄……(Mark Word);

                 2、类型指针,确定对象是哪个类的实例;

                 3、如果是数组对象还需一块记录数组长度的数据。

实例数据:对象真正存储的有效信息

对齐填充:不是必然存在的,起着占位符的作用。内存管理系统要求对象起始地址必须是8字节的整数倍,对象头是8字节的倍数,实例数据不一定是8字节的倍数,需要对齐填充来占位。

 

对象的访问定位:通过reference数据操作对象

1、通过句柄访问:reference存储的是对象的句柄地址,句柄包含对象实例数据(实例池)和类型数据(方法区)的地址信息。

2、直接指针方式:reference保存的就是对象地址

两者比较:

句柄reference中存储的是稳定的句柄地址,不会随着对象的改变而改变,对象改变只会改变句柄中实例数据的指针,句柄本身不会变。

直接指针最大的好处是速度快,节省了一次指针定位的时间开销。

补充:VM Args:-Xms20m –Xmx20m 前者指的是堆的最小值,后者指的是堆的最大值。

四、    垃圾收集器

程序计数器、虚拟机栈、本地方法栈三个区域随线程而生随线程而死,每一个栈帧中分配多少内存在类结构确定下来时就已知了。这几个区域的内存分配与回收都具有确定性。

Java堆和方法区:一个接口的多个实现类的内存可能不一样,所以在运行的时候才能知道创建具体对象,所以是动态分配和回收内存的。垃圾回收器所关注的主要也是这两块。

五、    对象是否存活

1、 引用计数算法(java虚拟机不用)

给对象中添加一个引用计数器,每当有一个地方引用它,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的,就会被垃圾回收器回收。

2、 可达性分析算法

讲一些列称为“GC Roots”的对象作为起始点,从这个起始点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。

可作为GC Roots的对象包括:1、虚拟机栈中引用的对象;2、方法区中类静态属性引用的对象;3、方法区中常量引用的对象;4、本地方法栈引用的对象。

六、    对象死亡

一个对象真正死亡至少需要经历两次标记过程:第一次是在可达性分析中,若不可达标记一次;第二次是当对象没有覆盖finalize()方法或虚拟机已经调用过finalize()方法。(任何一个对象的finalize()方法只会被系统自动调用一次)

七、    垃圾收集算法

标记-清除算法:先标记所有需要回收的对象,标记完成后统一回收所有被标记的对象。

复制算法(适用于存活对象少,回收对象多的情况):将可用内存按容量分为大小相等的两块,每次只是用其中一块(1)。当一块内存用完了,就将还存活着的对象复制到另外一块上面(2),然后再把使用过的内存空间一次清理掉。此时内存块(1)就是保留区域,内存块(2)为使用中的内存块。依次不断切换。

标记-整理算法(适用于存活对象多,回收对象少的情况):标记过程和“标记-清除算法”一样,后续步骤是让存活的对象都向一端移动,然后直接清理掉边界以外的内存。

分代收集算法:新生代和老年代。新生代用复制算法(但是是将新生代划分为8:1:1的eden和两个survivor的空间),老年代中用标记-清除或标记-整理算法。

注意:为什么用两个survivor?当第一次进行Minor GC时,会将存活的对象保存到survivorA;当第二次进行Minor GC时,survivorA空间存活的对象就会被保存到survivorB,然后清除eden和survivorA;当第三次进行Minor GC时,将survivorB存活的对象保存到survivorA。如此反复,保证总有一个survivor是空闲的,从而保证内存存放连续无碎片。

下面是Eden空间和两块Survivor空间的工作流程。引自杰风居的理解Java垃圾回收机制

// 分配了一个又一个对象
放到Eden区
// 不好,Eden区满了,只能GC(新生代GC:Minor GC)了
把Eden区的存活对象copy到Survivor A区,然后清空Eden区(本来Survivor B区也需要清空的,不过本来就是空的)
// 又分配了一个又一个对象
放到Eden区
// 不好,Eden区又满了,只能GC(新生代GC:Minor GC)了
把Eden区和Survivor A区的存活对象copy到Survivor B区,然后清空Eden区和Survivor A区
// 又分配了一个又一个对象
放到Eden区
// 不好,Eden区又满了,只能GC(新生代GC:Minor GC)了
把Eden区和Survivor B区的存活对象copy到Survivor A区,然后清空Eden区和Survivor B区
// ...
// 有的对象来回在Survivor A区或者B区呆了比如15次,就被分配到老年代Old区
// 有的对象太大,超过了Eden区,直接被分配在Old区
// 有的存活对象,放不下Survivor区,也被分配到Old区
// ...
// 在某次Minor GC的过程中突然发现:
// 不好,老年代Old区也满了,这是一次大GC(老年代GC:Major GC)
Old区慢慢的整理一番,空间又够了
// 继续Minor GC
// ...
// ...

八、    引用

强引用:类似“Object obj = newObject()”这类的引用,只要强引用还在,垃圾收集器永远不会收掉被引用的对象;

软引用:在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收;

弱引用:当垃圾收集器工作时,无论内存是否足够,都会回收掉只被弱引用关联的对象;

虚引用:为一个对象设置虚引用的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。

九、关于新生代与老年代引自http://www.th7.cn/Program/java/201409/276272.shtml

Java 中的堆是 JVM 所管理的最大的一块内存空间,主要用于存放各种类的实例对象。
在 Java 中,堆被划分成两个不同的区域:新生代 ( Young )、老年代 ( Old )。新生代 ( Young ) 又被划分为三个区域:Eden、From Survivor、To Survivor。
这样划分的目的是为了使 JVM 能够更好的管理堆内存中的对象,包括内存的分配以及回收。
堆的内存模型大致为:

 

Eden

(8/10)

From

(1/10)

To

(1/10)

Old

Young                              old

从图中可以看出:堆大小 = 新生代 + 老年代。其中,堆的大小可以通过参数 –Xms、-Xmx 来指定。
(本人使用的是 JDK1.6,以下涉及的 JVM 默认值均以该版本为准。)
默认的,新生代 ( Young ) 与老年代 ( Old ) 的比例的值为 1:2 ( 该值可以通过参数 –XX:NewRatio 来指定 ),即:新生代 ( Young ) = 1/3 的堆空间大小。老年代 ( Old ) = 2/3 的堆空间大小。其中,新生代 ( Young ) 被细分为 Eden 和两个 Survivor 区域,这两个 Survivor 区域分别被命名from 和 to,以示区分。
默认的,Edem : from : to= 8 : 1 : 1 ( 可以通过参数 –XX:SurvivorRatio 来设定 ),即: Eden = 8/10 的新生代空间大小,from = to = 1/10 的新生代空间大小。
JVM 每次只会使用 Eden 和其中的一块 Survivor 区域来为对象服务,所以无论什么时候,总是有一块 Survivor 区域是空闲着的。
因此,新生代实际可用的内存空间为 9/10 ( 即90% )的新生代空间。

Java 中的堆也是 GC 收集垃圾的主要区域。GC 分为两种:Minor GC、Full GC ( 或称为 Major GC )。
Minor GC 是发生在新生代中的垃圾收集动作,所采用的是复制算法
新生代几乎是所有 Java 对象出生的地方,即 Java 对象申请的内存以及存放都是在这个地方。Java 中的大部分对象通常不需长久存活,具有朝生夕灭的性质。
当一个对象被判定为 "死亡" 的时候,GC 就有责任来回收掉这部分对象的内存空间。新生代是 GC 收集垃圾的频繁区域。
当对象在 Eden ( 包括一个 Survivor 区域,这里假设是 from 区域 ) 出生后,在经过一次 Minor GC 后,如果对象还存活,并且能够被另外一块 Survivor 区域所容纳( 上面已经假设为 from 区域,这里应为 to 区域,即 to 区域有足够的内存空间来存储 Eden 和 from 区域中存活的对象 ),则使用复制算法将这些仍然还存活的对象复制到另外一块 Survivor 区域 ( 即 to 区域 ) 中,然后清理所使用过的Eden 以及 Survivor 区域 ( 即from 区域 ),并 且将这些对象的年龄设置为1,以后对象在Survivor 区每熬过一次 Minor GC,就将对象的年龄 + 1,当对象的年龄达到某个值时 ( 默认是 15 岁,可以通过参数 -XX:MaxTenuringThreshold 来设定 ),这些对象就会成为老年代。
但这也不是一定的,对于一些较大的对象 ( 即需要分配一块较大的连续内存空间 ) 则是直接进入到老年代
Full GC 是发生在老年代的垃圾收集动作,所采用的是标记-清除算法
现实的生活中,老年代的人通常会比新生代的人 "早死"。堆内存中的老年代(Old)不同于这个,老年代里面的对象几乎个个都是在 Survivor 区域中熬过来的,它们是不会那么容易就 "死掉" 了的。因此,Full GC 发生的次数不会有 Minor GC 那么频繁,并且做一次 Full GC 要比进行一次 Minor GC 的时间更长。
另外,标记-清除算法收集垃圾的时候会产生许多的内存碎片 ( 即不连续的内存空间 ),此后需要为较大的对象分配内存空间时,若无法找到足够的连续的内存空间,就会提前触发一次 GC 的收集动作。

-Xms

初始堆大小。如:-Xms256m

-Xmx

最大堆大小。如:-Xmx512m

-Xmn

新生代大小。通常为 Xmx 的 1/3 或 1/4。新生代 = Eden + 2 个 Survivor 空间。实际可用空间为 = Eden + 1 个 Survivor,即 90% 

-Xss

JDK1.5+ 每个线程堆栈大小为 1M,一般来说如果栈不是很深的话, 1M 是绝对够用了的。

-XX:NewRatio

新生代与老年代的比例,如 –XX:NewRatio=2,则新生代占整个堆空间的1/3,老年代占2/3

-XX:SurvivorRatio

新生代中 Eden 与 Survivor 的比值。默认值为 8。即 Eden 占新生代空间的 8/10,另外两个 Survivor 各占 1/10 

-XX:PermSize

永久代(方法区)的初始大小

-XX:MaxPermSize

永久代(方法区)的最大值

-XX:+PrintGCDetails

GC 信息

-XX:+HeapDumpOnOutOfMemoryError

让虚拟机在发生内存溢出时 Dump 出当前的内存堆转储快照,以便分析用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值