jvm学习笔记(一):内存管理机制

运行时数据区

程序计数器

jvm虽然号称多线程,但是实际是通过cpu切换实现的,但从A线程切换到B线程是,A线程会暂停,在切回A线程时,A线程上次执行到哪,程序计数器就是记录这些信息的。线程私有

线程私有,每个java方法执行的时候都会对应创建一个栈,用来存放方法局部变量等信息。生命周期随方法的结束而结束。

本地方法栈

和栈基本一样,只是执行的java方法为native方法

用来存放对象实例,是GC回收的主要地方,因为现在垃圾回收器都采用分代回收,所以从内存回收角度看堆又分为新生代、老年代、持久代,在细分Edon和from survivor和to survivor。

方法区

存放类加载信息、常量、静态变量。

运行时常量池:方法区的一部分,jvm加载类的时候不只有类信息,还有一种常量池,常量池用来存放编译期生成的各种字面量和符号引用。


对象的创建

给对象分配内存对象有两种方法:指针碰撞和空闲列表。

采用哪种方法由java堆是否规整决定,而java堆是否规整由垃圾回收器是否有压缩整理功能决定。


内存分配如何保证线程安全?

1、将分配动作进行同步处理

2、为每个线程单独划分一块线程内存(TLAB)。可以通过命令设置是否使用TLAB


内存分配完成后,接下来将分配到的内存化为零空间都初始值,保证对象实例字段在代码中可以不赋初始化值而直接使用。

设置对象头信息。

最后一步按照代码初始化实例字段的值。


对象的内存布局

对象在内存的存储布局分3块:对象头、实例数据、对齐填充

对象头:包括两部分,一部分存储对象自身数据,如哈希码、GC分代年龄等。另一部存储对象指向元数据的指针。如果是数组则还要存储数组长度。

实例数据:真正的对象实例数据

对齐填充:jvm要求对象必须是8的倍数


对象的访问定位

对象的访问方式两种:句柄和直接指针

句柄:对象的实例信息和类信息都存储在句柄池。稳定

指针:速度快。现在jvm使用





OOM异常

java堆溢出

对象不停创建,堆内存不够时发生。

-XX:+HeapDumpOnOutOfMemoryError参数会让jvm出现内存溢出异常时dump出快照

-Xmx和-Xms设置堆大小

栈溢出

-Xss参数设置栈大小

方法区和运行时常量池溢出

-XX:PermSize和-XX:MaxPermSize参数设置方法区大小,同时间接限制运行时常量池大小。


如何判断对象是否可被回收

引用计数法和可达性分析算法

引用计数法:给对象设置一个计数器,当有一个引用时加1,为0时代表可回收。简答高效,但是存在相互引用的对象无法被回收的问题,因此现在回收器很少使用。

可达性分析算法:现在主流语言都使用这个算法,通过GC Root为根开始向下找,没有引用的为可回收的。

可作为GC Root对象的:栈中引用的对象和方法区中引用的对象


finalize()方法

当发生GC,发现对象不可达,标记对象,放入F-Queen队列中,稍后会由低优先级的Finalizer线程执行这个队列,但是在执行之前会对队列对象第二次标记,如果这次对象由被GC Root引用上了,则对象自救成功,逃脱回收。

不建议使用。


垃圾收集算法

标记清除、复制、标记整理


标记清除:低效、产生大量内存碎片


复制:会牺牲内存。现在虚拟机的新生代采用这种方法,新生代分1个Eden和两个Survivor,比例8比1,每次使用Eden和一个Survivor,发生GC的时候会把Eden和使用着的Survivor中对象复制到另一个Survivor,然后清空Eden和使用着Survivor,最后对调Survivor。适合GC存活对象较少时。新生代每次都会回收掉大量内存,所以适合这种方法。

Eden和Sur比例为8:1,所以新生代内存使用率达到百分之90,但是存在一个问题,当GC时存活对象超过百分10怎么办?

分配担保:对象直接通过分配担保机制进入老年代


标记整理:当每次回收掉的内存较少时,如果还采用复制算法则效率就低下,此时采用标记整理,适合老年代


枚举根节点(oopMap)

每次GC的时候都要通过可达性分析算法先分析出哪些对象可被回收,但如果每次都全栈、方法区的扫描GC Root的话会效率很低,因此引入OOPMap数据结构。

oopmap:

根枚举的时候只需要扫描栈中的引用类型的,不需要基本类型,在某个时候把栈上代表引用的位置全部记录下来,这样到真正 gc 的时候就可以直接读取,而不用再一点一点的扫描了。事实上,大部分主流的虚拟机也正是这么做的,比如 HotSpot ,它使用一种叫做 OopMap 的数据结构来记录这类信息。 

实现准确GC。

http://rednaxelafx.iteye.com/blog/1044951


垃圾收集器

Serial、ParNew、Parallel Seavenge、 Parallel Old、Serial Old、CMS 、G1

Serial

最古老的收集器,单线程,新生代,工作时必须 stop the world,但是对于单cpu环境简单高效,对于client模式下的虚拟机适合选择

ParNew

Serial的多线程版本,除了多线程外其他和Serial完全一样,但是有一个优势:当老年代使用CMS时,新生代只能选择Serial或者ParNew。单CPU环境肯定不比Serial强,但多CPU就牛逼了。可以使用:-XX:ParallelGCThreads来限制线程数

Parallel Seavenge

新生代收集器,和ParNew比较:注重的是吞吐量。什么是吞吐量?比如jvm运行100分钟,垃圾回收总共用了2分钟,用户代码运行98分钟,则吞吐量就是 98/100。所以适合后台运算,不适合用户交互。

两个参数:-XXMaxGCPauseMillis  最大收集时间,毫秒

-XX:GCTimeRatio  设置吞吐量大小,0到100整数,垃圾收集占总时间比率,也就是上面举例则为 2。

自适应参数策略:   -XX:+UseAdaptiveSizePolicy  是一个开关,控制jvm自动调节参数。

CMS

老年代收集器,目标是尽可能降低用户停顿时间。分四个阶段


缺点:1、并发标记阶段会抢占用户线程的cpu,可能降低用户线程速度

2、产生浮动垃圾:也就是在并发清除阶段还会产生新的垃圾,所以CMS不能再老年代将被用完时在执行gc,需要预留空间。

参数:-XX:CMSInitialtingOccupancyFraction   默认当老年代空间被使用百分68时执行GC,这是一个保守设置,可以适当调高该参数

3、因为使用的是标记-清楚算法,所以会产生大量空间碎片,可能提前触发fullGC。参数:-XX:CMSFullGCsBeforeComparction  设置执行多少次不压缩的full gc后跟着来一次带压缩的

G1

当今最牛逼的收集器,将堆划分为多个大小相等的Region,并且维持一个不同Region回收优先级列表,所以可实现可预测停顿。还未被普遍使用。


虚拟机参数

-XX:+PrintGCDetails   告诉jvm在垃圾回收时打印日志

-XX:SurvivorRatio=8  Eden和Survivor的比例为8比1

-XX:PretenureSizeThreshold   给对象分配内存时,当对象大于这个值,则直接分配到老年代(默认是分配到Eden)

-XX:MaxTenuringThreshold   每个对象有个age计数器,对象降生到Eden,当经历一次Minor GC未被回收则被放到Survivor,此时age为1,以后每经历一次Minor GC 则age加1,当age达到阈值则对象进入老年代,   此参数为该阈值,默认15

(对象进入老年代还有一种情况,当survivor中相同age所有对象大小总和大于survivor空间一半时,则年领大于等于该age的对象也会进入老年代)

空间分配担保

新生代Minor GC会把Eden和from Survivor中存活对象复制到to Survivor,但当存活对象大于to survivor时,jvm首先查看老年代最大连续可用空间是否大于新生代空间总和,如果大于则进行Minor GC并且多余部分进入老年代。如果小于则查看HandlePromotionFailure设置值是否允许担保,不允许则直接full GC。如果允许则判断老年代连续最大可用空间是否大于历次进入老年代对象平均值,如果大于则minor GC,否则fullGC。


























猜你喜欢

转载自blog.csdn.net/u011064905/article/details/80388131