内存模型
JVM所管理的内存分为以下几个运行时数据区:程序计数器(PC寄存器)、Java堆、方法区(包含常量池)、虚拟机栈、本地方法栈。
程序计数器:一块较小的内存空间,它是当前线程所执行的字节码的行号指示器。
当线程在执行一个Java方法时,该计数器记录的是正在执行的虚拟机字节码指令的地址。当线程在执行Native方法时,该计数器的值为空,该区域是唯一一个没有规定任何OOM的。
Java虚拟机栈:该区域是线程私有的,生命周期也与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行时都会同时创建一个栈帧,栈是用于支持虚拟机进行方法调用和方法执行的数据结构。
本地方法栈:该区域与虚拟机栈所发挥的作用非常相似,只是虚拟机栈为虚拟机执行Java方法服务,而 本地方法栈则为使用到的本地(Native)方法服务。
Java堆:Java堆是所有线程共享的一块内存区域。几乎所有的对象实例和数组都在这里分配内存,是垃圾收集器管理的主要区域。
Java堆可以处在物理上不连续的内存空间中,只要逻辑上是连续的即可。
方法区:方法区也是各个线程共享的内存区域,它用于存储已经被虚拟机加载的类信息、常量、静态常量、即时编译器(JIT)编译后的代码等数据,方法区区域又被称为“永久代”。垃圾收集行为在这个区域比较少出现,该区域的内存回收目标主要是针对废弃常量和无用类的回收。
运行时常量池是方法区的一部分。
堆:存放所有new出来的对象。
栈:存放基本类型的变量数据和对象的引用。
常量池:属于方法区的一部分,是所有线程共享的区域,存放基本类型常量和字符串常量。
对象本身并不存在栈中,而是存放在堆中或常量池中。
引用保存在Java栈中的本地变量表中,Java堆保存实例化对象,同时还要保存能够查找到此对象类型数据的地址信息。而方法区保存类型数据。
Class没有公共构造方法。Class对象是在加载类时由JVM以及通过调用类加载器中的defineClass方法自动构造的。
GC
内存分配策略
1、对象优先在Eden区分配;
2、大对象直接进入老年代;
3、长期存活的对象将进入老年代;
4、动态对象年龄判定;
5、空间分配担保。
对象存活的判定
1)计数算法:每当有一个地方引用它,计数器值加一,当引用失效时,计数器值减一;任何时刻计数器为0的对象就是不可能再被使用的。
JVM没有选用计数算法来管理内存,最直接的原因是它很难解决对象之间相互循环引用的问题。
2.JVM采用可达性分析算法。方法:通过一系列称为“GC Roots”的对象作为起点,向下搜索,走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连,则证明此对象是不可用的。
垃圾回收算法
1)标记-清除算法
首先标记所有需要回收的对象,标记完成后,统一回收所有被标记的对象。缺点:1.两个过程效率都不高;2.标记清除后产生大量不连续内存碎片。
2)复制算法
将可用内存划分为相等大小的两块,当一块内存用完了,就将还存活的对象复制到另外一块上面,然后把已使用过的空间一次性清除。商业的VM用于回收新生代。
3)标记-整理算法
先标记,然后让所有存活的对象都向一端移动,然后直接清除掉端边界外的内存。
4)分代收集算法
根据对象存活周期的不同将内存划分为几块,一般把Java堆分为新生代和老年代,根据各个年代的特点采用最适当的手机算法,新生代复制算法,老年代标记清理或标记整理算法。
MinorGC
通常是指对新生代的回收。指发生在新生代的垃圾收集动作。非常频繁,一般回收速度也比较快。
FullGC
MajorGC除并发gc外均需对整个堆进行扫描和回收。
Java Heap溢出
java堆用于存储对象实例,我们只要不断地创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象,就会在对象数量达到最大堆容量限制后产生内存溢出异常。
出现这种异常,一般是先通过内存映像分析工具对dump处理的堆内存快照进行分析,重点是确认内存中的对象是否是必要的,先分清是因为内存泄漏还是内存溢出。
Java内存回收机制
不论哪种语言的内存分配方式,都有返回所分配内存的真实地址,也就是返回一个指针到内存块的首地址。Java中对象是采用new或反射的方式创建的,这些对象的创建都是在堆中分配的,所有对象的回收都是由Java虚拟机通过垃圾回收机制完成的。实时监控对象是否可达,如果不可达,则将其回收,这样也可以清楚引用循环的问题,判断一个内存空间是否符合垃圾收集标准有两个:一个是给对象赋予空值null,以下再没有调用过;另一个是给对象赋予新值,这样重新分配了内存空间。
内存泄漏
内存泄漏是指无用对象(不再使用的对象)持续占有内存或无用对象的内存,得不到及时释放,从而造成内存空间的浪费。
内存泄漏和内存溢出的差别
内存泄漏是指分配出去的内存没有被回收回来,由于失去了对该内存区域的控制,因而造成了资源的浪费。
内存溢出是指程序所需要的内存超出了系统所能分配的内存的上限。
Java内存泄漏的根本原因:长生命周期的对象持有短生命周期的对象的引用就很可能发生内存泄漏,尽管短生命周期对象已经不再需要,但是因为长生命周期的对象持有它的引用而导致不能被回收。具体有几大类:
1)静态集合类引起内存泄漏;
2)当集合里面的对象属性被修改后,再调用remove()方法时不起作用;
3)监听器;
4)各种连接;
5)内部类和外部模块的引用;
6)单利模式。
类型擦除
泛型实际上只在程序源码中存在,在编译后的字节码文件中,就已经被替换为了原来的原生类型,并且在相应的地方插入了强制转型代码。
Java语言中的泛型实现方法称为类型擦除,基于这种方法实现的泛型被称为伪泛型。
Java内存模型
java线程<--->工作内存<--->save操作 主
java线程<--->工作内存<--->和 <-->内
java线程<--->工作内存<--->load操作 存
lock:作用于主内存的变量,它把一个变量标识为一条线程独占的状态。
unlock:作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
read(读取):作用于主内存的变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load操作使用。
load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。
use(使用):作用于工作内存的变量,它把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时,将会执行这个操作。
assign(赋值):作用于工作内存的变量,它把一个从执行引擎按收到的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
store(存储):作用于工作内存的变量,它把工作内存中一个变量的值传送到主内存中,以便随后的write操作使用。
write(写入):作用于主内存的变量,它把store操作从工作内存中得到的变量的值仿佛主内存的变量中。
如果要把一个变量从主内存复制到工作内存,那就要顺序执行read和load操作,如果要把变量从工作内存同步回主内存,就要顺序执行store和write操作。