对java1.8 jvm 和 gc的 部分理解,以及JVM调优

JVM有很多种,我们平时基本都是使用的Hotspot

主要有3个子系统构成:

  1. 类加载系统 -----javac命令编译,java命令执行,执行类装载系统,然后加载到下面的内存区域
  2. 运行时数据区(内存结构)-----下面详解
  3. 字节码执行引擎 -----最终由引擎运行加载到内存中的数据
  • 本次学习的是 运行时数据区

内存结构通常分为5个区

所有线程共有的:(优化主要就是这两个区)
    堆区(一般来说,所有的对象都在堆区,也有可能放在栈,不扣细节,想了解的,搜索 java逃逸分析)
    
    方法区/元空间(常量池,静态变量,类元信息等)
线程私有的:
	虚拟机栈/栈区(一般存放局部变量,基础数据类型;栈帧是指当一个方法调用另一个方法时,
			会创建一个新的栈区来存放这个方法的数据,我们称这个区域栈帧;
		栈帧中的信息:
			局部变量表(记录变量),
			操作数栈(对变量操作时需要,比如a+b,需要另外的栈来执行),
			动态链接(方法中,可能会调用其他方法,记录其他方法的内存地址),
			方法出口(当前方法执行完毕时,返回上一个方法的位置,以及返回值也在这里记录))
			
	本地方法栈/本地方法区(其他语言实现的功能)
	
	程序计数器(记录当前线程代码运行的位置,线程独有)

GC概念

  • 堆中是分区的,有 年轻代,老年代(永久代);
  • 年轻代 又分为1个 Eden区,和两个Survivor区;下面称呼S0区,S1区
  • 堆的内存,会分给老年代,和年轻代,默认比例2:1;
  • 年轻代分配给Eden和S0,S1的内存默认比例8:1:1;
  • 一般来说,每次new出一个对象,都是放在Eden区,我们一直new 对象,会填满Eden;
    此时会触发 minor GC; 一个单独的线程,专门负责的,当GC时,会暂停所有其他线程
  • 可达性算法:当触发GC时,会将GC Roots 作为起点,从这些节点向下搜索 引用的对象,凡是被找到的对象,都会被标记为 “非垃圾对象”,其余未被标记的,全部清除;
  • GC Roots :线程栈的本地变量,静态变量,本地方法栈的变量 等
  • 被标记 “非垃圾对象” 的,会被复制到S0区;并且给这些对象 的分代年龄+1;
  • 然后我们继续new对象,再一次将 Eden区填满时,再次执行GC,使用可达性算法 扫描Eden区,和S0区,将 “非垃圾对象”复制到S1区,将Eden和S0区 未被标记的对象 清除;
  • 这样重复执行,当某个对象的 分代年龄达到15时(不同的垃圾回收器,不一样),会被复制到 老年代;
  • 当老年代也被填满了,且有数据要进入老年代,就会进行Full GC;
  • Full GC:会对整个堆区中的垃圾 对象进行回收;如果老年代内存不够用,就会OOM;
  • OutOfMemory:内存溢出

JVM调优概念

  • 因为GC时,虚拟机会暂停其他所有线程,特别是当Full GC频繁,且时间久时,严重影响用户体验;虚拟机调优 就是为了 降低GC次数,降低GC时间,且尽量避免Full GC;
  • 案例:某电商平台,大促销活动时,每秒500个订单,只有1台服务器 负责订单系统;
    • 服务器配置:4核8G;2个G留给系统,5G分配给堆空间,1G分配给其他
    • 每个订单假定1KB,每秒产生500KB,下单还涉及到其他操作,比如库存,优惠券,积分等等,这里假定这个数据放大20倍,是10M;订单系统还负责有订单查询等等操作,再假定这个数据放大10倍,就是说,每秒产生100M的数据,而这些数据,都是在使用一次 之后,就变成垃圾的;
    • 因为堆区默认分配的空间为 老年代:新生代 为 2:1;
    • 则 老年代有约3.3G,新生代约1.6G;
    • 而新生代中,Eden和两个Survivor 的默认比例是8:1:1;
    • 1.6G内存分发之后,就是Eden区 1280 M,S0和S1各 160M;
    • 而我们每秒产生的垃圾对象约100M,大约13秒,就会占满Eden区,进行一次minor GC
    • 而当我们在GC的时候,这1秒正在运行中的订单对象,不会被清除,我们假设这1秒也产生了100M的对象,就会被挪到S0区域;
    • 这里要提一个机制:动态年龄判断机制;根据这个机制,当一次进入Survivor的对象大于这个Survivor内存的一半时,很大概率会直接放入老年代,Survivor区有160M,一次产生的100M左右的数据,就可能会进入老年代;
    • 这样就导致,大约13秒,就会有100M左右的数据进入老年代,大约33次,7.8分钟,就要进行一次Full GC;这,,问题就出来了,这么频繁的Full GC,用户体验肯定是极差的;
    • 而这些被加入到老年代的对象,其实都是使用一次之后,就变成垃圾的对象,我们用Full GC来回收他们,肯定是不合适的;
    解决方案
    • 我们通过计算,大约评估出了每秒要产生100M的垃圾对象,就可以对这些参数进行修改了
    • 1:将堆空间中的内存,2G分配给老年代,3G分配给新生代;这样,Eden就会有 2400M,S0和S1区各300M;24秒一次minor GC,那一秒产生的约100M的对象被放入S0,下一次minor GC,这些对象就会被清除,不会进入到老年代,问题就解决了;
    • 2:我们可以通过压测,确定老年代需要的内存大小,将更多的内存分配给 年轻代,尽量避免Full GC;
    • 3:JKD1.9之后,默认使用的是G1垃圾回收器,还可以修改GC的最大持续时间;比如一次GC只允许200ms;
    其他:待补充

可以通过 工具确定对内存进行分析:阿里开源 JVM内存调优工具:arthas

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在进行 JDK 1.8 的 JVM 参数时,可以考虑以下几个方面: 1. 堆内存设置: - 整初始堆大小和最大堆大小,使用 `-Xms` 和 `-Xmx` 参数来设置。根据应用的负载情况和服务器的可用内存,合理分配堆内存大小。 2. 垃圾回收器选择: - JDK 1.8 默认使用的是并行垃圾回收器(Parallel GC)。如果应用有较高的并发需求,可以考虑使用并发标记清除垃圾回收器(CMS GC)或 G1 垃圾回收器(G1 GC)。 3. 并行度设置: - 根据服务器的 CPU 核心数量和应用负载情况,整并行垃圾回收的线程数。使用 `-XX:ParallelGCThreads` 参数来设置,并行垃圾回收线程的数量。 4. 元空间(Metaspace)设置: - 元空间是 JDK 1.8 中替代永久代的内存区域。可以使用 `-XX:MaxMetaspaceSize` 参数来设置元空间的最大大小。 5. 垃圾回收相关参数: - 根据应用的特点和性能需求,整垃圾回收相关参数。例如,可以使用 `-XX:MaxGCPauseMillis` 来设置最大垃圾回收停顿时间,以平衡吞吐量和停顿时间。 6. 监控与工具: - 使用 JDK 自带的工具,如 jstat、jmap、jstack 等,来监控应用的内存、垃圾回收情况和线程状态。根据监控结果,进行针对性的。 注意,JVM 参数的需要根据具体应用的特点和实际情况进行实验和测试,以获得最佳性能和稳定性。建议在进行参数前,先了解应用的负载情况和性能瓶颈,并备份原有的参数配置,以便在过程中出现问题时可以回滚。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值