JVM内存模型
JVM的作用:JVM将java字节码解释为具体平台的具体指令,屏蔽了与具体平台相关的信息
**程序计数器:**较小的内存空间,当前线程执行的字节码的行号指示器;各线程之间独立存储,互不影响;
- java 栈:线程私有,生命周期和线程,每个方法在执行的同时都会创建一个 栈帧用于存储局部变量表,操作数栈,动态链接,方法出口等信息。方法的执行就对应着栈帧在虚拟机栈中入栈和出栈的过程;栈里面存放着各种基本数据类型和对象的引用;
- 本地方法栈:主要为虚拟机使用到的Native方法服务。
- 堆:被所有线程共享的一块内存区域,在虚拟机启动的时候创建,用于存放对象实例。
- 方法区:被所有方法线程共享的一块内存区域。用于存储已经被虚拟机加载的类信息,常量,静态变量等。
JVM垃圾回收机制
如何判断对象已死
- 引用计数法:给对象增加一个引用计数器,每当有一个地方引用它时,计数器就+1,引用计数法无法解决对象的循环引用问题
- 可行性分析法:
可达性算法:通过一系列称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索走过的路径称为“引用链”,当一个对象到 GC Roots 没有任何的引用链相连时(从 GC Roots 到这个对象不可达)时,证明此对象不可用。
Java中可以作为GC Root的包括下面几种:
1)虚拟机栈中的引用对象
2)方法区中类静态属性引用的对象
3)方法区中常量引用的对象
4)本地方法栈中引用的对象
常用的垃圾回收算法
-
标记-清除算法
标记-清除算法采用从根集合(GC Roots)进行扫描,对存活的对象进行标记,标记完毕后,再扫描整个空间中未被标记的对象,进行垃圾回收
这种算法实现起来比较容易,但是会造成内存碎片 -
标记-复制算法
复制算法是为了解决标记-清除算法的缺陷而提出的。
它将内存划分为大小相等的两块,每次只使用其中的一块。当这A快内存用完了,就将还存活的对象复制到B块上面,然后把A块的内存空间一次性清理掉
这种算法虽然实现简单,运行高效且不易产生内存碎片,但是却对**内存空间的使用做出了高昂的代价,因为能使用的空间缩减为原来的一半。**很显然,复制算法的效率跟存活对象的数量有很大关联,若存活对象很多,那么效率将大大降低 -
标记-整理算法
该算法是为了解决复制算法的缺陷,充分利用内存空间而提出的。
该算法与标记-清除算法一样,但是在完成标记后,不直接清理可回收对象,而是将存活对象全部向一端移动,接着清理掉边界以外的内存。 -
分代收集算法(重点)
分代收集算法是目前大部分JVM的垃圾收集器采用的算法。其核心思想是根据对象存活的生命周期将内存划分为若干个不同的区域。
将其分为年轻代、老年代和永久代。然后根据不同的区域采用合适的收集算法。
分代收集算法是目前大部分JVM的垃圾收集器采用的算法。其核心思想是根据对象存活的生命周期将内存划分为若干个不同的区域。将其分为年轻代、老年代和永久代。然后根据不同的区域采用合适的收集算法。
Java一般将堆区分为年轻代和老年代,将方法区划为永久代。
年轻代:新创建的对象都存放在这里。因为年轻代会频繁的进行GC清理,JVM在年轻代采用的是标记-复制算法,先标记出存活的实例,然后清除掉无用实例,将存活的实例根据年龄(每个实例被经历一次GC后年龄会加1)拷贝到不同的年龄代。
老年代:老年代中是经历了N此垃圾祸首后仍然存活的对象,其中的N由JVM的参数决定。这块内存区域一般大于年轻代。GC发生的次数也比年轻代要少。
永久代:用于存放静态文件,如Java类、方法等。为方法区。
法区主要回收的内容有:废弃的常量、无用的类,对与废弃常量可以同过引用的可达性判断,但是对于无用类需要同时满足以下3个条件:
1)该类的所有实例都已经被回收了
2)加载该类的 ClassLoader 已经被回收了
3)该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法
GC在优先级最低的线程中运行,一般在应用程序空闲时被调用。当内存不足时才会主动调用