jvm之垃圾收集器与内存分配

对象已死吗

引用计数算法

给对象添加一个医用计数器,每当有一个地方引用它时,计数器就加1 ;当引用失效时,计数器值就减1。虚拟机并不是通过引用计数算法来判断对象是否存活的。

可达性算法

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

Java语言中,可作为GCRoots的对象包括下面几种:

虚拟机栈(栈侦中的本地变量表)中引用的对象。

方法区中类静态属性引用的对象

方法区中常量引用的对象

本地方法栈中Native方法(JNI)引用的对象

 

再谈引用

Java对引用的概念进行了扩充,将引用分为强引用(只要强引用在,垃圾收集器就永远不会回收掉被引用的对象),软引用(系统将要发生内存溢出异常时,将会把这些对象列进回收范围作为第二次回收),弱引用(被弱引用关联的对象只能生存到下一次垃圾收集之前,无论内存是否足够),虚引用。

 

生存还是死亡

如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链,那它就会被第一次标记,并且进行筛选,筛选的条件是此对象是否有必要执行finalize()方法。当对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用,视为“没有必要执行”,如果有必要执行finalize()方法,这个对象将会放置在F-Queue队列中,如果对象重新在finalize()中重新与引用链上的任何一个对象建立关联即可,在第二次标记时,它将被移除“即将回收”的集合,否则基本上就真的回收了。

 

回收方法区:

永久代的垃圾收集主要回收废弃常量和无用的类。

收废弃常量:当前系统中没有任何String对象引用常量池中的常量,也没有其它地方引用这个字面量,就会被清出常量池。

无用的类:

该类所有实例都已经被回收

加载该类的ClassLoader已经被回收

该类对应的Class对象没有在任何地方被引用。无法在任何地方通过反射访问该类的方法。

 

垃圾收集算法

标记-清除算法

算法分为“标记”和“清除”两个阶段:首先要标记出所需要回收的对象,在标记完成后统一回收所有被标记的对象。(标记和清除两个效率不高)

复制算法

将可用内存按容量分为大小相等的两块,每次只使用其中一块,当其中一块内存用完了,就将还存活着的对象复制到另一块上面,然后再把已使用过的内存空间一次清掉。(只是将内存区缩小了一半)

标记整理算法

与“标记-清除”算法后续步骤不同,不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。

分代收集算法

根据对象存活周期的不同将内存划分为几块。一般是把java堆分为新生代和老年代。新生代中,每次垃圾收集时都发现有大批对象死去,选用复制算法,只需要付出少量存活对象的复制成本。老年期,对象存活率高,就使用‘标记-清除’或者‘标记-整理’算法来进行回收。

Hotspot的算法实现

安全点:

什么是SafePoint?

SafePoint是Java代码中的一个线程可能暂停执行的位置。SafePoint保存了在其他位置没有的一些运行时信息。SafePoint保存了线程上下文中的任何东西,包括对象,指向对象或非对象的内部指针

何时进入SafePoint

从线程状态的角度看,Waiting/Idle/Blocked/Running native code是处于SafePoint的,Running Java code是处在非SafePoint的状态。处于Safepoint时,Heap不能访问,Java代码不能执行。我们的以下行为会导致进入SafePoint: 新生代耗尽,大对象分配导致的老年代耗尽,进入同步块等。

SafePoint是如何工作的呢?

在JVM中,SafePoint协议是协同的。每个线程通过检查SafePoint数据结构的状态来确定是否需要暂停自己来进入安全态。

对于编译的代码,JIT通过在代码中适当的位置插入SafePoint检查的代码(通常在调用的返回和循环的退出)

对于解释的代码,JVM保存了两张字节码的执行表,如果需要进行SafePoint检查,JVM会在两张表之间切换。

SafePoint对性能有何影响

SafePoint也可能因为以下因素被延时:大循环,大的内存拷贝(System.arrayCopy/Unsafe.copyMemory),线程中断,Page Fault。从非safePoint到SafePoint的时间长,可能意味着更长的SWT时间,如果需要GC,有可能因为某个线程一直没有到达SafePoint而导致GC线程无法工作。

如何查看JVM SafePoint行为

使用JVM 参数:

-XX:+PrintSafepointStatistics

–XX:PrintSafepointStatisticsCount=1

举根节点

GC停顿
在进行对象的可达性分析时,必须在一个能确保一致性的快照中进行(一致性即在整个分析期间,整个执行系统不可以出现分析过程中,对象引用关系还在不断变化的过程),该点不满足的话分析结果准确性就无法得到保证。所以GC进行时必须停顿所有Java线程。所以枚举根节点时必须要停顿。

HotSpot枚举的实现
在HotSpot的实现中,是使用一组称为OopMap(Ordinary Object Pointer:普通对象指针)的数据结构来实现的。在类加载完成的时候,HotSpot就把对象内什么偏移量上是什么类型的数据计算出来,在JIT编译过程中,也会在特定的位置记录下栈和寄存器中哪些位置是引用。所以GC扫描时就可以直接得知这些信息。

安全点(Safepoint)

安全点的位置的作用及选取 
安全点即何时生成OopMap的位置。程序执行时并非在所有地方都停顿下来开始GC,只有在到达安全点时才能停顿。安全点的选定基本是以程序“是否具有让程序长时间执行的特征”为标准进行选定的--因为每条指令执行的时间都非常短暂,程序不太可能因为指令流长度太长这个原因而过长时间运行,“长时间执行”的最明显特征就是指令序列复用,例如方法调用、循环跳转、异常跳转等,所以这些功能的指令才会产生Safepoint。

如何让所有的线程在最近的安全点停顿下来
抢先式中断(Preemptive Suspension):不需要线程的执行代码主动去配合,在GC发生时,首先把所有线程全部中断,如果发现有线程中断的地方不在安全点上,就恢复线程,让他跑到安全点上。现在已不采用此方式。
主动式中断(Voluntary Suspension):当GC需要中断线程的时候,不直接对线程操作,仅仅简单地设置一个标志,各个线程执行时主动去轮询这个标志,发现中断标志为真时就自己中断挂起。轮询标志的地方和安全点是重合的,另外再加上创建对象需要分配内存的地方。

安全域(Safe Region)

safepoint机制保证了程序执行时,在不太长的时间内就会遇到可进入GC的safepoint。但是程序不执行的时候,没有分配CPU时间,典型的例子就是线程处于sleep状态或者blocked状态,这时候线程无法响应JVM的中断请求,走到安全的地方去中断挂起。所以这就需要安全域来解决。

安全域是指在一段代码片段之中,引用关系不会发生变化。在这个区域中的任意地方开始GC都是安全的。可以把Safe region 看做是扩展了的Safepoint。
在线程执行到Safe Region中的代码时,首先表示自己已经进入Safe region,在哪段时间里JVM要发起GC时,就不用管标识自己为Safe region状态的线程了。在线程要离开Safe Region时,要检查系统是否已经完成了根节点枚举或者是整个GC过程,如果完成了,那线程就继续执行,否则它就必须等待直到收到可以安全离开Safe Region的信号为止。

 

垃圾收集器

                                    

 

serial收集器

Serail收集器是最基本的,发展历史最悠久的收集器,单线程的收集器,在它进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束。

parnew收集器

为Serial收集器的多线程版本,其他行为与Serial收集器一样。

parallel scavenge 收集器

新生代收集器,使用复制算法的收集器,也是并行的多线程收集器,Parallel Scav收集器的目标是达到一个可控制的吞吐量。

吞吐量:CPU用于运行用户代码的时间与CPU总消耗时间的比值。

吞吐量=运行用户代码时间/(用户运行代码时间+垃圾收集时间)

Paraller Scavenge提供了两个参数用于精确控制吞吐量:控制最大垃圾收集停顿时间-XX:MaxGCPauseMills 直接设置吞吐量大小的:-XX:GCTimeRatio。

-XX:+UseAdaptiveSizePolicy的参数开关,GC自适应的调节策略。

serial old 收集器

Serial收集器的老年代版本,单线程收集器,使用“标记-整理”算法。

用途1:在JDK1.5 以及以前的版本中与Parallel Scavenge 收集器搭配使用

用途2:作为CMS收集器的后备预案,在并发收集发生Concurrent Failure时使用。

parallel old 收集器

Parallel Old是Parallel Scavenge 收集器的老年代版本,使用多线程“标记-整理”算法。这个收集器在JDK1.6中才开始使用。直到Parallel Old收集器出现后,“吞吐量优先”收集器终于有了比较名副其实的应用组合,在注重吞吐量以及CPU资源敏感的场合,优先考虑Parallel Scavenge 加Parallel Old收集器。

cms 收集器

CMS收集器是一种以获取最短回收停顿时间为目标的收集器。(目前大部分的java应用集中在互联网或B/S系统的服务端上,服务响应速度,系统停顿时间最短

运作过程:

1初始标记(仅仅标记GC Roots直接关联到的对象速度很快)

2并发标记(GC Roots tracing 过程)

3重新标记(修正并发标记期间用户程序继续运作而导致标记产生变动的那一部分对象的标记记录)

4并发清除

缺点:

1.CMS对CPU资源敏感,会因为占用了部分线程导致应用程序变慢,总吞吐量会降低。

CMS默认启动回收线程数比例(CPU数量+3)/4,当CPU数量小,这个时候CMS对用户程序的影响就会很大

2.CMS 无法处理浮动垃圾(程序运行还会有新的垃圾产生)

3.CMS是基于“标记-清除”的收集器。收集结束时会有大量空间碎片。

 

g1 收集器

G1具备特征:

并发与并行

分代收集

空间整合:基于标记-整理算法实现的收集器

可预测的停顿

使用G1收集器时,Java堆的内存布局就与其他收集器有很大差别,它将整个Java堆划分为多个大小相等的Region区域。虽然保留新生代和老年代概念但是,不存在物理隔离,都是一部分Region区域。

G1建立可预测的停顿时间,因为可有计划的避免在整个Java堆进行全区域的垃圾回收。

可以跟踪每个Region区域的垃圾对接价值大小(回收所获得的空间大小以及回收所需时间的预测值),后台维护一个优先列表,每次根据运行的收集时间,优先回收价值最大的Region(Garbage-First名称由来)

G1中Region都有一个与之对应的Remembered Set ,检查Reference引用的对象是否处于不同的Region之中。把相关信息存在Remembered Set中,在GC跟节点的枚举中加入Remembered Set可保证不对全堆扫描也不会遗漏

G1收集器的运作大致可划分为:

初始标记

并发标记

最终标记

筛选标记

理解gc日志

最前面的33.125代表了GC发生的时间(经过的秒数)

GC和Full GC 说明了这次垃圾收集的停顿类型。

有“Full”,说明发生了stop the wrold(用户线程全部停止)

DefNew 表示Serial收集器的新生代名

ParNew表示ParNew收集器

PSYoungGen表示Parallel Scavenge收集器。

3324K->152K(3712K)含义是GC前该内存区域已使用容量->GC 后该内存区域已使用容量(该内存区域总容量)

而3324K->152K(11904K)表示GC前Java堆已使用容量->GC 后Java堆已使用容量(Java堆总容量)

0.0025925secs表示该内存区域GC所占用的时间,单位是秒。

内存分配与回收策略

Minor GC:新生代内存不足会触发的垃圾回收动作

Major GC:老年代的GC,当老年代内存不足,或者预测新生代的对象总空间大于老年代最大可用的连续空间,或者担保失败,检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小。触发Major GC .

Full GC:指发生在老年代和新生代的GC,速度很慢,需要Stop The World。

对象优先在Eden分配

大对象直接进入老年代(需要连续内存空间的Java对象,很长的字符串或者数组)

-XX:PretenureSizeThread 设置为3MB,这样超过3MB的对象就会直接在老年代中分配。

长期存活的对象将进入老年代

-XX:MaxTenuringThreshold

如果MaxTenuringThreshold设置成1 ,对象在第二次GC发生时进入老年代。

动态对象年龄判断

如果Survivor空间中相同年龄所有的对象大小总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到MaxTenuringThreshold要求的年龄

空间分配担保

虚拟机会检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果条件不成立,则会查看HandlePromotionFailure设置值是否允许担保失败。

担保成功条件:

检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小

JDK6以后没有使用这个参数

 

JVM基本参数

  1. 内存大小

-Xmx20m/-Xms5m

当下Java应用最大可用内存为20M,最小内存为5M

-Xmn新生代的设置

-XX:NewRatio新生代和老年代的比例

-XX:SurvivorRatio新生代内部Survivor和Eden区域的比例 两个Survivor:Eden =2:8

-verbose:gc 表示输出虚拟机中GC的详细情况

-XX:+PrintGCDetails 打印GC详情信息

 

jvm区域总体分两类,heap区和非heap区。heap区又分:Eden Space(伊甸园)、Survivor Space(幸存者区)、Tenured Gen(老年代-养老区)。 非heap区又分:Code Cache(代码缓存区)、Perm Gen(永久代)、Jvm Stack(java虚拟机栈)、Local Method Statck(本地方法栈)。

-XX:+UseSerialGC 使用Serial +Serial的收集器组合进行垃圾回收

以上信息方便测试以及查看gc日志

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值