Day10
1. 简单说下你对JVM的了解
- JVM是Java语言跨平台的关键,Java在虚拟机层面隐藏了底层技术的复杂性以及机器与操作系统的差异性。运行程序的物理机千差万别,而JVM则在千差万别的物理机上面提供了统一的运行平台,实现了在任意一台JVM上编译的程序,都能在任何其他JVM正常运行。
- JVM由三部分组成:类加载子系统、执行引擎和运行时数据区
- 类加载子系统:可以根据指定的全限定名来载入类或接口
- 执行引擎:负责执行那些包含在被载入类的方法中的指令
- 当程序运行时,JVM需要内存来存储许多内容,例如:字节码、对象、参数、返回值、局部变量、运算的中间结果等,JVM会把这些东西都存储到运行时数据区中,以便于管理。而运行时数据区又可以分为方法区、堆、虚拟机栈、本地方法栈、程序计数器
2. 说说你了解的JVM内存模型
- JVM内存模型主要由三部分组成:类加载子系统、执行引擎、运行时数据区。
- 类加载子系统:根据全限定名称来载入类或接口。3.
- 执行引擎:负责执行那些被包含在被载入类的方法中的指令。4.
- 运行时数据区,用来存储字节码,对象,参数,返回值,局部变量及运行结果等。5.
- 运行时数据区包括:方法区,堆,虚拟机栈,本地方法栈,程序技计数器。6.6.
- 方法区和堆被所有线程所共享,存在线程安全问题。7.7.
- 虚拟机栈,本地方法栈和程序计数器线程私有,不存在线程安全问题。
3. 说说Java运行时数据区
- 程序计数器 程序计数器是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。在Java虚拟机的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,它是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。 由于Java虚拟机的多线程是通过线程轮流切换、分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器都只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。
- 虚拟机栈 虚拟机栈也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的线程内存模型:每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧用于存储局部变量表、操作数栈、动态连接、方法出口等信息。每一个方法被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。 经常有人把Java内存区域笼统地划分为堆和栈,这种划分方式直接继承自传统的C、C++程序的内存布局结构,在Java语言里就显得有些粗糙了,实际的内存区域划分要比这更复杂。不过这种划分方式的流行也间接说明了程序员最关注的、与对象内存分配关系最密切的区域是堆和栈两块。其中,栈通常就是指这里讲的虚拟机栈,或者更多的情况下只是指虚拟机栈中局部变量表部分。 在Java虚拟机规范中,对这个内存区域规定了两类异常状况:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常。如果Java虚拟机栈容量可以动态扩展,当栈扩展时无法申请到足够的内存会抛出OutOfMemoryError异常。
- 本地方法栈 本地方法栈与虚拟机栈所发挥的作用是非常相似的,其区别只是虚拟机栈为虚拟机执行Java方法服务,而本地方法栈则是为虚拟机使用到的本地方法服务。 Java虚拟机规范对本地方法栈中方法使用的语言、使用方式与数据结构并没有任何强制规定,因此具体的虚拟机可以根据需要自由实现它,甚至有的Java虚拟机直接就把本地方法栈和虚拟机栈合二为一。 与虚拟机栈一样,本地方法栈也会在栈深度溢出或者栈扩展失败时分别抛出StackOverflowError和OutOfMemoryError异常。
- 堆 堆是虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,Java世界里“几乎”所有的对象实例都在这里分配内存。堆是垃圾收集器管理的内存区域,因此一些资料中它也被称作“GC堆”。 堆既可以被实现成固定大小的,也可以是可扩展的,不过当前主流的Java虚拟机都是按照可扩展来实现的。如果在Java堆中没有内存完成实例分配,并且堆也无法再扩展时,JVM将会抛出OutOfMemoryError异常。
- 方法区 方法区与堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。虽然Java虚拟机规范中把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫作“非堆”,目的是与堆区分开来。 运行时常量池是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池表,用于存放编译期生成的各种字面量与符号引用。 根据Java虚拟机规范的规定,如果方法区无法满足新的内存分配需求时,将抛OutOfMemoryError异常。
4. 说说JVM的垃圾回收机制
- 垃圾回收通常有三件事情要做,第一将决定哪些对象要回收,JVM决定了将未被引用的对象占用空间回收,通常使用引用计数法或者可达性分析法,后者是JVM主流使用方法。第二是什么时候回收,通常在内存不够用或者定期回收。第三是用什么方式回收,有分代回收,分区域回收,收集器有年轻代收集器Serial,ParNew,Parallel Scavenge,老年代有CMS,Serial Old,Parallel Old,G1是分区回收器的新工具,可以并发标记,并发回收
- JVM垃圾回收主要分为两个阶段,垃圾标记阶段和垃圾清除阶段,垃圾标记主要有两种算法,引用计数算法和可达性分析算法,引用计数算法无法解决循环依赖问题,所以java垃圾回收器主要使用可达性分析算法。垃圾清除阶段有标记-清除法、复制算法、标记压缩算法和增量收集算法。垃圾回收主要发生在堆,堆分为新生代、老年代,对新生代的垃圾收集称为Minor GC,对老年代的垃圾收集称为Major GC,整堆垃圾收集称为FUll GC;首先新生成的对象都是放在Eden区,当Eden区满了不能创建对象,则触发Minor GC,将无用对象清除,将有用对象复制到Survivor中的任意一个(如s1),同时清空Eden区。当Eden区再次满了,将S1和Eden区有用的对象复制到S2区,如此循环下去,保证Survivor区一个有用,一个为空,Survivor区中对象有年龄,默认超过了15次GC的,复制到Old区,Old区的对象达到一定比例启动Major GC。当Old区也满了将会触发一次完整的垃圾回收Full GC
5. 说说JVM的垃圾回收算法
- 标记清除算法:分为标记和清除两个阶段。标记所有需要回收的对象,标记结束后统一回收。
- 标记复制算法:为了解决效率问题,复制算法就出现了。它将可用的内存按容量划分为两等分,每次只使用其中一块。和survior一样也是用from和to两个指针。fromPlace存满了,就把存活的对象copy到另一块toPlace上,然后交换指针的内容。这样就解决了内存碎片的问题。这个算法的代价就是把内存缩水了,这样堆内存的使用效率就会变得十分低下了
- 标记整理算法:复制算法在对象存活率高的时候会有一定的效率问题,标记过程仍然与标记清除算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活对象都向一端移动,然后直接清理掉边界以外的内存
- 分代收集算法:根据对象存活周期的不同将内存划分为几块。一般都是把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法