被解刨的JVM

36 篇文章 1 订阅
10 篇文章 0 订阅

源于蚂蚁课堂的学习,点击这里查看(老余很给力)    

作为java应用程序的基层员工,JVM总是默默无闻地辛苦工作。年近尾声,年度评优工作开始进展。往年都是框架、并发等员工当选,咱不能总让老实人吃亏。综合解剖一下JVM,看看它做了哪些了不起的事情。(主要针对java内存结构做分析)

一、类加载过程

Java应用程序是不能直接运行的,需要通过java complier进行编译,将其转为class字节码文件,然后交由不同环境的JVM进行运行。那么,JVM到底是干什么的呢?它如何做到存放或创建实例对象的?又是如何进行对象内存回收?别着急,我们先看看这位员工张什么样吧。

 

二、Java内存结构

别说,小伙长得有模有样。

1.线程独占

栈:由栈帧组成,每一个栈帧就是一个方法。

栈帧又分为局部变量表(定义在方法中的局部变量引用或基本类型)、操作数栈(方法的运算)、动态链接和返回地址。(有内存逃逸的现象)

程序计数器:用于标识当前线程到底运行在程序的哪一行,CPU调度切换回来时,线程可以继续运行。

本地方法栈:被native修饰的方法,java与C通讯的方法。

 

2.线程共享

元空间:存放类的字节码信息、静态信息等。

堆:存放创建的对象、字符串常量池。

 

三、堆内存结构

堆属于JVM的消化系统。JVM内存的吞吐绝大部分由堆来控制。

堆内存主要用于存放创建的对象,这些对象根据业务来说寿命各异。对于不同年龄的对象,堆内存为了方便管理,采用分代的方式划分。

1.新生代

顾名思义,存放一些比较稚嫩的对象,需要进过岁月磨炼,长大成人,才能晋升至老年代。

新生代又细分为eden、from和to区。

Eden存放刚刚创建的对象,当Eden为创建出的对象申请内存发现Eden剩余内存不足时,会进行新生代的垃圾回收(minorGC),Eden区中在本次回收时还在被使用着的对象不会被收回内存,成为幸存者,他们的年龄也增长一岁。

From和to是同等大小的两块区域,用作复制算法。Eden中的幸存者进入from(或者to区,本质相同),当再次进行minorGC时,from区中会把当前正在使用的对象放入到to区,然后清空整个from区。To区中的对象年龄加一岁,重复以往,直到新生代中对象的年龄超过一个参数(可配置,默认16)时,这些对象会脱胎换骨,进入到老年代。

其实整个过程可以类比咱们的现实生活:这些新生对象好比求职者,每一轮minorGC就是一轮面试,层层面试选拔,当通过最终面试后,新生对象入职老年代。

当前,并非新生代进入老年代只有一条路,当创建的对象大小超过了整个新生代大小,那么也会直接进入到老年代;还有一些空间担保、动态年龄等也会使得对象直接进入到老年代。好比求职者是一个业内大咖,那么自然可以省去众多面试,直接保送。

 

2.老年代

老年代的对象一般比较稳定,毕竟老员工了,对外业务成熟。当老年代为进入的对象申请内存失败时,触发fullGC,将新生代和老年代中不可达(一种垃圾回收算法)的对象全部剔除,回收内存,然后存放对象;如果多次fullGC后,还是无法满足此对象的存放,则抛出内存溢出的异常信息。

具体的堆内存调配,需要配合各种垃圾回收算法。

 

四、垃圾回收算法

何为垃圾?举个比较有味道的形容。堆内存好比我们的肚子,咱们肚子容量有限,需要定时将消化后的残留物进行排泄。对比堆内存,那些永远不会被使用的对象放在堆内存占据空间,就是垃圾。

Java引以为豪的一点就是无序程序猿手动释放内存,jvm的堆内存通过垃圾收集器结合垃圾回收算法去在合适的时间清除垃圾,回收内存,满足java应用程序的高效运行。

常见的垃圾回收算法如下。

引用标记法

如果此对象被其他对象引用着,则表明此对象不为垃圾,不会对其进行回收。但由于其无法解决循环依赖的问题,所以被淘汰。(即A和B都是垃圾,A中有B,B中有A)

可达性分析法(GCROOT/根搜索法)

堆内存的对象被堆内存外的指针关联着,那么只要是这条链上的堆内存对象,都意味可达,不会被收集。这种方式可以解决循环依赖。例如:A对象被栈帧中的局部变量表的一个指针正在引用着,A中有B,B中有C,那么ABC都可达。

标记清除法

在堆内存空间中,判断对象是否可达,将不可达的对象删除。这种方式会使得堆内存的内存地址不连续,导致堆内存出现很多碎片化的问题,空间利用率不高。例如ABCDE五个对象中BD为垃圾,那么通过标记清除法清理后,堆内存变为A[]C[]E([]为BD占用的内存地址大小),可用的内存地址不连续,寻址繁琐。假设B对象2KB,D对象1KB,那么如果新创建的对象F为4KB,则只能存放在E后面,变为A[]C[]EF,使得空余的空间利用率降低,出现空间碎片。

标记整理法

标记整理法在标记清除法的基础上进行优化,删除不可达对象后,将后面的内存地址向前移动,从而解决碎片化问题。但移动对象的内存地址可能会影响对象的正常使用,为了避免此问题,移位期间会发生停止所有用户线程的情况(Stop-The-World,简称STW)。

标记复制法

标记复制法,摒弃了内存地址移位的繁琐,通过空间换时间的方式来提升回收效率。采用同等大小的两块区域,将可达对象放入一块区域,清空另一块区域。

五、垃圾回收器

垃圾回收器是具体在堆内存上按照其内部的垃圾回收算法执行内存回收任务的机制。

1.串行收集器

单线程回收堆内存垃圾,会发生STW。

优点:CPU资源占用少、清理干净

缺点:单线程清理堆内存空间,不适用于大型项目,STW的时间增加。

使用场景:堆内存比较小或桌面应用的程序。

Serial

用于新生代。其老年代对应的垃圾收集器可以为CMS和Serial old。

Serial old

用于老年代,采用标记整理算法。

2.并行收集器

多线程回收垃圾,会发生STW。

优点:堆内存空间大时,提升清除效率

缺点:CPU资源占用大。

使用场景:大型java后台项目。

ParNew

Serial的多线程版本,用于新生代。其老年代对应的垃圾收集器可以为CMS和Serial old。

Parallel Scavenge

用于新生代。其老年代对应的垃圾收集器可以为Parallel old和Serial old。

Parallel old

用于老年代。

3.并发收集器

GCROOT对对象判定是否可达时,串行收集器和并行收集器会判断当前对象是否可达,同时向下查找此对象的关系链是否可达,如果关系链特别长,则整个判定时间就会特别长,其STW的时间随之增加。并发收集器中用户线程和GC线程可以同时运行。分成多个阶段回收垃圾,可能在某个阶段发生短暂的STW。

CMS

标记清除算法,当CMS无法回收对象时,改用Serial old串行收集。(CMS会产生碎片化问题,需要通过标记整理使得空间连续)

初始标记:标记直接可达的对象(即直接和堆内存外指针关联的对象),速度很快(不需要向下查找关系链),会发生短暂STW(防止标记期间垃圾对象被引用)。

并发标记:GCROOT根据标记出来的对象向下查找它的关系链,进行标记,期间不影响用户线程的使用。

重新标记:修正初始标记和并发标记后对象的引用关系和标记不匹配的记录。如B对象在并发标记时,因为用户线程和GC线程并发,有可能B先被GCROOT判定为垃圾,后被用户线程使用,那么B对象就不再是垃圾,不应该清除。此期间,为了防止脏数据,也会发生短暂的STW(有点类似于数据库的表锁)。

并发清除:GC线程采用标记清除发清理垃圾,但可能会出现浮动垃圾(用户线程与GC线程同时运行,可能用户线程在运行期间产生垃圾对象,这些垃圾无法被CMS在本次回收中收集,等候下次GC回收)。CMS在并发清除过程中,不能移动内存地址。故无法使用标记整理或复制算法,这两种会发生STW。CMS可以设置阈值来提前进行内存回收,防止内存溢出。

G1

将整个堆内存分为均等大小的区域,采用三色标记法标记对象是否为垃圾(后续通过其它博文进行详解)。

六、GC相关参数

-Xms

初始大小内存,默认为物理内存 1/64,等价于 -XX:InitialHeapSize

-Xmx

最大分配内存,默认为物理内存的 1/4,等价于 -XX:MaxHeapSize

-Xss

设置单个线程栈的大小,一般默认为 512-1024k,等价于 -XX:ThreadStackSize

-Xmn

设置年轻代的大小。

整个JVM内存大小=年轻代大小 + 年老代大小 + 持久代大小

持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。

-XX:MetaspaceSize

设置元空间大小。

元空间的本质和永久代类似,都是对 JVM 规范中的方法区的实现。

元空间与永久代之间最大区别:元空间并不在虚拟机中,而是使用本地内存

因此默认情况下,元空间的大小仅受本地内存限制,元空间默认比较小,我们可以调大一点。

-XX:+PrintGCDetails

输出详细GC收集日志信息。

-XX:SurvivorRatio

设置新生代中 eden 和 S0/S1 空间比例,默认 -XX:SurvivorRatio=8,Eden : S0 : S1 = 8 : 1 : 1。

-XX:NewRatio

配置年轻代和老年代在堆结构的占比,默认 -XX:NewRatio=2 新生代占1,老年代占2,年轻代占整个堆的 1/3。

-XX:MaxTenuringThreshold

设置垃圾最大年龄。

欢迎大家和帝都的雁积极互动,头脑交流会比个人埋头苦学更有效!共勉!

公众号:帝都的雁

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值