jvm内存模型gc算法垃圾回收器

1. jvm虚拟机内存模型
程序计数器、虚拟机栈、本地方法栈、堆、方法区
在这里插入图片描述
2. 该程序计数器
程序计数器是一块线程私有的空间,各个线程之间互不影响。当线程数超过cpu数量时一个cpu同一时间只能有一个线程执行,其它线程被切换出去,为此每个线程必须用一个独立的程序计数器,用于记录下一条要运行的指令。
如果线程正在执行一个java方法,则计数器记录正在执行的java字节码的地址,如果正在执行一个native方法,则程序计数器为空
3. 虚拟机栈
虚拟机栈也是线程私有的内存空间,虚拟机在运行时使用栈针保存数据。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
4. 本地方法栈
在这里插入图片描述
5. 堆
在这里插入图片描述
6. 方法区
在这里插入图片描述
7. 堆分配参数
在这里插入图片描述
在这里插入图片描述
8. 垃圾回收算法

引用计数法:无法处理java循环引用的情况,所以java不使用这种算法
枚举根节点做可达性分析
通过一系列名为“GC Roots”的对象作为起始点,从“GC Roots”对象开始向下搜索,如果一个对象到“GC Roots”没有任何引用链相连,说明此对象可以被回收。

GC Roots 的对象:

虚拟机栈中局部变量(也叫局部变量表)中引用的对象
方法区中类的静态变量、常量引用的对象
本地方法栈中 JNI (Native方法)引用的对象

标记清除(Mark-sweep):cms垃圾回收器就是使用的这种算法,它会产生空间碎片所以回收后有时要进行碎片整理发生stw
在这里插入图片描述
复制算法:优点不会产生内存空间碎片,缺点内存折半
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
标记压缩:
在这里插入图片描述
9. jvm的垃圾收集器
在这里插入图片描述
常用的组合为图中绿色线标注的两组收集器组合
其它常见的收集器如下:

  1. Serial/ Serial Old收集器
    最基本,历史最久
    新生代采取复制算法,暂停所有用户线程
    老年代采取标记-整理算法,暂停所有用户线程
    单线程,进行垃圾收集时必须暂停其他所有工作线程
    最简单,单线程里面最高效
    Client模式下默认新生代收集器
    2. PerNew收集器
    Serial收集器的多线程版本
    Server模式下首选的新生代收集器
    除Serial收集器外,只有它能与CMS收集器配合工作
    -XX:+UseConcMarkSweepGC选项之后默认的新生代收集器
    -XX:UsePerNewGC强制指定
    -XX:ParallelGCThreads参数限制垃圾收集器的线程数。
    3. Parallel Scavenger收集器
    使用复制算法的收集器
    并行多线程收集器
    目标是达到一个可控的吞吐量(Throughput)
    精确控制吞吐量的参数:
    -XX:MaxGCPauseMillis ,接受一个大于0的毫秒数,收集器将尽可能保证内存回收花费的时间不超过设定值。注意:时间越小,收集越频繁
    -XXGCTimeRatio参数,直接设置吞吐量,接受一个大于0小于100的整数,即垃圾收集时间占总时间的比率,也就是吞吐量的倒数,默认值99,即默认最大1%的垃圾回收时间。
    -XX:+UseAdaptiveSizePolicy,开关参数,打开之后不需要手工指定新生代的大小(-Xmm),Eden与Survivor比例(-XXSurvivorRatio)、晋升老年代对象年龄(-XX:PretenureSizeThreshold)等细节参数,虚拟机根据当前系统运行情况收集性能监控信息,动态调整这些参数自适应调节(GC Ergonomics)最合适的停顿时间和最大吞吐量。
    4. Serial Old收集器
    Serial收集器的老年代版本
    单线程收集器
    使用“标记-整理”的办法
    在Client模式下作为虚拟机的默认老年代收集器
    在Server模式下可以和Parallel Scavenge收集器搭配
    在Server模式下作为CMS的后备,在并发时和Concurrent Mode Failure时使用
    5. Parallel Old收集器
    Parallel Scavenge收集器的老年代版本
    多线程处理
    使用“标记-整理”算法
    从JDK1.6开始提供
    在需要注意吞吐量和CPU资源敏感的时候优先考虑Parallel Scavenge+Parallel Old组合
    6. CMS收集器
    CMS = Concurrent Mark Sweep
    以获取最短回收停顿时间为目标
    适合在注意服务器响应速度,希望系统停顿时间最短,比如互联网网站或者B/S系统服务端。
    基于“标记-清除”
    初始标记(CMS initial mark)
    需要全暂停,但是仅仅标记GC Roots能关联到的对象,速度很快
    并发标记(CMS Concurrent mark)
    进行GC Roots Tracing的过程,和用户线程一起工作
    重新标记(CMS remark)
    全暂停
    为了修正并发标记期间用户线程导致的记录变化
    一般比初始标记长,远比并发标记时间短
    并发清除(CMS Concurrent sweep)
    和用户线程一起工作

cms 缺点在于:

1、对于CPU资源和敏感,因在并发回收阶段会占用线程资源,会导致应用程序变慢,总吞吐量降低

2、无法清除浮动垃圾(Floating Garbage)因为在垃圾回收阶段用户进程可能产生新的垃圾,但是出现在标记阶段之后,无法在当次处理,叫浮动垃圾。
CMS收集器需要预留空间提供并发收集时的程序运行,用-XX:CMSInitiatingOccupancyFraction设置触发收集的百分比,JDK1.5 默认68%,JDK1.6默认92%
CMS运行期间预留的内存无法满足程序需要,会临时启动Serial Old收集器重新进行老年代gc

3、基于“标记-清楚”的算法会产生大量碎片
-XX:+UseCMSCompactAtFullCollection参数(默认开启),在FullGC时开启内存碎片的整合过程,解决空间碎片问题,但是停顿时间变长
-XX:CMSFullGCsBeforeCompaction:设置执行多少次不压缩的Full GC,再进行带压缩的(默认为0,标识每次Full GC时都需要进行碎片整理)

7. G1收集器
Garbage-First
面向服务端应用
并行与并发利用多个CPU来缩短Stop-The-World时间
分代收集
空间整合,从整体上基于“标记-整理”,聚不上基于“复制”,不会产生碎片
可预测的停顿
保留新生代和老年代的概念,但是它们之间不再物理隔离,都是一部分Region的集合。
跟踪Region垃圾堆价值大小,后台维护优先列表,每次根据允许的收集时间,回收价值最大的Region。
通过Remembered Set来避免全堆扫描,每个Region都有一个对应的Remembered Set。在Reference对象被写入时,中断写操作,检查Reference引用的对象是否处于不同的Region,如果是,通过CardTable把相关引用的信息记录到被引用对象所属的Region的Remembered Set中。
除了维护Remembered Set的操作,G1收集器运作包括以下几个步骤:
初始标记(Initial Marking):记录GC Roots能关联到的对象。时间短,中断用户操作。
并发标记(Concurrent Marking),从GC Roots开始进行可达性分析,耗时较长,可并行
最终标记(Final Marking),把并发标记期间对象变化记录在线程Remembered Set Logs中,并把其中的数据合并到Remembered Set中。需要停顿线程,但是可并行执行。
筛选回收(Live Data Counting Evacuation)对各个Region的回收价值和成本进行排序,根据用户期望的GC停顿时间来指定回收计划。
10.参数

并行gc参数
在这里插入图片描述
cms(并发标记清除)相关参数:
在这里插入图片描述
11. 小结

1、垃圾回收常用组合
关注停顿时间
-XX:+UseConcMarkSweepGC 新生代默认使用并行收集器-XX:UsePerNewGC
关注吞吐量
-XX:+UseParallelOldGC Parallel Scavenge+Parallel Old组合

2、java.lang.OutOfMemoryError: unable to create new native thread
2.1.你的应用创建了太多线程了,理论上讲,一个应用进程,创建多个线程,确实可以提高应用程序的并发能力,但线程的数量并不是越大,你的应用程序并发能力就越强的。
2.2.你的服务器并不允许你的应用程序创建这么多线程,linux系统默认允许单个进程可以创建的线程数是1024个,你的应用创建超过这个数量,就会报java.lang.OutOfMemoryError: unable to create new native thread
解决:查找有无线程滥用情况,线程池等,修改Xss参数,或者提供更大的内存(缩小堆内存等)

3、即时编译
JIT是just in time的缩写,也就是即时编译。 Java程序最初是仅仅通过解释器解释执行的,即对字节码逐条解释执行,这种方式的执行速度相对会比较慢,尤其当某个方法或代码块运行的特别频繁时,这种方式的执行效率就显得很低。于是后来在虚拟机中引入了JIT编译器(即时编译器),当虚拟机发现某个方法或代码块运行特别频繁时,就会把这些代码认定为“Hot Spot Code”(热点代码),为了提高热点代码的执行效率,在运行时,虚拟机将会把这些代码编译成与本地平台相关的机器码,并进行各层次的优化,完成这项任务的正是JIT编译器。
-XX:CompileThreshold=10000
通过JIT编译器,将方法编译成机器码的触发阀值,可以理解为调用方法的次数,例如调1000次,将方法编译为机器码

发布了52 篇原创文章 · 获赞 7 · 访问量 3807

猜你喜欢

转载自blog.csdn.net/maomaoqiukqq/article/details/100167423