这也是个老话题, 估计所有java老手都会使用 -Xms 和 -Xmx
关于这两个参数的核心问题是, 什么时候jvm使用的是xms大小,什么时候使用的是xmx大小
jvm启动以后,虚拟机将使用xms设置的堆大小,当GC运行次数超出预期,频繁的发生时, 它才会调高堆的大小,注意不是一次调整到xmx而是逐渐提高
直到GC发生次数降低到预期值或者是堆内存大小已经调整到xmx设置的值
一旦堆的大小范围确定下来了,接下来你需要考虑的是新生代和老生代内存的划分
其实根据之前说过的不同垃圾收集器的原理, 现代jvm都是采用分代回收的方式, 那么也就意味着 如果新生代过大,老生代较小, Minor GC 的发生次数就会降低, 但是Full GC可能会频繁发生
反过来如果新生代较小, 老生代过大可能会导致Minor GC频繁发生, Full GC运行时间过长的情况
平衡是门艺术
所有用于调整代空间的命令都是针对新生代的, 剩余的部分都会被划分给老生代
-XX:NewRatio=N
设置新生代比例,默认值是2
通过公式 Inital Young Gen Size = Initial Heap Size / ( 1 + N ) 来得到 如果堆大小为1GB 那么新生代也就是 1024M / (1+2) = 341.3 M
如果觉得这个公式比较复杂, 下面的方式优先级更高, 优先级更高, 优先级更高 重要的事说3遍, 如果直接设置了size上面的ratio也就没用了
-XX:NewSize=N 初始大小
-XX:MaxNewSize=N 最大值
-XmnN 将上面两个参数直接设置成一样的快捷参数
当堆大小扩张时, 新生代的大小也会扩张, 直到MaxNewSize设置的大小
下面的话题才是一直以来比较容易蒙圈的问题, 永久代和元空间
简单来说这两个空间都是用来保存class的基本信息的, 所谓的Non-heap 非堆内存指的就是这两种空间
java7 采用的是永久代, java8使用元空间
我们先来看看java7的模型, 在java7中
永久代的垃圾回收是和老生代绑定的, 无论是永久代还是老生代满了都会触发Full GC
如果使用CMS收集器 -XX:+CMSClassUnloadingEnabled 可以使收集器并发的回收永久代 如果是G1 收集器, 只能通过Full GC回收
不过从java7 开始, 由于永久代已经开始变得臃肿和过时, 一部分数据结构已经开始从永久代移动到其他空间,
例如 符号引用(Symbols)被移到了native heap, 运行时常量池 (interned strings) 和静态变量被移动到了heap(堆)中
永久代在java8中被完全的移除了,参数-XX:PermSize和-XX:MaxPermSize 已经作废
然后我们来看java8的新模型
java8中元数据被存储在native memory , native memory = 元数据+Native Heap
对于32位的JVM,native memory的容量=4G-Java Heap-PermGen;对于64位的JVM,native memory的容量=物理服务器的总RAM+虚拟内存-Java Heap-PermGen
-XX:MaxMetaspaceSize,class metadata分配的最大空间,默认是没有限制的
-XX:MinMetaspaceFreeRatio,在GC之后,最小的Metaspace剩余空间容量的百分比
-XX:MaxMetaspaceFreeRatio,在GC之后,最大的Metaspace剩余空间容量的百分比
对于用户而言, 最显而易见的好处在于存储元数据的元空间再也不会出现OOM错误, 可以最大化的使用本地内存
元空间(meta space)的生命周期实际上是与classloader绑定的, 仅当classloader被丢弃之后, 元空间中的信息才会被回收, 每个classloader拥有自己的meta space,
换句话说, 其实metaspace 就是classloader专门用于存储class元数据的空间
所有的classloader都会从一个公共空间来申请内存快, 当classloader需要存储新的元数据时, 它会在其中申请空间存储信息,当classloader被丢弃之后元空间返还给公共空间
简单的说, 如果你只关心class到底会不会被回收, 实际上是问 class在何时被jvm卸载
当且仅当
1. 这个类没有对象存活
2. 加载这个类的classloader已经被丢弃
3. 该类对应的java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法
class会被卸载