JVM之内存区域与垃圾收集
jvm是什么?
JVM是JRE的一部分。它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。JVM有自己完善的硬件架构,如处理器、堆栈、寄存器等,还具有相应的指令系统。Java语言最重要的特点就是跨平台运行。使用JVM就是为了支持与操作系统无关,实现跨平台。所以,JAVA虚拟机JVM是属于JRE的,而现在我们安装JDK时也附带安装了JRE(当然也可以单独安装JRE)。
JDK和JRE有什么区别?
JDK是java的开发包,其中包括JRE,JRE仅仅是java的运行时环境。而JDK包括了同版本的JRE,此外还包括有编译器和其它工具。即JDK是java开发环境JRE是java运行环境,JDK包含了JRE。
JVM内存区域划分
由于JVM是自己模拟了计算机的功能,所以可以将jvm的内存区域类比于计算机的各个功能组件。
程序计数器(寄存器)
程序计数器用于指示当前执行的字节码的行号,用于实现分支循环、转跳、异常处理、线程恢复等功能。由于在切换线程时需要保存各个线程运行的位置,所以程序计数器是线程隔离的
虚拟机栈(栈)
用于描述Java方法执行的内存模型,每个方法执行时都会在虚拟机栈中创建一个栈帧,用于保存局部变量、操作数栈、动态连接、方法区出口等信息,每个方法从执行到结束都对应着从入栈到出栈的过程,由于每个线程都执行自己的方法,所以它是线程隔离的。会抛出OutOfMemoryError和StackOverFlowEroor异常。
相关调优参数:
-Xss 设置线程栈的大小
本地方法栈(栈)
用于运行native方法的栈。它是线程隔离的。会抛出OutOfMemoryError和StackOverFlowEroor异常。
java堆(堆)
用于存放对象的实例,也用于存放数组,是GC工作的主要区域。是线程共享的。会抛出OutOfMemoryError异常
相关调优参数:
-Xmx 设置堆的最大值 -Xms 设置堆的最小值
-Xmn |-XX:NewSize 设置新生代的大小
-XX:NewRatio 设置老年代与新生代的比例 -XX:SurviorRatio Eden区与Survivor区的比例
-XX:TargertSurvirvorRatio设置担保的阀值,到达这个大小的值,直接被送到老年代
方法区(特殊的堆)
它用于储存那些经常用的数据,比如常量、静态变量、已被加载的类信息、以及已经编译好的代码(可能这就是有虚拟机叫HotSpot的原因?),所以他也是线程之间共享的,也会抛出OutOfMemory异常。
在方法区中有一块区域叫做运行时常量池,这里会保存class文件中的常量池,包括编译期间生成的各种字面量以及符号引用,字符串对象就是放在这个地方的。
直接内存
在JDK1.4中引入了NIO为了避免在java堆和Native堆中来回切换数据,在java堆中使用DirectByteBuffer引用了一个堆外内存,这块堆外内存在一些情况下可以显著提高性能。
相关调优参数
-XX:PermSize:设置方法区最大值 -xx:PermSize:设置方法区最小值
当加载并使用一个对象时是jvm是怎么做的?
- 检查类是否已经被加载若没有加载这去加载这个类
- 为对象在堆中分配内存,有两种方式分配内存一种是指针碰撞法,一种是空闲表法。在多线程进行时分配内存可能会产生错误,由此产生了两种解决办法,一种是为分配内存的操作加上CAS锁,另一种是使用本地线程分配缓冲池(TLAB)
- 为对象进行必要的配置,指明对象是哪个类的实例,设置对象的元数据、哈希码、GC分代年龄等信息。
- 执行方法,使程序按照意愿初始化
如何去访问一个对象
- 使用句柄访问 在java堆中维护一个句柄池,句柄池指向堆中的对象实例数据,和方法区中的对象类型数据。
- 通过直接指针访问 java堆中的对象实例数据保存着指向方法区方法类型数据的指针。
垃圾收集
垃圾收集器是jvm最重要的功能之一,它能帮我们自动的回收内存。要回收一个对象涉及几个问题,1、怎么判断对象是否可以回收 3、何时回收对象 2、怎样回收对象
对象是否可以回收
- 引用计数器法 每个对象被引用一次,他的值加一,没有被引用的对象计数值为零。这种方法无法解决循环引用的问题
- 可达性分析算法 找到GCRoot作为起始点,通过这个节点找到他的引用链,没有被引用的对象可以回收。在jvm中可作为GC Root的对象有下面几种:方法区种静态属性引用的对象、虚拟机栈种引用的对象、方法区种常量池种引用的对象、本地方法栈种引用的对象
引用的分类:
强引用:只要有强引用GC永远不会回收
软引用:在没有内存空间时会被回收
弱引用:只能生存到下一次GC发生前
虚引用:GC不会理会,只是为了找到另一个对象的信息
如何回收对象
- 标记-清除 分为标记和清除两个阶段,首先标记要清除的对象,标记完后统一回收。不足之处有两个,一是会产生大量内存碎片二是效率不够高。
- 复制算法 将内存区域分为两块,每次只使用其中一块,当这块内存区域没有空间了,就将它上面还存活的对象复制到另一块上,然后清除之前使用的那块内存区域。
在JVM中就是使用这种算法回收新生代的内存区域的,将新生代划分为一块Eden空间和两块较小的Survivor空间(如果只有一块Survivor当进行Minor GC时当Eden和Surivor都有数据时容易产生碎片,所以需要一块区域始终保存是空的状态),当回收时,将Eden和另一块Survivor还存活的对象复制到另一块Survivor对象中,再清理掉刚才使用的Eden和Survivor空间。
- 标记-整理算法 第一步标记,第二步把生存的对象都向一段移动。用于老年代
何时回收对象
- OopMap 由于回收对象需要枚举根节点,所以GC时会导致程序的停顿,所以我们要尽可能的减少他们的停顿,但是直接去遍历所有节点找到可以清除的对象是十分耗时的,于是JVM使用了一种叫OopMapde 数据结构用于记录被引用对象的位置,再OopMap的协助下可以快速的完成GC Root的枚举。
- 安全点 如果给每条指令都建立OopMap建立OopMap会耗费大量的空间,所有JVM只再特定的位置上记录了这些位置,这些位置就叫检查点。到检查点有两种方式,主动式中断和被动式中断。
- 安全区 使不会运行的程序也可以进入GC。
- 对象回收策略 大对象直接进入老年代、长期存活的对象将进入老年代、当某个年龄的对象太多时,将大于这个年龄的对象全部放入老年代
具体的垃圾收集器
为了不同的使用需求JVM设计了不同的垃圾收集器,不同的垃圾收集器有不同的适用状况:
- 新生代垃圾收集器(复制算法)
- Serial 采用复制算法的单线程垃圾收集器,简单高效,每次停顿时间少适用于客户端。
- ParNew 采用复制算法的多线程收集器。再多CPU条件下表现较好,是唯一能和CMS配合的新生代多线程收集器
- Parallel Scavenge 采用复制算法的多线程收集器,可以控制GC停顿时间,重点在于提升吞吐量,适用于服务端
-XXMacGCPauseMills 用于控制停顿时间
-XXGCTimeRatio 用于控制吞吐量 - 老年代垃圾收集器
- Serial Old Serial的老年版本,采用标记整理算法GC
- Parallel Old ParallOld的老年版本,采用多线程的标记整理算法
- CMS 以获取最短停顿时间为目标的老年代收集器,基于标记清除算法。分为四个阶段:初始标记、并发标记、重新标记、并发清除
- G1收集器(基于标记整理)
G1收集器将内存区域划分为多个大小相等的独立区域,它会跟踪各个区域堆积的垃圾价值大小来选择回收的顺序,以此尽量的提高收集的效率
特点:并行与并发、分代收集、空间整合、可预测的停顿