运行时数据区
- 首先弄清楚一点:一个java进程开启一个jvm,进程又可分成多线程运行
- 进程在 jvm 上运行时,数据保存在运行时数据区,运行时数据区包括堆、方法栈、虚拟机栈、本地方法区和程序计数器,其中堆和方法栈是线程共享的,虚拟机栈、本地方法区和程序计数器是线程私有的
堆(线程共享)
- 存储程序运行时用到的所有对象,而线程虚拟机栈的局部变量表保存的是对象的引用指针;堆空间分为新生代、老年代、元数据(jdk1.8之后,1.7之前称为永久代),元数据实际上不属于堆空间
为什么分代? 对象的生命周期不一样,GC过后有些被回收有些存活下来,存活下来的对象也不必与新来的对象一起GC,需要另外空间来存放存活下来的对象
为什么Eden:servivor是8:1:1而不是9:1? Minor GC => 新生代 Major GC => 老年代 一次 Major GC 所花时间 > 一次 Minor GC 所花时间 所以我们希望新生代的对象尽量不要被放到老年代,即 GC
之后应有98%的对象被回收,如果是9:1的话,对象很容易存活到老年代,所以希望对象在新生代进行更多次的GC以达到目标对象分配比例配置:-XX:SurvivorRatio=8
新生代
- 新生代占堆空间的1/3,分为Eden区(8/10)、From区(1/10)、To区(1/10)
- 新创建的对象会被放入Eden区,当Eden区空间不足时,虚拟机会做一次 Minor GC,大部分对象会被垃圾回收器回收,剩下的存活的对象放入 From 区,这是复制回收算法
- 当 From区满时,进行 Minor GC 会对 From 区也做 GC,存活的会进入 To 区,此时 From 区与 To 区角色互换
- 当 Eden 区再次满的时候, Minor GC 会把存活下来的对象放入 To 区(因为From 区与 To 区已经角色互换)
老年代
- 当 Minor GC之后 新生代满或放不下新对象时,会触发担保机制,把存活的对象放入老年区
- 当整个堆空间放满之后,会进行 Full GC,Full GC = Minor GC + Major GC,System.gc() 引起的是 Full GC
元空间
- JDK1.7之前: 永久代
- JDK1.8之后: 元空间(直接内存),这样设计是为了解决永久代可能溢出的问题,可以动态扩容,属于堆外内存,可能会挤压堆内内存,所以根据需求定义其大小
堆空间大小的设定
通过GC日志获取多次 full GC 存活下来的活跃数据的平均值
总堆大小 = 活跃数据 * 4
新生代 = 活跃数据 * 1.5
老年代 = 总堆大小 - 新生代
(虚拟机有默认值和自定调整)
内存规整
当多线程进行对Eden区存放堆数据的时候会出现线程安全问题(指针碰撞)
栈上分配:线程在Eden区有自己的Buffere(可调整),可避免过多的锁,当Buffere不足时会清除到堆内存
堆的调整
-Xms …m