JVM内存模型
是什么?
java运行时拿到了自己能支配的内存后将内存分为五个区域
本地方法栈
-
存储C++原生方法,java基于C++写的
程序计数器
-
记录当前执行到哪一行代码了
方法区
-
JDK7之前叫做永久带,8之后改名为元数据空间,存储元数据信息
-
存放静态的方法static(Main),或者是类加载器classloader
栈区
-
存储函数运行时的临时变量,与本地方法栈,程序计数器合起来是
线程私有
的(每个方法运行时单独开辟的运行内存) -
变量如果是对象,则是对对象的引用地址,最终指向堆区的实际对象
你(线程)可以把它想象成一碗饭(栈区),先进去的米粒(基本类型,对象的引用)最后才能被吃到,且这碗饭只有你能吃(线程私有),吃完的时候(线程结束),重新打饭(下一个线程数据入栈), 总体遵循先进后出规则
堆区
-
和方法区一样是
全局共享
的 -
存储对象
存储例子
值类型
当func1运行结束后销毁,输出的a为原来的10
对象类型
-
开辟了一个Person空间,p为指针类型int指向Person
-
String name为在编译阶段从常量值获取的对象
-
线程/函数运行结束,栈销毁,堆空间触发GC垃圾回收机制
题型巩固
打印出来的p2.id是多少? (对象不会随着栈关闭而销毁)
GC机制
1-标记清除算法
对存活对象标记为1,清除未被标记过的对象 (生命特征为0)
缺点
-
产生内存碎片,清除的对象内存为1K大小,而此时new一个2K大小的新对象则存放不进去这个位置,解决这种问题提出另一种算法,标记整理
2-标记整理
清除之后,后面对象往前补位,将1KB大小的位置占满,而不是留有间隙
缺点
-
所有对象前移,代价大
3-复制算法
将内存一分为二,在一区内存快满的时候进行存活对象标记,并且将其紧凑的复制到二区
缺点
-
需要两倍内存
实际GC
划分Young区和Old区
Young
(新生代)
Young区由包含了Eden区(伊甸园,亚当和夏娃偷吃禁果,创建新生命的地方,new的所有对象都在Eden出生)
当Eden区没空间时触发minorGC,采用复制算法将存活的对象复制到survive0区,S0,S1,Eden区的大小比例为1:1:8
当复制到S0区时,S1,Eden区数据全部删除,下一次E区快满的时候 S0,E区打标,并将存活对象复制到S1区,和S0区交替使用
如果S0/S1区空间容纳不下复制过去的对象,则全部转移至老年区Old
E+S1 快炸的时候=》S0
E+S0 快炸的时候=》S1
E+S1 快炸的时候=》S0
。。。。往复执行
Old
(老年代)
存放类型
年龄大于15的对象
如果对象累计标记大于15时,也就是存活大于15轮(年龄大于15岁),可以将其看成是老顾客(默认为老赖,长生不老,永远存活),就不需要在Survive中复制来复制去,直接将其存放到Old区
大对象(1千万大小的Int数组 )
由于在E,S0,S1区进行复制操作时,消耗比较大
FullGC
当OldGC快满的时候 Old同YoungGC 触发Full GC,引起stop world。世界暂停。整个java程序暂停运行,全力进行垃圾回收,采用了上述的标记清理,标记整理算法
标记清理,标记整理算法用于 老年区Old的FullGC
复制算法用于新生区的YoungGC
有名的垃圾收集器
Young
ParNew
Old
CMS
Jvisualvm
我们结合jvm实时检测图表更加直观的查看其在 新生代Young和老年代Old里的变化过程
-
绿色为Old老年代区
-
棕色为Eden区
-
砖红色为S0/S1区
初始创建对象会占用一部分 Eden空间,随着存活时间的增加空间占用也会逐渐增加
当Eden区满时触发minorGC。清空所有未标记对象并复制标记对象数据到S0/S1 往复执行
这里我们手动增加的1MB对于Old区是大对象。直接存放在old区,当然还有另一种超15轮标记对象数据这里就不演示了
上述笔记参考自——B站《程序员郑清》《free-coder的个人空间-free-coder个人主页-哔哩哔哩视频 (bilibili.com)》