JVM原理速记复习Java虚拟机总结思维导图面试必备,
JVM原理速记复习Java虚拟机总结思维导图面试必备,
良心制作,右键另存为保存
喜欢可以点个赞哦
Java虚拟机
一、运行时数据区域
线程私有
程序计数器
- 记录正在执行的虚拟机字节码指令的地址(如果正在执行的是Native方法则为空),是唯一一个没有规定OOM(OutOfMemoryError)的区域。
Java虚拟机栈
- 每个Java方法在执行的同时会创建一个栈桢用于存储局部变量表、操作数栈、动态链接、方法出口等信息。从方法调用直到执行完成的过程,对应着一个栈桢在Java虚拟机栈中入栈和出栈的过程。(局部变量包含基本数据类型、对象引用reference和returnAddress类型)
本地方法栈
- 本地方法栈与Java虚拟机栈类似,它们之间的区别只不过是本地方法栈为Native方法服务。
线程公有
Java堆(GC区)(Java Head)
- 几乎所有的对象实例都在这里分配内存,是垃圾收集器管理的主要区域。分为新生代和老年代。对于新生代又分为Eden空间、From Survivor空间、To Survivor空间。
JDK1.7 方法区(永久代)
- 用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
对这块区域进行垃圾回收的主要目的是对常量池的回收和对类的卸载,但是一般难以实现。
HotSpot虚拟机把它当做永久代来进行垃圾回收。但很难确定永久代的大小,因为它受到很多因素的影响,并且每次Full GC之后永久代的大小都会改变,所以经常抛出OOM异常。
从JDK1.8开始,移除永久代,并把方法区移至元空间。
运行时常量池
- 是方法区的一部分
Class文件中的常量池(编译器生成的字面量和符号引用)会在类加载后被放入这个区域。
允许动态生成,例如String类的intern()
JDK1.8 元空间
- 原本存在方法区(永久代)的数据,一部分移到了Java堆里面,一部分移到了本地内存里面(即元空间)。元空间存储类的元信息,静态变量和常量池等放入堆中。
直接内存
- 在NIO中,会使用Native函数库直接分配堆外内存。
二、HotSpot虚拟机
对象的创建
- 当虚拟机遇到一条new指令时
对象的内存布局
-
-
- 是对象真正存储的有效信息,也就是在代码中定义的各种类型的字段内容。
-
- 不是必然存在的,仅仅起着占位符的作用。
HotSpot需要对象的大小必须是8字节的整数倍。
对象的访问定位
句柄访问
- 在Java堆中划分出一块内存作为句柄池。
Java栈上的对象引用reference中存储的就是对象的句柄地址,而句柄中包含了到对象实例数据的指针和到对象类型数据的指针。
对象实例数据在Java堆中,对象类型数据在方法区(永久代)中。
优点:在对象被移动时只会改变句柄中的实例数据指针,而对象引用本身不需要修改。
直接指针访问(HotSpot使用)
- Java栈上的对象引用reference中存储的就是对象的直接地址。
在堆中的对象实例数据就需要包含到对象类型数据的指针。
优点:节省了一次指针定位的时间开销,速度更快。
三、垃圾收集
概述
- 垃圾收集主要是针对Java堆和方法区。
程序计数器、Java虚拟机栈个本地方法栈三个区域属于线程私有,线程或方法结束之后就会消失,因此不需要对这三个区域进行垃圾回收。
判断对象是否可以被回收
第一次标记(缓刑)
引用计数算法
- 给对象添加一个引用计数器,当对象增加一个引用时引用计数值++,引用失效时引用计数值--,引用计数值为0时对象可以被回收。
程序计数器
- 记录正在执行的虚拟机字节码指令的地址(如果正在执行的是Native方法则为空),是唯一一个没有规定OOM(OutOfMemoryError)的区域。
Java虚拟机栈
- 每个Java方法在执行的同时会创建一个栈桢用于存储局部变量表、操作数栈、动态链接、方法出口等信息。从方法调用直到执行完成的过程,对应着一个栈桢在Java虚拟机栈中入栈和出栈的过程。(局部变量包含基本数据类型、对象引用reference和returnAddress类型)
本地方法栈
- 本地方法栈与Java虚拟机栈类似,它们之间的区别只不过是本地方法栈为Native方法服务。
Java堆(GC区)(Java Head)
- 几乎所有的对象实例都在这里分配内存,是垃圾收集器管理的主要区域。分为新生代和老年代。对于新生代又分为Eden空间、From Survivor空间、To Survivor空间。
JDK1.7 方法区(永久代)
- 用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
对这块区域进行垃圾回收的主要目的是对常量池的回收和对类的卸载,但是一般难以实现。
HotSpot虚拟机把它当做永久代来进行垃圾回收。但很难确定永久代的大小,因为它受到很多因素的影响,并且每次Full GC之后永久代的大小都会改变,所以经常抛出OOM异常。
从JDK1.8开始,移除永久代,并把方法区移至元空间。 运行时常量池
- 是方法区的一部分
Class文件中的常量池(编译器生成的字面量和符号引用)会在类加载后被放入这个区域。
允许动态生成,例如String类的intern()
- 是方法区的一部分
JDK1.8 元空间
- 原本存在方法区(永久代)的数据,一部分移到了Java堆里面,一部分移到了本地内存里面(即元空间)。元空间存储类的元信息,静态变量和常量池等放入堆中。
直接内存
- 在NIO中,会使用Native函数库直接分配堆外内存。
- 是对象真正存储的有效信息,也就是在代码中定义的各种类型的字段内容。
- 不是必然存在的,仅仅起着占位符的作用。
HotSpot需要对象的大小必须是8字节的整数倍。
句柄访问
- 在Java堆中划分出一块内存作为句柄池。
Java栈上的对象引用reference中存储的就是对象的句柄地址,而句柄中包含了到对象实例数据的指针和到对象类型数据的指针。
对象实例数据在Java堆中,对象类型数据在方法区(永久代)中。
优点:在对象被移动时只会改变句柄中的实例数据指针,而对象引用本身不需要修改。
直接指针访问(HotSpot使用)
- Java栈上的对象引用reference中存储的就是对象的直接地址。
在堆中的对象实例数据就需要包含到对象类型数据的指针。
优点:节省了一次指针定位的时间开销,速度更快。
程序计数器、Java虚拟机栈个本地方法栈三个区域属于线程私有,线程或方法结束之后就会消失,因此不需要对这三个区域进行垃圾回收。
第一次标记(缓刑)
引用计数算法
- 给对象添加一个引用计数器,当对象增加一个引用时引用计数值++,引用失效时引用计数值--,引用计数值为0时对象可以被回收。
但是它难以解决对象之间的相互循环引用的情况,此时这个两个对象引用计数值为1,但是永远无法用到这两个对象。
- 可达性分析算法(Java使用)
- 以一系列GC Roots的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连是,则证明此对象不可用,可以被回收。
GC Roots对象包括
第二次标记
- 当对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过。
如果对象在finalize方法中重新与引用链上的任何一个对象建立关联则将不会被回收。 finalize()
- 任何一个对象的finalize()方法都只会被系统调用一次。
它的出现是一个妥协,运行代价高昂,不确定性大,无法保证各个对象的调用顺序。
finalize()能做的所有工作使用try-finally或者其他方式都可以做的更好,完全可以忘记在这个函数的存在。
- 任何一个对象的finalize()方法都只会被系统调用一次。
- 当对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过。
方法区的回收
- 在方法区进行垃圾回收的性价比一般比较低。
主要回收两部分,废弃常量和无用的类。
满足无用的类三个判断条件才仅仅代表可以进行回收,不是必然关系,可以使用-Xnoclassgc参数控制。
引用类型
-
- 使用new一个新对象的方式来创建强引用。
只要强引用还存在,被引用的对象则永远不会被回收。
- 使用new一个新对象的方式来创建强引用。
-
- 使用SoftReference类来实现软引用。
用来描述一些还有用但是并非必须的对象,被引用的对象在将要发生内存溢出异常之前会被回收。
- 使用SoftReference类来实现软引用。
-
- 使用WeakReference类来实现弱引用。
强度比软引用更弱一些,被引用的对象在下一次垃圾收集时会被回收。
- 使用WeakReference类来实现弱引用。
-
- 使用PhantomReference类来实现虚引用。
最弱的引用关系,不会对被引用的对象生存时间构成影响,也无法通过虚引用来取得一个对象实例。
唯一目的就是能在这个对象被收集器回收时收到一个系统通知。
- 使用PhantomReference类来实现虚引用。
垃圾收集算法
-
- 首先标记出所有需要回收的对象,在标记完成后统一回收被标记的对象并取消标记。
不足:
-
- 和标记 - 清除算法一样,但标记之后让所有存活对象都向一段移动,然后直接清理掉端边界以外的内存。
解决了标记 - 清除算法的空间问题,但需要移动大量对象,还是存在效率问题。
- 和标记 - 清除算法一样,但标记之后让所有存活对象都向一段移动,然后直接清理掉端边界以外的内存。
-
- 将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用多的内存空间一次清理掉。
代价是将内存缩小为原来的一般,太高了。
- 将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用多的内存空间一次清理掉。
现在商业虚拟机都采用这种算法用于新生代。
因为新生代中的对象98%都是朝生暮死,所以将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中一块Survivor空间。
当回收时,如果另外一块Survivor空间没有足够的空间存放存活下来的对象时,这些对象将直接通过分配担保机制进入老年代。
-
- 一般把Java堆分为新生代和老年代。
在新生代中使用复制算法,在老年代中使用标记 -清除 或者 标记 - 整理 算法来进行回收。
- 一般把Java堆分为新生代和老年代。
HotSpot的算法实现
枚举根节点(GC Roots)
- 目前主流Java虚拟机使用的都是准确式GC。
GC停顿的时候,虚拟机可以通过OopMap数据结构(映射表)知道,在对象内的什么偏移量上是什么类型的数据,而且特定的位置记录着栈和寄存器中哪些位置是引用。因此可以快速且准确的完成GC Roots枚举。
- 目前主流Java虚拟机使用的都是准确式GC。
安全点
- 为了节省GC的空间成本,并不会为每条指令都生成OopMap,只是在“特定的位置”记录OopMap,这些位置称为安全点。
程序执行只有到达安全点时才能暂停,到达安全点有两种方案。
但是当线程sleep或blocked时无法响应JVM的中断请求走到安全点中断挂起,所以引出安全区域。
安全区域
- 安全区域是指在一段代码片段之中,引用关系不会发生变化,是扩展的安全点。
线程进入安全区域时表示自己进入了安全区域,这个发生GC时,JVM就不需要管这个线程。
线程离开安全区域时,检查系统是否完成GC过程,没有就等待可以离开安全区域的信号为止,否者继续执行。
垃圾收集器
新生代
-
- 它是单线程收集器,只会使用一个线程进行垃圾收集工作,更重要的是它在进行垃圾收集时,必须暂停其他所有的工作线程。
-
优点:对比其他单线程收集器简单高效,对于单个CPU环境来说,没有线程交互的开销,因此拥有最高的单线程收集效率。
它是Client场景下默认新生代收集器,因为在该场景下内存一般来说不会很大。
- 2. parnew收集器
- 它是Serial收集器的多线程版本,公用了相当多的代码。
在单CPU环境中绝对不会有比Serial收集器更好的效果,甚至在2个CPU环境中也不能百分之百超越。
它是Server场景下默认的新生代收集器,主要因为除了Serial收集器,只用它能与CMS收集器配合使用。
- 3. parallel scavenge收集器
- “吞吐优先”收集器,与ParNew收集器差不多。
但是其他收集器的目标是尽可能缩短垃圾收集时用户线程停顿的时间,而它的目标是达到一个可控制的吞吐量。这里的吞吐量指CPU用于运行用户程序的时间占总时间的比值。
老年代
-
- 是Serial收集器老年代版本。
-
也是给Client场景下的虚拟机使用的。
- 5. parallel old收集器
- 是Parallel Scavenge收集器的老年代版本。
在注重吞吐量已经CPU资源敏感的场合,都可以优先考虑Parallel Scavenge和Parallel Old收集器。
- 6. cms收集器
- Concurrent Mark Sweep收集器是一种以获取最短回收停顿时间为目标的收集器。
- 运作过程
- 1. 初始标记(最短)。仍需要暂停用户线程。只是标记一下GC Roots能直接关联到的对象,速度很快
1 和4 两个步骤并没有带上并发两个字,即这两个步骤仍要暂停用户线程。
- 优缺点
- 并发收集、低停顿。
-
- Garbage First是一款面向服务端应用的垃圾收集器
运作过程
五、类加载机制
概述
- 虚拟机把描述类的数据从Class问价加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型。
Java应用程序的高度灵活性就是依赖运行期动态加载和动态连接实现的。
类的生命周期
- 加载 -> 连接(验证 -> 准备 -> 解析) -> 初始化 -> 使用 - >卸载
类初始化时机
主动引用
- 虚拟机规范中没有强制约束何时进行加载,但是规定了有且只有五种情况必须对类进行初始化(加载、验证、准备都会随之发生)
被动引用
- 除上面五种情况之外,所有引用类的方式都不会触发初始化,称为被动引用。
类加载过程
-
- 为了确保Class文件的字节类中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。可以通过-Xverify:none关闭大部分类验证。
-
- 类变量是被static修饰的变量,准备阶段为类变量分配内存并设置零值(final直接设置初始值),使用的是方法区的内存。
-
- 将常量池内的符号引用替换为直接引用的过程。
其中解析过程在某些情况下可以在初始化阶段之后再开始,这是为了支持Java的动态绑定。
解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄、和调用点限定符。
- 将常量池内的符号引用替换为直接引用的过程。
-
初始化阶段才真正执行类中定义的Java程序代码,是执行类构造器
()方法的过程。
在准备阶段,类变量已经给过零值,而在初始化阶段,根据程序员通过程序制定的主观计划去初始化类变量和其他资源。() - 类构造器方法。是由编译器自动收集类中的所有类变量的赋值动作和静态语句块中的的语句合并产生的。
类(加载) 器
类与类加载器
- 类加载器实现类的加载动作。
类加载器和这个类本身一同确立这个类的唯一性,每个类加载器都有独立的类命名空间。在同一个类加载器加载的情况下才会有两个类相等。
相等包括类的Class对象的equals()方法、isAssignableFrom()方法、isInstance()、instanceof关键字。
- 类加载器实现类的加载动作。
类加载器分类
启动类加载器
- 由C++语言实现,是虚拟机的一部分。负责将JAVA_HOME/lib目录中,或者被-Xbootclasspath参数指定的路径,但是文件名要能被虚拟机识别,名字不符合无法被启动类加载器加载。启动类加载器无法被Java程序直接引用。
扩展类加载器
- 由Java语言实现,负责加载JAVA_HOME/lib/ext目录,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器。
应用程序类加载器
- 由于这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也称他为系统类加载器。负责加载用户类路径(ClassPath)上所指定的类库,一般情况下这个就是程序中默认的类加载器。
自定义类加载器
- 由用户自己实现。
双亲委派模型
- 双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应该有自己的父类加载器。父子不会以继承的关系类实现,而是都是使用组合关系来服用父加载器的代码。
在java.lang.ClassLoader的loadClass()方法中实现。 工作过程
- 一个类加载器首先将类加载请求转发到父类加载器,只有当父类加载器无法完成(它的搜索范围中没有找到所需要的类)时才尝试自己加载
好处
- Java类随着它的类加载器一起具备了一种带有优先级的层次关系,从而使得基础类库得到同意。
- 双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应该有自己的父类加载器。父子不会以继承的关系类实现,而是都是使用组合关系来服用父加载器的代码。
四、内存分配与回收策略
Minor GC 和 Full GC
Minor GC
- 发生在新生代的垃圾收集动作,因为新生代对象存活时间很短,因此Minor GC会频繁执行,执行速度快。
时机
- Eden不足
Full GC
- 发生在老年区的GC,出现Full GC时往往伴随着Minor GC,比Minor GC慢10倍以上。
时机
-
- 只是建议虚拟机执行Full GC,但是虚拟机不一定真正去执行。
不建议使用这种方式,而是让虚拟机管理内存。
- 只是建议虚拟机执行Full GC,但是虚拟机不一定真正去执行。
-
- 常见场景就是大对象和长期存活对象进入老年代。
尽量避免创建过大的对象以及数组,调大新生代大小,让对象尽量咋新生代中被回收,不进入老年代。
- 常见场景就是大对象和长期存活对象进入老年代。
-
- 当系统中要加载的类、反射的类和常量较多时,永久代可能会被占满,在未配置CMS GC的情况下也会执行Full GC,如果空间仍然不够则会抛出OOM异常。
可采用增大方法区空间或转为使用CMS GC。
- 当系统中要加载的类、反射的类和常量较多时,永久代可能会被占满,在未配置CMS GC的情况下也会执行Full GC,如果空间仍然不够则会抛出OOM异常。
-
- 发生Minor GC时分配担保的两个判断失败
-
- CMS GC 并发清理阶段用户线程还在执行,不断有新的浮动垃圾产生,当预留空间不足时报Concurrent Mode Failure错误并触发Full GC。
-
内存分配策略
-
- 大多数情况下,对象在新生代Eden上分配,当Eden空间不够时,发起Minor GC,当另外一个Survivor空间不足时则将存活对象通过分配担保机制提前转移到老年代。
-
- 配置参数-XX:PretenureSizeThreshold,大于此值得对象直接在老年代分配,避免在Eden和Survivor之间的大量内存复制。
-
- 虚拟机为每个对象定义了一个Age计数器,对象在Eden出生并经过Minor GC存活转移到另一个Survivor空间中时Age++,增加到默认16则转移到老年代。
-
- 虚拟机并不是永远要求对象的年龄必须到达MaxTenuringThreshold才能晋升老年代,如果在Survivor中相同年龄所有对象大小总和大于Survivor空间的一半,则年龄大于或等于该年龄的对象直接进入老年代。
-
- 在发生Minor GC之前,虚拟机先检查老年代最大可用的连续空间是否大于新生代的所有对象,如果条件成立,那么Minor GC可以认为是安全的。
可以通过HandlePromotionFailure参数设置允许冒险,此时虚拟机将与历代晋升到老年区对象的平均大小比较,仍小于则要进行一次Full GC。
在JDK1.6.24之后HandlePromotionFailure已无作用,即虚拟机默认为true。
- 在发生Minor GC之前,虚拟机先检查老年代最大可用的连续空间是否大于新生代的所有对象,如果条件成立,那么Minor GC可以认为是安全的。
相关文章
- 暂无相关文章
用户点评