JVM学习

转载自:https://blog.csdn.net/zhaocuit/article/details/73826256

 

一、jvm内存区域

这里写图片描述

1、程序计数器

  • 内存空间小
  • 线程私有
  • 指示当前线程执行字节码的行号
  • 如果执行本地方法,计数器值为空
  • 不会出现OutOfMemoryError

2、虚拟机栈

  • 线程私有
  • 每个方法执行的同时会创建一个栈帧,栈帧存储局部变量表、操作栈、动态连接、方法出口等信息
  • 方法调用过程即为栈帧的入栈出栈过程
  • jvm允许栈大小固定或者动态扩展
  • 局部变量表存储基本数据类型、对象的引用、返回类型,Long、double占用2个局部变量空间(或者叫”字”)
  • 影响栈大小的jvm参数,-Xss
  • 影响栈深度的因素:帧栈大小(局部变量表)
  • StackOverflowError:栈深度大于jvm允许的深度时(StackTest1,StackTest2)
  • OutOfMemoryError:在扩展过程中,没有足够的空间来支持扩展时(StackOutOfMemory)
  • 局部变量表中的字可以被重用(StackTest3)
  • 局部变量表中的字使用可能会影响GC回收。如果字没有被复用,则它所引用的对象不会被GC回收(StackTest4)

3、本地方法栈

  • 与虚拟机栈类似
  • 虚拟机栈管理Java函数的调用
  • 本地方法栈管理本地方法的调用

4、方法区

  • 被所有线程共享
  • 主要存储类的类型信息、常量池、域信息、方法信息,存储的信息来自class文件
  • 独立于Java堆的内存空间
  • 也可以被GC回收,包括常量池的回收和类元数据的回收
  • 常量池回收例子(PermGenGC)
  • 类元素回收条件: 
    该类的所有实例被回收 
    装载该类的ClassLoader被回收
  • 可能会抛出OutOfMemoryError:PermGen space异常

5、堆

  • 被所有线程共享
  • 所有的对象和数组都被分配到堆中
  • 堆被分为新生代(eden、s0、s1)和老年代
  • 刚创建的对象在eden中,经过一次GC后,会移动到s0或s1中,到了指定的GC次数(或者同一个年龄的变量占S空间的一半)后,会被移动到老年代中

二、jvm内存分配参数

1、最大(小)堆内存参数

  • -Xmx:最大堆内存参数(HeapMaxTest)。
  • -Xms:最小堆内存参数,如果被设置,则jvm尽量在此值以下内运行程序,如果实在不能满足要求,才向系统申请更多的内存空间,但不能大于-Xmx,否则会抛出异常OutOfMemoryError
  • -Xms如果设置过小,则GC会更频繁,GC的总体时间会更多。(HeapMinTest)
  • 建议将-Xms和-Xmx设置成一样

2、堆新生代参数

  • -Xmn:新生代大小。
  • -XX:NewSize:最小新生代大小。
  • -XX:MaxNewSize:最大新生代带大小。
  • 设置-Xmn等同于-XX:NewSize=-XX:MaxNewSize。

3、堆的比例分配

  • -XX:SurvivorRatio:设置eden和s0的比例,s0=s1
  • 默认值为8,即eden=-Xmn/10*8,s0=-Xmn/10
  • 示例代码(HeapSurvivorRatioTest)
  • -XX:NewRatio:设置新生代和老年代的比例
  • -XX:NewRatio=老年代/新生代
  • 默认值为2,即老年代:新生代=2:1
  • 示例代码(HeapNewRatioTest)

4、设置方法区参数

  • -XX:PermSize:最小方法区大小。
  • -XX:MaxPermSize:最大方法区大小。
  • 方法区大小决定了系统可以支持多少个类定义和多少个常量。(PermGenGC1)
  • 建议这2个参数值都设置成一样。

5、设置线程栈参数

  • -Xss:设置线程栈大小。
  • 在进行局部变量分配、函数调用时需要申请栈空间。
  • 栈空间的大小决定函数调用的深度(在局部变量一定的情况下)
  • 栈空间的大小决定局部变量的个数(在函数调用深度一定的情况下)
  • 如果栈空间过大,则支持的线程数量越少(在栈总空间一定的情况下),示例代码(StackOutOfMemory)

6、总结

这里写图片描述

三、垃圾回收算法

1、如何判断一个Java对象是否已死?

  • 引用计数算法:

    给对象添加一个计数器,有一个地方引用它时就加1,引用失效时减1。 
    优点:简单、高效 
    缺点:对循环引用无效(A引用B,B引用A)

  • 根搜索算法:

    从GC Roots节点开始搜索,能搜索到的对象则认为对象还存活,不能搜索到的对象则认为死亡,应被垃圾回收。

2、标记-清除算法

  • 标记需要回收的对象,然后清除标记的对象
  • 缺点:清除后会产生大量内存碎片,导致大对象在内存空间充足时无法被分配。

    这里写图片描述

3、复制算法

  • 将内存划分为大小相等的2块,当A块用完时,将存活的对象复制到B块中,清除A块内存中的所有对象。
  • 优点:效率比标记-清除高,且不会出现内存碎片。
  • 缺点:内存被分成了AB两块,相当于缩小了一半的内存,如果对象的死亡率低则会有较多的复制。
  • 新生代一般都采用这种算法,因为新生代死亡率较高。
  • jdk的新生代对这个算法进行了优化,jdk将其划分为eden、s0、s1三个部分,其中s0和s1即为大小相等的两块内存。执行过程大致为:新创建的对象首先分配到eden中,当空间不够时,进行垃圾回收,将存活的对象拷贝到s0中,清除eden所有对象,继续执行;当空间再次不够时,再次进行垃圾回收,将eden和s0中的存活对象拷贝到s1中,并清除eden和s0中所有对象,如果s1的空间不足以容纳eden和s0中的存活对象,则某些对象会进入老年代;如此循环迭代。

    这里写图片描述

4、标记-整理算法

  • 标记需要回收的对象,将存活对象移动到一端,最后清除边界外的内存。
  • 老年代一般会使用这种算法,因为老年代存活率较高。

    这里写图片描述

5、分代搜集算法

  • 根据对象的存活周期不同将内存划分成几块,一般是把Java堆划分成新生代、老年代。
  • 根据各个年代的特点选用适当的回收算法。

四、垃圾回收器

这里写图片描述

上图中的箭头表示能搭配使用,比如新生代如果使用serial垃圾回收器,那么老年代就可以使用CMS或serial old,不能使用parallel old。

1、Serial

  • 单线程垃圾收集器
  • 垃圾回收时需要暂停所有用户线程(stop the world)
  • 采用复制算法
  • client模式下新生代默认垃圾回收器
  • 优点:简单、高效(与其他回收器的单线程比)
  • 缺点:单线程

2、ParNew

  • 是Serial回收器的多线程版本
  • server模式下首选的新生代回收器,因为他可以和CMS配合工作(CMS比较牛逼)

3、Paralle Scavenge

  • 并行多线程回收器
  • stop the world
  • 采用复制算法
  • 重点关注系统的吞吐量,即用户代码时间/(用户代码时间+垃圾回收时间)
  • 主要适合后台运算任务
  • 相关参数: 
    MaxGCPauseMillis:最大垃圾回收时间,大于0的毫秒数 
    GCTimeRatio:吞吐量,(0-100]的整数,默认1%的垃圾回收时间。 
    -XX:+UseAdaptiveSizePolicy:打开这个参数后,只需要设置-Xmx(最大堆)、GCTimeRatio(或者MaxGCPauseMillis),jvm会自动设置堆相关参数。

4、Serial Old

  • 是Serial回收器的老年代版本。
  • 标记-整理算法。
  • 单线程回收垃圾。
  • stop the world。
  • client模式下老年代默认垃圾回收器。
  • server模式下主要作为CMS发生concurrent model 
    failure的时候作为后备预案。

5、Paralle Old

  • 并行多线程回收器
  • stop the world
  • 采用标记整理算法
  • 主要配合Paralle Scavenge使用

6、CMS

  • 以最短停顿时间为目标的回收器
  • 主要应用在B/S架构上,重服务器的响应速度
  • 采用标记清除算法
  • 主要分为如下几个阶段:

    初始标记:标记GC roots能直接关联到的对象,速度快,执行时间短,会stop the world。

    并发标记:GC roots追踪的过程,不会stop the world,执行时间相对较长。

    重新标记:修正并发标记期间产生的变动,初始标记<重新标记执行时间<并发标记或并发清除,会stop the world。

    并发清除:垃圾回收,不会stop the world,执行时间相对较长。

  • 缺点:

    对CPU资源比较敏感:默认回收线程数=(CPU+3)/4,当CPU小于4个时,对应用程序影响较大。

    无法处理浮动垃圾(并发清除阶段产生的垃圾):默认在老年代空间使用了68%后,就会进行垃圾回收(-XX:CMSInitiatingOccupancyFraction) 
    如果在运行期间预留的内存不够使用时,将用serial old进行垃圾回收,这样垃圾回收的时间就很长了。

    易产生内存空间碎片:采用标记-清除算法 
    -XX:+UseCMSCompactAtFullCollection:是否在FullGC后进行碎片整理,碎片整理过程会stop the world。 
    -XX:+CMSFullGCsBeforeCompaction:多少次FullGC后进行一次碎片整理。

7、G1

  • 1.7版本才算成熟,个人感觉是CMS的替代版
  • 采用标记-整理算法,没有碎片产生
  • 精确控制停顿,在M毫秒内垃圾回收时间不得超过N毫秒。
  • 将Java堆划分为多个独立的region,并跟踪region的垃圾堆积程度,在后台维护一个优先列表,优先回收垃圾最多的region。
  • G1和CMS一样,都要经历初始标记、并发标记、重新标记和并发清除四个过程。

五、JVM相关参数汇总

这里写图片描述

六、内存分配策略

  • 对象优先在eden分配(TestAllocation)
  • 大对象直接进入老年代(TestPretenureSizeThreshold)
  • 长期存在的对象进入老年代(TestMaxTenuringThreshold.test1())
  • 动态对象年龄的判断(TestMaxTenuringThreshold.test2())
  • 空间分配担保(TestMaxTenuringThreshold.test3()) 
    建议都加上-XX:+HandlePromotionFailure
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值