JVM简单调优及工作中例子记录

前序:
Jvm调优需要我们对系统有所了解,其中比较关键的是对核心业务的理解,特别是会造成频繁GC的部分,比如高并发造成的不及时回收。
 

要知道为什么会造成频繁GC,首先我们要懂怎么估算java类的大小
下面列举各个基本类型和字符串估算的表格,以及测试的类
 
基本类型
大小(字节)
取值范围
装箱基本类型
int
4
-2^31 ~ 2^31-1
Integer
char
2
 
Character
byte
1
-2^7 ~ 2^7-1
Byte
short
2
-2^15 ~ 2^15-1
Short
long
8
-2^63 ~ 2^63-1
Long
float
4
 
Float
double
8
 
Double
boolean
1或4
true~false
Boolean
注释:1字节(byte) = 8比特(bit),1kb = 1024字节。为什么boolea需要4个字节,原则上只需要1个比特,但是操作系统以字节作为单位,所以至少要一个字节。又因为jvm用int替代boolean,所以需要4字节。
 
讲解完基础类型,我们大概看一下String字符是多少字节(默认就是2字节):
 
Java规定了字符的内码要用UTF-16编码,一个字符是2个字节。外码字符所占字节取决于具体编码。几种常见的编码换算如下: 
 
-- ASCII编码       1字节编码,只有英文字符,不能编码汉字。 
-- GBK编码         英文1字节                汉字2字节。 
-- UTF-8编码       英文1字节                汉字3字节。 
-- Unicode编码     英文2字节                汉字2字节。
 
下面可以借助getObjectSize()来获取类的大小(以下是一个demo,可自行测试):
public class Test01 {
    public Test01() {
        Demo1 demo1 = new Demo1();
        long objectSize = getObjectSize(demo1);
        System.out.println(objectSize);
    }
    public static void main(String[] args) throws UnsupportedEncodingException {
        new Test01();
        // 字符串大小
        System.out.println("测试".getBytes("ISO8859-1").length);
    }
}
    /**
    * 对齐填充(8字节补齐)
    * 这里有一个注意点:如果大小未满8字节的倍数,会自动补齐8字节倍数。
    */
    class Demo1 {
        private String str;
        // private int intVal;
        // private int intVal2;
        //private double doubleVal;
    }
 

接下来就举一个简单的jvm调优参考例子:
---------------调优前-----------------
假设有一个日活过亿的电商网站(日均活跃用户500w,平均每人点击20-30次),下单率10%,也就是
    下单人数 = 10% * 500w = 50w
把这一千万在整天进行平摊(考虑到用户活跃时间一般只有4-5小时(具体数字见自己系统的分析),所以在这5小时进行平摊)
        每秒下单数 = 50w / (5 * 60 * 60) = 27
如果只有27这个数字是很小的,如果把当前服务部署成3个,那每个每秒就9个下单请求,基本不会对系统造成影响,JVM的垃圾回收肯定是正常的。
        
但是这些系统一般会有一个高峰,在短时间内产品了一天的下单量,比如搞活动双十一啥的,这时候公式计算就变成下面这样:
        下单人数 = 10% * 500w = 50w
用户活跃时间就不是4-5个小时了,可能是几分钟内涌入大量订单
         每秒下单数 = 30w / 几分钟 = 1000多
这时候我们也部署成3个,每台300来个订单,这时候300明显远大于正常情况,假如三台都是(4G8核)的机器,我们使用2G作为老年代,1G作为新生代(Eden区:s1区:s2区 = 8:1:1 = 800M:100M:100M)
 

 
高峰导致的问题(平时都是很平稳的):
高峰的时候会发现系统频繁FullGC告警,频繁FullGC会导致STW(stop the world),让页面产生卡顿,对用户体验不好(正常情况下STW不应该这么频繁)。
 
假设这是一个订单需求上线后才出现的(也做过jvm参数的变更)。这时候可以从jvm参数来入手如下:
-- 1 估算下单时产生的内存大小
    假定每个订单有几十个字段,每个字段8个字节,放大来看:8 * 100 = 800byte,约等于1KB(我们就当它1KB算了)
    每秒300个请求就是: 300 * 1KB = 300KB 的对象生成,
    然后还有一起杂七杂八的库存、优惠券、积分等,放大20倍:300KB * 20 = 6MB,然后杂七杂八的查询再放大10倍:6MB * 10 = 60MB
    然后Eden=800MB,所以每13秒会minor GC一次:800MB / 60MB = 13,并且由于第13次的60M是可能还执行完,其引用还存在(CG root可达),这时候第15次的60MB不会被minor GC,本来应该会从移动s区,但是由于s区只有100MB,60MB 大于 100MB*50%,所以这个60MB会直接移动到老年代,导致老年代一点点上上涨,最后满了触发fullGC,导致STW。
 
--------------调优解决方法--------------------------------
将新生代设置为2GB,其中Eden=1.8GB,s1=s2=200MB。这样的话60MB放进s区的时候也不会超过50%,随后就能被minorGC掉,从而不会放到老年代中,导致老年代短时间一直涨。导致系统卡顿。
 

其他编程中的小例子:
1 编程中假设查询出来仅仅有两个字段,就尽量使用只有两个成员变量的类来接收这些数据,不要为了方便用一个含几十个成员变量的类来接收,这样会很占用内容。
 
2 编程中性能会涉及到网络IO、磁盘IO等,比如我们查数据库,把数据从一次从库查出来和循环中一个个查出来很大区别,主要体现在查数据库的时候有网络IO和磁盘IO,多次查询的网络IO+磁盘IO累计起来很可怕。
 
3 使用中不要以为用redis就一定很快,比如你用redis在循环中查数据,假设每次吊redis耗时1ms,但是数据一多(假设2w条)就会变成2w * 1ms = 20s,这样是无法忍受的,相反一次性从redis查两位万条可能就1-50ms之间,性能完全没得比。
 
 
 
发布了171 篇原创文章 · 获赞 730 · 访问量 47万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章