本节主要介绍的内容有:Java 虚拟机的内存划分、垃圾回收机制、虚拟机内存分析工具。
1、关于 Java
- Java 程序设计语言、Java 虚拟机、Java API 类库这三部分统称为 JDK,JDK 是用于支持 Java 程序开发的最小环境。
- Java API 类库中的 Java SE API 子集和 Java 虚拟机这两部分统称为 JRE,JRE 是支持 Java 程序运行的标准环境。
- HotSpot 是目前使用最为广泛的虚拟机。
Java以后发展的几个方向:
- 模块化,功能组件可插拔;
- 混合语言:各不同的功能模块使用不同的运行在虚拟机之上的语言开发;
- 多核并行:使用分治算法等提升多核利用率;
- 丰富现有的语法;
- 解决 64 位虚拟机上的性能问题。
2、JVM 内存管理
2.1 JVM 内存区域
下图是 Java 虚拟机的内存主要区域划分,注意图中由浅蓝色标识的部分是所有线程共享的数据区;淡紫色标识的部分是每个线程私有的数据区域
。
我们这里对各个部分功能做简要的总结:
- 程序计数器:
线程私有
,用来指示当前线程所执行的字节码的行号,就是用来标记线程现在执行的代码的位置;对 Java 方法,它存储的是字节码指令的地址;对于 Native 方法,该计数器的值为空。 - 栈:
线程私有
,与线程同时创建,总数与线程关联,代表Java方法执行的内存模型。每个方法执行时都会创建一个栈桢来存储方法的的变量表、操作数栈、动态链接方法、返回值、返回地址等信息。一个方法的执行和退出就是用一个栈帧的入栈和出栈表示的。通常我们不允许你使用递归就是因为,方法就是一个栈,太多的方法只执行而没有退出就会导致栈溢出,不过可以通过尾递归优化。栈又分为虚拟机栈和本地方法栈,一个对应 Java 方法,一个对应 Native 方法。 - 堆:用来给对象分配内存的,
几乎所有的对象实例(包括数组)都在上面分配
。它是垃圾收集器的主要管理区域,因此也叫 GC 堆。它实际上是一块内存区域,由于一些收集算法的原因,又将其细化分为新生代和老年代等。如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出 OutOfMemoryError 异常。 - 方法区:方法区由多线程共享,用来存储类信息、常量、静态变量、即使编译后的代码等数据。
运行时常量池
是方法区的一部分,它用于存放编译器生成的各种字面量和符号引用,比如字符串常量等。根据 Java 虚拟机规范的规定,当方法区无法满足内存分配需求时,将抛出 OutOfMemoryError 异常。
2.2 垃圾回收
根据上面 JVM 内存区域的描述,因为程序计数器和两种栈的生命周期与线程相同,线程或者方法结束即可回收。所以,所谓的垃圾回收主要是针对方法区和堆内存而言。
生存还是死亡?
可达性分析 四种引用类型 对象的自我救赎
判断一个对象是否可以回收通用的方式有两种。
- 一种是引用记数法,即给对象添加一个引用计数器,被引用时计数器加1,引用失效时减1。
这种方法不常用,因为它难以解决两个变量相互引用的问题。 - 另一种是可达性分析,即通过一系列 GC Roots 的对象作为起始点,从节点向下搜索,
当一个对象没有任何一条可到 GC Roots 的引用链,则该对象可回收。
根据可达性分析的原理,对象之间存在引用关系,但是 “是或否被引用” 不足以描述更多的场景,
所以在这基础之上人们又提出了四种引用类型的概念:强引用、软引用、弱引用和虚引用
,它们的引用强度依次减弱。
- 当使用
new
关键字创建一个对象的时候,这个对象就是强引用的,它绝对不会被回收,即使内存