JVM 分代模型:年轻代和老年代
根据编写的代码方式的不同,采用不同的方式来创建和使用对象,其实对象的生存周期是不同的。
年轻代:创建和使用完之后立马就要回收的对象放在那里面
老年代:创建之后需要一直长期存在的对象放在里面
永久代:JVM里的永久代就是方法区,方法区存放一些类信息。
垃圾回收
大部分的正常对象,都是优先在新生代分配内存的。
若程序要分配新的对象时,发现新生代内存空间不足,就会触发一次垃圾回收,然后就把所有的垃圾对象给干掉,腾出大量的内存空间。
如果有对象躲过了十多次垃圾回收,那就会放入老年代里。
如果老年代也满了,那么也会触发垃圾回收,把老年代里没人引用的垃圾对象清理掉。
如何设置JVM内存大小
设置JVM内存的大小
-Xms: Java 堆内存的大小
-Xmx: Java 堆内存的最大大小
-Xmn: Java堆内存中的新生代大小,扣除新生代剩下的就是老年代的内存大小
-XX:PermSize: 永久代大小(JDK 1.8以后为:-XX:MetaspaceSize)
-XX:MaxPermSize: 永久代最大大小(JDK 1.8以后为:-XX:MaxMetaspaceSize)
-Xss: 每个线程的栈内存大小
通过实战计算合适的JVM堆大小
假设每天100万个支付订单,那么一般用户交易行为都会发生在每天的高峰期,比如中午或晚上。
假设每天的高峰是3个小时,用100万平均分配到几个小时里,那么大概每隔100笔订单左右,咱们以100笔订单来计算。
假设我们的支付系统部署了3台机器,每台机器实际上每秒大概处理30笔订单。
计算处理订单的时间
如果用户发起一次支付请求,那么支付需要在JVM中创建一个支付订单对象,填充进去数据,然后把这个支付订单写入数据库,还可能会处理一些其他的事情。
假设一次支付请求的处理,包含一个支付订单的创建,大概需要1秒钟的时间。现在,应该是每台机器一秒钟接收到30笔支付订单的请求,然后再JVM的新生代里创建了30个支付订单的对象,做了写入数据库等操作。接着一秒之后,这30个支付订单就处理完毕,这些支付订单对象的引用就回收了。
计算每个支付订单对象大概需要多大的内存空间
直接根据支付订单类中的实例变量的类型来计算。Integer类型的变量数据是4个字节,Long类型的变量数据是8个字节等等。我们算他大一点,一个支付订单对象占据500字节,不到1kb。
那么30个支付订单,大概的内存空间是 30* 500字节 = 15000字节,大概为15kb,其实非常非常的小。
对完整的支付系统内存占用进行预估
真实的支付系统上线运行,肯定每秒会创建大量其他的对象,可以把之前的计算结果扩大10到20倍。每秒钟除了在内存里创建支付订单对象,还会创建其他数十种对象。那么每秒钟创建出来的被栈内存的局部变量引用的对象大致占据的内存空间在几百KB~1MB之间。
如果新生代就几百MB的内存,会导致系统运行几百秒后,新生代内存空间就满了,就得触发Minor GC,频繁的触发Minor GC,会影响线上系统的性能稳定性。若机器采用4核8G,然后 -Xms 和 -Xmx 设置为3G,给整个堆内存3G内存空间,-Xmn设置为2G,给新生代2G内存空间。如果业务量更大,可以横向扩展部署5台机器,或者10台机器,这样每台机器处理的请求更少,对JVM的压力更小。
如何合理设置永久代大小
永久代里面主要是存放一些类的信息,一个刚上线的系统,没太多可以参考的规范,但是一般设置个几百MB,大体都是够用的。
如何合理设置栈内存大小
这个栈内存大小设置,一般都不会特别的去预估和设置,一般默认就是比如512KB到1MB,就差不多了。这个栈内存就是每个线程自己的栈内存空间,用来存放线程执行方法期间的各种布局变量的。