1、内存区域模型
JVM内存分为:虚拟机栈、堆、方法区、程序计数器、本地方法栈五个部分。
程序计数器(线程私有):
是当前线程所执行的字节码的行号指示器,每条线程都要有一个独立的程序计数器,这类内存也称为“线程私有”的内存。
正在执行java方法的话,计数器记录的是虚拟机字节码指令的地址(当前指令的地址)。如果还是Native方法,则为空。
这个内存区域是唯一一个在虚拟机中没有规定任何OutOfMemoryError情况的区域。
Java虚拟机栈(线程私有):
也是线程私有的。
每个方法在执行的时候会创建一个栈帧,存储了局部变量表,操作数栈,动态连接,方法返回地址等。
每个方法从调用到执行完毕,对应一个栈帧在虚拟机栈中的入栈和出栈。
通常所说的栈,一般是指虚拟机栈中的局部变量表部分。
局部变量表所需的内存在编译期间完成分配。
如果线程请求的栈深度大于虚拟机所允许的深度,则StackOverflowError。
如果虚拟机栈可以动态扩展,扩展到无法申请足够的内存,则OutOfMemoryError。
-Xss来指定jvm方法栈大小
本地方法栈(线程私有):
和虚拟机栈类似,主要为虚拟机使用到的Native方法服务。
也会抛出StackOverflowError和OutOfMemoryError。
Java堆(线程共享):
被所有线程共享的一块内存区域,在虚拟机启动时创建,用于存放对象实例。
堆可以按照可扩展来实现(通过-Xmx和-Xms来控制)
-Xms :表示java虚拟机堆区内存初始内存分配的大小
-Xmx:表示java虚拟机堆区内存可被分配的最大上限。
当堆中没有内存可以分配给实例,也无法再扩展时,则抛出OutOfMemoryError异常。
问题:为什么-Xms、-Xmx要设置一样大?
服务器一般设置-Xms、-Xmx 相等以避免在每次GC 后调整堆的大小。
方法区(线程共享):
被所有线程共享的一块内存区域。
用于存储已被虚拟机加载的类信息,常量,静态变量等。
这个区域的内存回收目标主要针对常量池的回收和对类型的卸载。
当方法区无法满足内存分配需求时,则抛出OutOfMemoryError异常。
在HotSpot虚拟机中,用永久代来实现方法区,将GC分代收集扩展至方法区,但是这样容易遇到内存溢出的问题。
**JDK1.7中,已经把放在永久代的字符串常量池移到堆中。
JDK1.8撤销永久代,引入元空间。**
运行时常量池:
是方法区的一部分,用于存放编译期生成的各种字面量和符号引用。
当常量池无法再申请到内存时,则抛出OutOfMemoryError异常。
直接内存:
不是运行时数据区的一部分,但也可能抛出OutOfMemoryError异常。
在JDK1.4中新加入的NOI类,引入了一种基于通道与缓冲区的I/O方式,它可以使用Native函数直接分配堆外内存,
然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。
2、java内存
(1)内存溢出和内存泄漏
内存泄漏:分配出去的内存回收不了
内存溢出:指系统内存不够用了
3、GC算法
3.1 GC算法
在JVM实现中,往往不是采用单一的一种算法进行回收,而是采用几种不同的算法组合使用,来达到最好的收集效果。接下来详细介绍几种垃圾收集算法的思想及发展过程。
(1)最基础的收集算法 ——标记/清除算法
分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象
(2)复制算法
为了解决效率问题,复制算法出现了。复制算法的原理是:将可用内存按容量划分为大小相等的两块,每次使用其中的一块。当这一块的内存用完了,就将还存活的对象复制到另一块内存上,然后把这一块内存所有的对象一次性清理掉。
(3)标记/整理算法
标记/整理算法的标记过程任然与标记/清除算法一样,但后续步骤不是直接对可回收对象进行回收,而是让所有存活的对象都向一端移动,然后直接清理掉端边线以外的内存。
(4)终极算法 —— 分代收集算法
它是对前几种算法的实际应用。分代收集算法的思想是按对象的存活周期不同将内存划分为几块,一般是把Java堆分为新生代和老年代(还有一个永久代,是HotSpot特有的实现,其他的虚拟机实现没有这一概念,永久代的收集效果很差,一般很少对永久代进行垃圾回收),这样就可以根据各个年代的特点采用最合适的收集算法。
(5)串行收集器(Serial Collector)
串行收集器使用一个单独的线程进行收集,不管是次要收集还是主要收集。
(6)并行收集器(Parallel Collector)
并行收集器有两种形式:一种并行收集器(-XX:+ UseParallelGC)在次要回收中使用多线程来执行,在主要回收中使用单线程执行;另一种是从Java 7u4开始默认使用的并行旧生代收集器(Parallel Oldcollector )(XX:+UseParallelOldGC),在次要回收和主要回收均使用多线程。
3.2 如何确定某个对象是垃圾对象
为了解决这个问题,在Java中采取了 可达性分析法。该方法的基本思想是通过一系列的“GC Roots”对象作为起点进行搜索,如果在“GC Roots”和一个对象之间没有可达路径,则称该对象是不可达的,不过要注意的是被判定为不可达的对象不一定就会成为可回收对象。被判定为不可达的对象要成为可回收对象必须至少经历两次标记过程,如果在这两次标记过程中仍然没有逃脱成为可回收对象的可能性,则基本上就真的成为可回收对象了。
4 类加载器
在类加载的第一阶段“加载”过程中,需要通过一个类的全限定名来获取定义此类的二进制字节流,完成这个动作的代码块就是类加载器。
应用程序自己决定如何获取所需的类。
4.1 双亲委派模型
从Java虚拟机的角度来说,只存在两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),这个类加载器使用C++语言实现(HotSpot虚拟机中),是虚拟机自身的一部分;另一种就是所有其他的类加载器,这些类加载器都有Java语言实现,独立于虚拟机外部,并且全部继承自java.lang.ClassLoader。
(1)双亲委派模型过程
某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。
4.2 java类的装载(Loading)、链接(Linking)和初始化(Initialization)
(1)加载
1.通过类的全名产生对应类的二进制数据流。
2.分析并将这些二进制数据流转换为方法区
3.创建对应类的 java.lang.Class 实例
(2)链接
分为三部分:验证,准备和解析。
(3)初始化
1.如果基类没有被初始化,初始化基类。
2.有类构造函数,则执行类构造函数。