热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

深入理解JVM和GC内存调优

JVM&GC运行时数据区堆(线程共享)新生代老年代元空间堆空间大小的设定内存规整堆的调整方法区(线程共享)程序计数器(线程私有)虚拟机栈(线程私有)

JVM & GC

  • 运行时数据区
    • 堆(线程共享)
      • 新生代
      • 老年代
      • 元空间
      • 堆空间大小的设定
      • 内存规整
      • 堆的调整
    • 方法区(线程共享)
    • 程序计数器(线程私有)
    • 虚拟机栈(线程私有)
      • 局部变量表
      • 操作数栈
      • 动态链接
      • 方法出口
    • 本地方法栈(线程私有)
  • GC-垃圾回收
    • 判断算法
      • 引用计数法
      • 可达性分析
    • 回收算法
      • 标记-清除算法
      • 复制-回收算法
      • 标记-整理算法
    • 垃圾集收器
      • 新生代(用复制回收算法)
        • serial
        • parNew
        • parallel
      • 老年代
        • CMS (用标记 - 清除算法)
        • serial Old (用标记 - 整理算法)
        • parallel Old (用标记 - 整理算法)
    • 查看当前JVM的垃圾集收器
    • GC 日志
      • 输出日志
      • 日志文件控制
      • 监视日志文件命令
      • JDK自带监控工具
      • MAT

运行时数据区

  • 首先弄清楚一点:一个java进程开启一个jvm,进程又可分成多线程运行
  • 进程在 jvm 上运行时,数据保存在运行时数据区,运行时数据区包括堆、方法栈、虚拟机栈、本地方法区和程序计数器,其中堆和方法栈是线程共享的,虚拟机栈、本地方法区和程序计数器是线程私有的
    深入理解 JVM 和 GC -- 内存调优

堆(线程共享)

  • 存储程序运行时用到的所有对象,而线程虚拟机栈的局部变量表保存的是对象的引用指针;堆空间分为新生代、老年代、元数据(jdk1.8之后,1.7之前称为永久代),元数据实际上不属于堆空间
    深入理解 JVM 和 GC -- 内存调优
  1. 为什么分代? 对象的生命周期不一样,GC过后有些被回收有些存活下来,存活下来的对象也不必与新来的对象一起GC,需要另外空间来存放存活下来的对象

  2. 为什么Eden:servivor是8:1:1而不是9:1? Minor GC => 新生代 Major GC => 老年代 一次 Major GC 所花时间 > 一次 Minor GC 所花时间 所以我们希望新生代的对象尽量不要被放到老年代,即 GC
    之后应有98%的对象被回收,如果是9:1的话,对象很容易存活到老年代,所以希望对象在新生代进行更多次的GC以达到目标

  3. 对象分配比例配置:-XX:SurvivorRatio=8

新生代

  • 新生代占堆空间的1/3,分为Eden区(8/10)、From区(1/10)、To区(1/10)
  • 新创建的对象会被放入Eden区,当Eden区空间不足时,虚拟机会做一次 Minor GC,大部分对象会被垃圾回收器回收,剩下的存活的对象放入 From 区,这是复制回收算法
  • 当 From区满时,进行 Minor GC 会对 From 区也做 GC,存活的会进入 To 区,此时 From 区与 To 区角色互换
  • 当 Eden 区再次满的时候, Minor GC 会把存活下来的对象放入 To 区(因为From 区与 To 区已经角色互换)

老年代

  • 当 Minor GC之后 新生代满或放不下新对象时,会触发担保机制,把存活的对象放入老年区
  • 当整个堆空间放满之后,会进行 Full GC,Full GC = Minor GC + Major GC,System.gc() 引起的是 Full GC

元空间

  • JDK1.7之前: 永久代
  • JDK1.8之后: 元空间(直接内存),这样设计是为了解决永久代可能溢出的问题,可以动态扩容,属于堆外内存,可能会挤压堆内内存,所以根据需求定义其大小

堆空间大小的设定

通过GC日志获取多次 full GC 存活下来的活跃数据的平均值
总堆大小 = 活跃数据 * 4
新生代 = 活跃数据 * 1.5
老年代 = 总堆大小 - 新生代
(虚拟机有默认值和自定调整)

内存规整

当多线程进行对Eden区存放堆数据的时候会出现线程安全问题(指针碰撞)
深入理解 JVM 和 GC -- 内存调优
栈上分配:线程在Eden区有自己的Buffere(可调整),可避免过多的锁,当Buffere不足时会清除到堆内存

堆的调整

-Xms …m 堆的起始大小(start)
-Xmm …m 堆的最大值(max)
-Xmn …m 堆的新生代大小(new)
-Xss …m 每个线程的栈大小

方法区(线程共享)

线程共享,保存类信息、静态变量、常量(jdk1.7)

程序计数器(线程私有)

  • 指向当前线程所执行的jvm指令的地址
  • 当线程挂起时,可以保存线程运行的状态,以便下次继续执行

虚拟机栈(线程私有)

  • 线程私有,保存当前线程运行方法所需要的数据、指令和返回地址,一个方法对应一个栈帧,每调用一个方法会压入一个栈帧,方法执行完出栈
  • 每一个栈帧包含局部变量表、操作数栈、动态链接、方法出口等
    深入理解 JVM 和 GC -- 内存调优

局部变量表

保存当前线程执行的方法的局部变量,宽度为4字节(32位),当线程要对变量操作时,会从栈顶的局部变量表开始找,找不到再往下找

操作数栈

  • 保存线程执行指令相关的操作数
  • 操作数栈和局部变量表是紧密联系的,例如 int c = a + b 在虚拟机底层执行的指令:
    iload_1
    iload_2
    iadd
    istore_3
    深入理解 JVM 和 GC -- 内存调优

动态链接

保存线程方法运行时引用的类库方法的接口地址

方法出口

方法执行完成的返回地址,正常返回:return,异常:执行异常处理

本地方法栈(线程私有)

线程私有,保存nactive方法,底层用c/c++实现的、没有实现类的方法

GC-垃圾回收

垃圾回收机制是针对堆空间中存放的对象数据的, 进行垃圾回收可以在一定程度上节省内存空间,以便放入更多的数据

判断算法

引用计数法

给每一个对象增加一个被应用计数标志以判断该对象是否可被清楚,但可能会出现循环引用,所以JVM不用这个算法

可达性分析

GC Root:

  • 虚拟机栈中局部变量表引用的对象
  • 方法区中静态变量和常量引用的对象
  • 本地方法栈中JNI引用的对象
    深入理解 JVM 和 GC -- 内存调优
    当带对象节点不可达时,会进入finalize()方法,不一定会立即被回收,看有没有重写finalize()方法

回收算法

标记-清除算法

对不可达的对象进行标记和清除,但是会产生内存碎片
深入理解 JVM 和 GC -- 内存调优

复制-回收算法

对不可达对象进行回收,存活下来的对象放入servivor区
深入理解 JVM 和 GC -- 内存调优

标记-整理算法

对不可达对象进行标记、清除和整理,因为进行了在 GC 的时候整理所以不会产生内存碎片
深入理解 JVM 和 GC -- 内存调优

垃圾集收器

  • 虽然每个进程开启一个 jvm,但同一个 jdk 默认垃圾集收器相同
  • 新生代的垃圾集收器:serial / parNew / parallel 都用的是复制回收算法
  • 老年代的垃圾集收器:CMS(标记 - 清除) / serial Old(标记 - 整理) / parallel Old(标记 - 整理)
    深入理解 JVM 和 GC -- 内存调优

新生代(用复制回收算法)

serial

单线程执行垃圾回收代码,垃圾回收时其他线程不能执行,即应用线程停顿(stop-the-world)
深入理解 JVM 和 GC -- 内存调优

parNew

多线程执行垃圾回收代码,垃圾回收时其他线程不能执行,在多核心CPU的情况下停顿时间会比 serial 少
深入理解 JVM 和 GC -- 内存调优
配置线程个数:-XX:parallelGCThreads=n

parallel

主要关注吞吐量,吞吐量 = 运行用户代码时间 / (运行用户代码时间 + 垃圾回收时间)

-XX:MaxGCPauseMillise=n // 控制GC停止时间
-XX:GCTimeRatio=n // 控制GC运行时间的比率
-XX:UseAdaptivesizePolicy // 开启全局维护策略

老年代

CMS (用标记 - 清除算法)

  • 主要关注减小回收停顿时间(stop-the-world),但增加了回收时间,这是 CMS 与 parallel的区别
  • 先进行GC Root可达标记
  • 再进行并发标记,在并发标记的同时业务线程能并发运行
  • 接下来 stop-the-world 重新整理,因为并发标记的过程用户线程也在执行
  • 接着进行并发清除
    深入理解 JVM 和 GC -- 内存调优
    -XX:CMSInititiongOccupancyFraction // 碎片整理
    -XX:+UseCMSCompactAtFullCollection // Full GC 时开启压缩整理
    -XX:CMSFullGCsBeforeCompaction // 设置多少次 Full GC 之后开启压缩

serial Old (用标记 - 整理算法)

CMS备用预案,触发担保机制后老年代空间不足时,会进行 Full GC

parallel Old (用标记 - 整理算法)

查看当前JVM的垃圾集收器

-XX:+PrintFlagsFinal
深入理解 JVM 和 GC -- 内存调优

-XX:+PrintCommandLineFlags
深入理解 JVM 和 GC -- 内存调优

GC 日志

输出日志

-Xloggc: /…gc.log
分析 gc.log 的信息,例如:
解读:[GC类型 GC前占用空间 -> GC后占用空间(总空间), GC耗时]
深入理解 JVM 和 GC -- 内存调优

-XX: +PrintGCTimeStamps
深入理解 JVM 和 GC -- 内存调优

-XX: +PrintGCDetails
深入理解 JVM 和 GC -- 内存调优

日志文件控制

-XX: -UseGCLogFileRotation
-XX: GCLogFileSize=8k

监视日志文件命令

tail -f gc.log // GC日志
tail -f catalina.log // 应用日志

JDK自带监控工具

jconsole // java 监视和管理控制台
深入理解 JVM 和 GC -- 内存调优

jps // 拿到pid
深入理解 JVM 和 GC -- 内存调优

jmap -heap pid // heap使用情况
深入理解 JVM 和 GC -- 内存调优

jstat -gcutil pid 毫秒 // GC数据统计(Old GC没有变化且还在增长 => 内存泄漏)
深入理解 JVM 和 GC -- 内存调优

jstat -gccause pid 毫秒 // 引发GC的原因
深入理解 JVM 和 GC -- 内存调优

jvisualVM // 查看线程运行状况和dump等
深入理解 JVM 和 GC -- 内存调优
深入理解 JVM 和 GC -- 内存调优

jstack pid > p.txt // 导出线程的dump

MAT

-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/…error.hprof

  • 查找内存泄漏原因:
  • GC日志: xxx -> xxx(xxx) 有没有回收
  • MAT: Retained Heap 和 GC Root 指向

推荐阅读
  • 本文介绍了操作系统的定义和功能,包括操作系统的本质、用户界面以及系统调用的分类。同时还介绍了进程和线程的区别,包括进程和线程的定义和作用。 ... [详细]
  • Android中高级面试必知必会,积累总结
    本文介绍了Android中高级面试的必知必会内容,并总结了相关经验。文章指出,如今的Android市场对开发人员的要求更高,需要更专业的人才。同时,文章还给出了针对Android岗位的职责和要求,并提供了简历突出的建议。 ... [详细]
  • 在说Hibernate映射前,我们先来了解下对象关系映射ORM。ORM的实现思想就是将关系数据库中表的数据映射成对象,以对象的形式展现。这样开发人员就可以把对数据库的操作转化为对 ... [详细]
  • 本文讨论了在手机移动端如何使用HTML5和JavaScript实现视频上传并压缩视频质量,或者降低手机摄像头拍摄质量的问题。作者指出HTML5和JavaScript无法直接压缩视频,只能通过将视频传送到服务器端由后端进行压缩。对于控制相机拍摄质量,只有使用JAVA编写Android客户端才能实现压缩。此外,作者还解释了在交作业时使用zip格式压缩包导致CSS文件和图片音乐丢失的原因,并提供了解决方法。最后,作者还介绍了一个用于处理图片的类,可以实现图片剪裁处理和生成缩略图的功能。 ... [详细]
  • 本文介绍了Swing组件的用法,重点讲解了图标接口的定义和创建方法。图标接口用来将图标与各种组件相关联,可以是简单的绘画或使用磁盘上的GIF格式图像。文章详细介绍了图标接口的属性和绘制方法,并给出了一个菱形图标的实现示例。该示例可以配置图标的尺寸、颜色和填充状态。 ... [详细]
  • Android工程师面试准备及设计模式使用场景
    本文介绍了Android工程师面试准备的经验,包括面试流程和重点准备内容。同时,还介绍了建造者模式的使用场景,以及在Android开发中的具体应用。 ... [详细]
  • Java和JavaScript是什么关系?java跟javaScript都是编程语言,只是java跟javaScript没有什么太大关系,一个是脚本语言(前端语言),一个是面向对象 ... [详细]
  • 本文总结和分析了JDK核心源码(2)中lang包下的基础知识,包括常用的对象类型包和异常类型包。在对象类型包中,介绍了Object类、String类、StringBuilder类、StringBuffer类和基本元素的包装类。在异常类型包中,介绍了Throwable类、Error类型和Exception类型。这些基础知识对于理解和使用JDK核心源码具有重要意义。 ... [详细]
  • 深入理解Java虚拟机的并发编程与性能优化
    本文主要介绍了Java内存模型与线程的相关概念,探讨了并发编程在服务端应用中的重要性。同时,介绍了Java语言和虚拟机提供的工具,帮助开发人员处理并发方面的问题,提高程序的并发能力和性能优化。文章指出,充分利用计算机处理器的能力和协调线程之间的并发操作是提高服务端程序性能的关键。 ... [详细]
  • 基于Socket的多个客户端之间的聊天功能实现方法
    本文介绍了基于Socket的多个客户端之间实现聊天功能的方法,包括服务器端的实现和客户端的实现。服务器端通过每个用户的输出流向特定用户发送消息,而客户端通过输入流接收消息。同时,还介绍了相关的实体类和Socket的基本概念。 ... [详细]
  • 本文整理了Java面试中常见的问题及相关概念的解析,包括HashMap中为什么重写equals还要重写hashcode、map的分类和常见情况、final关键字的用法、Synchronized和lock的区别、volatile的介绍、Syncronized锁的作用、构造函数和构造函数重载的概念、方法覆盖和方法重载的区别、反射获取和设置对象私有字段的值的方法、通过反射创建对象的方式以及内部类的详解。 ... [详细]
  • 一次上线事故,30岁+的程序员踩坑经验之谈
    本文主要介绍了一位30岁+的程序员在一次上线事故中踩坑的经验之谈。文章提到了在双十一活动期间,作为一个在线医疗项目,他们进行了优惠折扣活动的升级改造。然而,在上线前的最后一天,由于大量数据请求,导致部分接口出现问题。作者通过部署两台opentsdb来解决问题,但读数据的opentsdb仍然经常假死。作者只能查询最近24小时的数据。这次事故给他带来了很多教训和经验。 ... [详细]
  • HashMap的相关问题及其底层数据结构和操作流程
    本文介绍了关于HashMap的相关问题,包括其底层数据结构、JDK1.7和JDK1.8的差异、红黑树的使用、扩容和树化的条件、退化为链表的情况、索引的计算方法、hashcode和hash()方法的作用、数组容量的选择、Put方法的流程以及并发问题下的操作。文章还提到了扩容死链和数据错乱的问题,并探讨了key的设计要求。对于对Java面试中的HashMap问题感兴趣的读者,本文将为您提供一些有用的技术和经验。 ... [详细]
  • OpenMap教程4 – 图层概述
    本文介绍了OpenMap教程4中关于地图图层的内容,包括将ShapeLayer添加到MapBean中的方法,OpenMap支持的图层类型以及使用BufferedLayer创建图像的MapBean。此外,还介绍了Layer背景标志的作用和OMGraphicHandlerLayer的基础层类。 ... [详细]
  • linux进阶50——无锁CAS
    1.概念比较并交换(compareandswap,CAS),是原⼦操作的⼀种,可⽤于在多线程编程中实现不被打断的数据交换操作࿰ ... [详细]
author-avatar
手机用户2502885647_951
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有