对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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值