内存管理=内存分配+内存回收
内存分配
WechatIMG3.jpegJMM指Java内存管理
线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存,又称作TLAB线程本地缓冲区,中存储了该线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。
虚拟机栈
特点如上图。
局部变量表存放了各种基本数据类型,对象引用类型,(“对象引用”可能是一个对象起始地址的引用指针,也可能是一个指向代表对象的句柄,不同的JVM实现不同),和return Address类型
StackOverflow : 栈深度超出允许范围
OutOfMemory: 当虚拟机栈在动态扩展时,无法申请到足够的内存则抛出
堆
- 线程共享
- 存放所有的对象实例和数组
- 可拓展-xmx -xms控制大小
- OutOfMemoryError
JVM规范中规定的是:所有对象实例及数组都要在堆上分配,但随着编译器发展,也出现了栈上分配等优化技术
没有在堆上完成对象的内存分配并且也无法扩展堆大小时,将抛出OutOfMemoryError
方法区
- 线程共享
- 存放所有已被加载的(类信息、常量、静态变量、JIT编译后的代码)
- Hotspot可以设置大小
- OutOfMemoryError
又称非堆,在Hotspot上,方法区的回收和堆的回收是绑定在一起的,任何一方触发full gc都会另一方也进行full gc
运行时常量池
- 运行时常量池是方法区的一部分,所以也是全局共享的。
- 其作用是存储 Java 类文件常量池中的符号信息。
- class 文件中存在常量池(非运行时常量池),其在编译阶段就已经确定;JVM 规范对 class 文件结构有着严格的规范,必须符合此规范的 class 文件才会被 JVM 认可和装载。
- 运行时常量池 中保存着一些 class 文件中描述的符号引用,同时还会将这些符号引用所翻译出来的直接引用存储在 运行时常量池 中。
- 运行时常量池相对于 class 常量池一大特征就是其具有动态性,Java 规范并不要求常量只能在运行时才产生,也就是说运行时常量池中的内容并不全部来自 class 常量池,class 常量池并非运行时常量池的唯一数据输入口;在运行时可以通过代码生成常量并将其放入运行时常量池中。
- 同方法区一样,当运行时常量池无法申请到新的内存时,将抛出 OutOfMemoryError 异常。
内存溢出
- 堆溢出
不断往数组中添加实例 - 栈溢出
无限递归的方法调用--StackOverflow
不断创建线程 — OOM - 方法区与运行时常量溢出
无尽的String.intern() — before jdk1.6
通过cglib 字节码增强,不断创建新类
内存回收
垃圾回收 — 如何判断对象已死
- 引用计数器
- 可达性分析
2.1. 栈帧中引用的对象
2.2. 方法区中静态属性引用的对象
2.3. 方法区中常量引用的对象
2.4. JNI引用的对象
引用计数很难解决循环引用问题
可达性分析通过从GC-Root,可能有多个,出发进行搜索,凡是没有在任何路径上的对象,就可以回收了
回收算法
- 标记-清除
1.1. Stop The World
1.2. 会产生大量碎片 - 复制
- 标记-整理
3.1. 无需额外空间 - 分代收集
4.1. 针对各年代的特点,采用不同算法。
标记就是通过可达性分析算法,为所有有引用的对象都打上标记,在标记和清除阶段,都需要将整个程序暂停,也就是stop-the-world。
复制算法,如果不想浪费50%的空间,就必须有额外空间做担保,例如老年代给新生代担保,那么由于没有再额外的空间给老年代担保了,所以老年代就不能采用复制算法。
标记-整理,又称作标记压缩,适合于老年代。
垃圾收集器
Serial、ParNew、Parallel Scavenge、Serial Old、Parallel Old
CMS 、G1
CMS是知道Java7为止默认的server模式的垃圾收集器
G1在Java7引入,在Java8成为推荐
网友评论