前序:



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字节。




 



2字节):



 



Java规定了字符的内码要用UTF-16编码,一个字符是2个字节。外码字符所占字节取决于具体编码。几种常见的编码换算如下: 



 



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)



 


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


 



高峰导致的问题(平时都是很平稳的):



高峰的时候会发现系统频繁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之间,性能完全没得比。