JDK、JRE、JVM之间的关系
JDK是针对Java开发的产品,是整个Java产品的核心,包括了Java的运行环境JRE、Java工具和Java基础类库。
JRE是运行Java程序的运行时环境,包含JVM和Java核心类库。
JVM是Java虚拟机的缩写,是整个Java跨平台的最核心的部分,能够运行Java语言写作的软件程序。
JVM
内存模型
在JVM内存模型中分为JVM虚拟机数据区和本地内存,其中JVM虚拟机数据区由程序计数器、本地方法栈、Java虚拟机栈和堆组成;本地内存由元数据区和直接内存组成。
程序计数器
是当前线程所执行的字节码的行号指示器,用于记录正在执行的虚拟机字节指令地址,线程私有。
Java虚拟机栈
线程私有,每个方法在执行的时候都会创建一个栈帧(是Java虚拟机进行方法调用和方法执行的数据结构),用于存储局部变量、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就是一个对应的栈帧在java虚拟机栈中入栈到出栈的过程。生命周期与线程同进同退,不存在线程安全问题,所有的栈帧出栈之后,线程也就完成了使命。
本地方法栈(调用非Java语言的方法)
本地方法栈的功能与Java虚拟机栈十分相同,区别在于本地方法栈为虚拟机使用到的native方法提供服务。当java程序需要与操作系统底层打交道的时候,这时java语言是受到限制的,需要调用底层提供Native方法来实现Java程序与操作系统底层之间的交互。
Java堆
堆是JVM内存最大,管理最复杂的一个区域。其唯一的用途就是存放对象的实例:所有的对象实例及数组都在堆上进行分配。1.8以后,字符串常量池从永久代中剥离出来,存放在堆中。
元数据区
元数据区取代了1.7版本及之前的永久代。元数据区和永久代本质上都是方法区的实现,方法区存放虚拟机加载的类信息、静态变量、常量等数据。
直接内存
JDK1.4引入了NIO(同步非阻塞式IO),它可以使用Native函数库直接分配堆外内存。用于数据缓冲区。分配回收成本较高,但读写性能较高,不受JVM内存的管理。
JVM内存溢出和内存泄漏
内存溢出和内存泄漏的区别
内存溢出:是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory。
内存泄漏:是指程序在申请内存后,由于某种原因无法释放已申请的内存空间,导致这块内存无法再次被利用,造成系统内存的浪费。内存泄漏最终会导致内存溢出。
Java虚拟机栈内存溢出
两种情况:
1、栈帧过多导致;一般是方法的递归调用,如果在方法的递归调用里没有设置正确的解除条件,每次调用就会产生一个栈帧,就会导致栈内存溢出;
2、栈帧过大导致;这种情况不容易出现。
调节栈内存的最大值:-Xss
堆内存溢出
导致的情况:
1、对象所需的内存较大,超过了堆内存所允许的最大空间限度。
2、内存泄漏,一些对象的引用不在被应用程序使用但垃圾收集器无法识别的情况,这些未被使用的对象仍然驻留在堆内存中,造成内存泄漏现象,并逐渐堆积最终发展成内存溢出。
调节堆内存的初始值和最大值:-Xms、-Xmx;
GC
GC是垃圾收集的意思,内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java提供的GC功能可以自动检测对象是否超过作用域从而达到自动回收内存的目的,垃圾收集器会自动的进行垃圾回收。
哪些对象是需要回收的呢?怎么确定?
引用计数算法
这个算法的实现是,给对象中添加一个引用计数器,每当一个地方引用这个对象时,计数器值+1;当引用失效时,计数器值-1。任何时刻计数值为0的对象就是不可能在被使用的。这种算法使用场景很多,但是Java中没有使用这种算法,因为这种算法很难解决对象之间相互引用的情况。虚拟机也并不是通过引用计数算法来判定对象是否存活的。
可达性分析算法
这个算法的基本思想是通过一系列称为”GC Roots“的对象作为起始点,从这些节点向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链时,则证明此对象是不可用的。
Java中GC Roots的对象
1、虚拟机栈中引用的对象;
2、元数据区中的类静态属性引用的对象;
3、元数据区中常量引用的对象;
4、本地方法栈中JNI(Native方法)引用的对象;
四种引用状态
1、强引用:代码中普遍存在的new一个对象,这类的引用,只要强引用还在,垃圾收集器永远不会回收掉被引用的对象;
2、软引用:描述有些还有用但并非必须的对象。在系统将要发生内存溢出异常之前,将会把这些对象列入回收范围进行二次回收;
3、弱引用:描述非必须对象。被弱引用关联的对象只能生存到下一次垃圾回收之前,垃圾收集器工作之后,无论当前内存是否足够,都会回收掉弱引用关联的对象;
4、虚引用:这个引用存在的唯一目的就是在这个对象被收集器回收时收到一个系统通知,被虚引用关联的对象和其生存时间完全没关系。
垃圾收集算法
标记-清除算法
这是最基础的算法,分为标记和清除两个阶段:首先标记出所有需要回收的对象,标记完成后统一回收所有被标记的对象。这种算法的不足体现在效率和空间。效率不高,空间残余内存碎片;
复制算法
复制算法是为了解决效率问题而出现的,它将可用的内存分为两块,每次只用其中一块,当这一块内存用完了,就将还存活着的对象复制到另一块上面,然后再把已经使用过的内存空间一次性清理掉。缺点:内存缩小成原来的一半;
标记-整理算法
过程和标记-清除算法一样,不过不是直接对可回收对象进行处理,而是让所有存活对象都想一端移动,然后直接清理掉边界以外的内存;
分代收集算法
大批对象死去、少批对象存活的,使用复制算法(年轻代);对象存活率高、没有额外空间进行分配担保的(老年代),采用标记-清理算法或者标记-整理算法。