1.内存分配
JVM运行时数据区分为5个区域。
程序计数器、Java虚拟机栈、本地方法栈是线程私有
Java堆、方法区是线程共享区域
程序计数器
程序计数器是一块较小的内存空间,可以看做当前线程所执行字节码的行号指示器,字节码解释器工作时就是通过改变这个计数器的值选取下一条需要执行的字节码指令,它是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复都是通过这个计数器完成的。
由于Java虚拟机的多线程是通过线程轮流切换,分配处理器执行时间的方式来实现的,一个处理器都只会执行一个线程的指令。为了线程切换后能恢复到正确的执行位置,每个线程需要有一个单独的程序计数器。每个线程互不影响,称这类内存区域为线程私有的内存区域。
Java虚拟机栈
与程序计数器一样,Java虚拟机栈也是线程私有的,它的生命周期和线程相同。Java虚拟机栈描述的是Java方法执行的线程内存模型:每个方法执行的时候,Java虚拟机都会同步创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每个方法被调用直至执行完毕的过程,对应一个栈帧在虚拟机栈中入栈和出栈的过程。
局部变量表存放了编译期可知的各种Java虚拟机基本数据类型、对象引用、和returnAddress类型(指向了一条字节码指令的地址)
本地方法栈
本地方法栈和Java虚拟机栈类似,只不过虚拟机栈为虚拟机执行Java方法(字节码)服务,而本地方法栈是为了虚拟机用到的本地(Native)方法服务
Java堆
Java堆是虚拟机所管理的内存中最大的一块, 并且是所有线程共享的一块内存区域。java虚拟机规范中描述“所有的对象和数组都应在堆内分配内存”,随着即时编译器的进步,栈上分配,标量替换优化手段导致在堆上分配不是那么绝对了。
Java堆是垃圾收集器管理的内存区域,也被称为“GC堆”。现代垃圾收集器大多基于分代收集来设计的,主要有“新生代”、“老年代”、“永久代”,新生代又包含“Eden空间”、“From Survivor空间”、“To Survivor空间”,当然也有不基于分代收集的垃圾收集器。
方法区
方法区和Java堆一样是所有线程共享的一个区域,用于存储已经被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据,有个别名称为“非堆”。
之前的JDK8以前有永久代的概念,HotSpot准备把分代设计扩展到方法区,也就是使用永久代代替方法区。因为有着永久代的内存限制,容易遇到内存泄漏的问题。java8废弃了永久代,采取直接内存
运行常量池
运行常量池是方法区的一部分。class文件除了有类的版本、字段、方法、接口等描述信息外,还有一项信息为常量表,存放编译期生成的各种字面量和符号引用,这部分信息会在类加载之后放入常量池。
运行时常量池的一个重要特征是具备动态性,运行期间也可以将常量放入常量池,常量池方法区的一部分回收方法区的内存限制。
直接内存
直接内存并不是虚拟机运行时数据区的一部分,这部分内存被频繁使用,也有可能导致OutOfMemoryError异常出现。jdk1.4加入的nio类 ,引入了通道(channel)和缓冲区(buffer)的io方式,它可以使用native函数库直接分配堆外内存,通过一个存储在java堆里的DirectByteBuffer对象作为这块内存的引用进行操作,这样在一定场景下能提高性能,因为避免了java堆和native堆里来回复制数据。
2. 垃圾收集算法
2.1 如何判断对象是否可以回收
引用计数法
在对象中添加一个引用计数器,每当有一个地方引用,计数器加一,否则计数器减一。引用计数法虽然占用一些额外的内存进行计数,原理简单,判定效率也很高,主流java虚拟机没有使用这个方法,缺点无法解决对象相互循环引用的问题。
可达性分析算法
主流商用程序语言的内存管理系统都是使用可达性分析算法来判断对象是否存活,这个算法的基本思路是通过一系列的“GC Goots”的根对象作为起始节点集,从这些节点开始,,根据引用关系向下搜索,搜索过程所走过的路径被称为引用链,如果这个对象到GC Root没有引用链相连就证明对象不可能在被使用了。
Java中可作为GC Root的对象包含:
虚拟机栈中应用的对象,譬如各个线程被调用的方法堆栈中使用到的参数,局部变量,临时变量等
在方法区静态属性引用的对象,譬如java类中的引用类型静态变量。
在方法区常量引用的对象,譬如字符串常量池里的引用
2.2 垃圾回收算法
标记清除
标记复制
标记整理
3.垃圾收集器
Serial [ˈsɪəriəl] 收集器
ParNew收集器
Parallel Scavenge [ˈpærəlel] [ˈskævɪndʒ]收集器
Serial Old收集器
Parallel Old收集器
CMS收集器
Garbage Fiest(G1)收集器
Shenandoah收集器