一、运行时数据区域

Java虚拟机在执行Java程序的时候会把它所管理的内存划分为若干个不同的数据区域,这些区域各有用途:

  1. 程序计数器:(线程私有的)

程序计数器是一块较小的内存,可以看作是当前线程所执行的字节码的行号指示器,字节码解释器工作时通过改变这个计数器的值来选取下一条指令。

  1. Java虚拟机栈:(线程私有)

Java虚拟机栈描述的是Java方法执行的内存模型,每个方法执行的同时会创建一个栈帧,用来存储局部变量表,操作数栈,动态链接,方法出口等信息。

  1. 本地方法栈:(线程私有):

本地方法栈为虚拟机使用到Native方法服务。

  1. Java堆:(线程共享)

Java堆存放对象的实例恶,几乎所有对象的实例都在这里分配。Java堆可以细分为新生代和老生代;又可以划分为Eden空间,From Survivor空间和To Survivor空间。

  1. 方法区:(线程共享)

方法区用于存储已经被虚拟机加载的类信息,常量,静态变量,编译器编译后的代码等数据。

  

  运行时常量池是方法区的一部分,用来存放编译期生成的各种字面量和符号引用。

二、对象在虚拟机中创建的过程

  虚拟机遇到一条new指令,首先将去检查这个指令的参数能否在常量池中定位到一个类的符号引用,并且检查这个符号应用代表的类是否已被加载,解析和初始化过,否则执行相应的类加载过程;

  在类加载检查通过之后,虚拟机将为新生的对象分配内存,选择恰当的堆内存分配方式(需要考虑到垃圾收集器和并发分配内存);

  内存分配完成后,虚拟机将分配到的内存空间都初始化为0后,虚拟机对对象进行必要的设置,如这个对象是那个类的实例,类的元数据信息,对象的哈希码,GC分代年龄。

  之后Java程序将进行<init>方法进行初始化,但与虚拟机初始化无关。

三、判断对象是否存在的方法

  

  一般来说,通过引用计数算法,给对象中添加一个引用计数器,每一个地方引用它时,计数器值就加1,当引用失效时,计数器值就减1.任何时候计数器为0时对象就是不能再被使用的。

  但是并不能解决循环引用的问题,需要使用可达性分析,通过一系列称为“GC Root”对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个镀锡那个到GC Root没有任何引用链,则证明此对象是不可用的。

  在Java语言中作为GC Root的对象包括:

  1. 虚拟机栈中的引用对象;

  2. 方法区中类静态属性引用的对象;

  3. 方法区中常量引用的对象;

  4. 本地方法栈中JNI引用的对象。

  引用强度:强引用,软引用,弱引用,虚引用。

  1. 强引用:最普通的引用,一般new出来的对象都是强引用类型,如果是一个强引用,那么GC不会进行回收,当出现内存不足的时候会抛出OutOfMemoryError错误,除非人为将对象设置为null;

  2. 软引用:通过SoftReference进行声明,如果一个对象是软引用类型的话,当出现内存不足的时候就会进行GC,通常软引用类型对内存敏感,可以作为高速缓存;

  3. 弱引用:通过WeakrReference进行声明,具有弱引用的对象生命周期更短,GC一旦发现了弱引用就会回收内存,但由于垃圾回收线程优先级较低,不容易发现这样的弱引用对象;

  4. 虚引用:通过PhantomReference进行声明,声明为虚引用的对象在任何时候都会被回收,通常用来进行垃圾回收的过程。

四、对象的死亡需要经过的过程:

  如果对象在进行可达性分析后发现没有与GC Root相连接将会被第一次标记并进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法,当对象没有覆盖finalize ()方法或者finalize()方法已经被虚拟机调用过,那么这两种情况都视为没有必要执行。

  如果对象被判定为有必要执行finalize()方法,那么对象会放置在一个F-Queue队列中由之后的finalize线程执行。

五、垃圾收集算法:

  • 标记-清除:

这是垃圾收集算法中最基础的,它的思想就是标记哪些要被回收的对象,然后统一回收。这种方法很简单,但是会有两个主要问题:1.效率不高,标记和清除的效率都很低;2.会产生大量不连续的内存碎片,导致以后程序在分配较大的对象时,由于没有充足的连续内存而提前触发一次GC动作。

  • 复制算法:

为了解决效率问题,复制算法将可用内存按容量划分为相等的两部分,然后每次只使用其中的一块,当一块内存用完时,就将还存活的对象复制到第二块内存上,然后一次性清楚完第一块内存,再将第二块上的对象复制到第一块。但是这种方式,内存的代价太高,每次基本上都要浪费一半的内存。

于是将该算法进行了改进,内存区域不再是按照1:1去划分,而是将内存划分为8:1:1三部分,较大那份内存交Eden区,其余是两块较小的内存区叫Survior区。每次都会优先使用Eden区,若Eden区满,就将对象复制到第二块内存区上,然后清除Eden区,如果此时存活的对象太多,以至于Survivor不够时,会将这些对象通过分配担保机制复制到老年代中。

  • 标记-整理

该算法主要是为了解决标记-清除,产生大量内存碎片的问题;当对象存活率较高时,也解决了复制算法的效率问题。它的不同之处就是在清除对象的时候现将可回收对象移动到一端,然后清除掉端边界以外的对象,这样就不会产生内存碎片了。

  • 分代收集

现在的虚拟机垃圾收集大多采用这种方式,它根据对象的生存周期,将堆分为新生代和老年代。在新生代中,由于对象生存期短,每次回收都会有大量对象死去,那么这时就采用复制算法。老年代里的对象存活率较高,没有额外的空间进行分配担保,所以可以使用标记-整理 或者 标记-清除。

六、垃圾收集器

  1. 新生代收集器:Serial、ParNew、Parallel Scavenge;

  2. 老年代收集器:Serial Old、Parallel Old、CMS;

  3. 整堆收集器:G1;

(A)、并行(Parallel)指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态;如ParNew、Parallel Scavenge、Parallel Old;

(B)、并发(Concurrent)指用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),用户程序在继续运行,而垃圾收集程序线程运行于另一个CPU上;    如CMS、G1(也有并行);

  1. Serial收集器

  Serial(串行)垃圾收集器是最基本、发展历史最悠久的收集器;

  针对新生代;采用复制算法;单线程收集;进行垃圾收集时,必须暂停所有工作线程,直到完成;

  1. ParNew收集器

  ParNew垃圾收集器是Serial收集器的多线程版本,除了多线程外,其余的行为、特点和Serial收集器一样。

  1. Parallel Scavenge收集器

  新生代收集器;采用复制算法;多线程收集;

  CMS等收集器的关注点是尽可能地缩短垃圾收集时用户线程的停顿时间;而Parallel Scavenge收集器的目标则是达一个可控制的吞吐量(Throughput);

  1. Serial Old收集器

Serial Old是 Serial收集器的老年代版本;针对老年代;采用"标记-整理"算法(还有压缩,Mark-Sweep-Compact);单线程收集;

  1. Parallel Old收集器

Parallel Old垃圾收集器是Parallel Scavenge收集器的老年代版本; JDK1.6中才开始提供;针对老年代; 采用"标记-整理"算法;多线程收集;

  1. CMS收集器

并发标记清理(Concurrent Mark Sweep,CMS)收集器也称为并发低停顿收集器(Concurrent Low Pause Collector)或低延迟(low-latency)垃圾收集器;

针对老年代;基于"标记-清除"算法(不进行压缩操作,产生内存碎片);以获取最短回收停顿时间为目标; 并发收集、低停顿;需要更多的内存;

  1. G1收集器

G1(Garbage-First)是JDK7-u4才推出商用的收集器;

 七、内存分配与回收策略

  对象优先在Eden区中分配,当Eden区没有足够空间进行分配虚拟机将执行一次Minor GC;

  大对象直接进入老年代,所谓大对象就是需要大量连续内存空间的Java对象,避免在Eden区及两个Survivor区之间发生大量的内存复制;

  长期存活的对象将进入老年代,虚拟机给每个对象定义一个对象年龄及数据,如果对象在Eden区出生并经过第一次Minor GC后仍然存活,并且能被Survivor容纳的话,将被移动到Survivor区中并且对象年龄加1,对象在Survivor区中每熬过一次Minor GC,年龄就增加一岁,当年龄增加到一定程度就会被晋升到老年代中;

  动态对象年龄判定,如果Survivor空间中相同年龄所有对象大小的总和大于Srvivor空间的一半,年龄大与或等于该年龄的对象就直接进入老年代;

  空间分配担保,在发生Minor GC之前,虚拟机会先检查老年代最大可用连续空间是否大于所有对象总空间,如果大于,那么Minor GC总是安全的。否则需要进行Full GC。但是可以进行冒险,新生代使用复制收集算法,但为了内存利用率,只是用一个Survivor空间作为轮换备份,因此当大量对象在Minor GC后仍然存活,就需要老年代进行担保,把Survivor区无法容纳的对象直接进入老年代。

 八、类加载过程

加载

加载时类加载的第一个过程,在这个阶段,将完成一下三件事情:

1. 通过一个类的全限定名获取该类的二进制流。

2. 将该二进制流中的静态存储结构转化为方法去运行时数据结构。

3. 在内存中生成该类的Class对象,作为该类的数据访问入口。

验证

验证的目的是为了确保Class文件的字节流中的信息不回危害到虚拟机.在该阶段主要完成以下四钟验证:

1. 文件格式验证:验证字节流是否符合Class文件的规范,如主次版本号是否在当前虚拟机范围内,常量池中的常量是否有不被支持的类型.

2. 元数据验证:对字节码描述的信息进行语义分析,如这个类是否有父类,是否集成了不被继承的类等。

3. 字节码验证:是整个验证过程中最复杂的一个阶段,通过验证数据流和控制流的分析,确定程序语义是否正确,主要针对方法体的验证。如:方法中的类型转换是否正确,跳转指令是否正确等。

4. 符号引用验证:这个动作在后面的解析过程中发生,主要是为了确保解析动作能正确执行。

准备

准备阶段是为类的静态变量分配内存并将其初始化为默认值,这些内存都将在方法区中进行分配。准备阶段不分配类中的实例变量的内存,实例变量将会在对象实例化时随着对象一起分配在Java堆中。

解析

该阶段主要完成符号引用到直接引用的转换动作。解析动作并不一定在初始化动作完成之前,也有可能在初始化之后。

初始化

初始化时类加载的最后一步,前面的类加载过程,除了在加载阶段用户应用程序可以通过自定义类加载器参与之外,其余动作完全由虚拟机主导和控制。到了初始化阶段,才真正开始执行类中定义的Java程序代码。

 九、类加载器

通过一个类的全限定名来获取描述此类的二进制字节流这个动态实现的虚拟机外部代码模块叫做“类加载器”。

  1. 启动类加载器:将\lib目录下的类库加载到虚拟机内存中;java程序无法直接使用,即rt.jar。

  2. 扩展类加载器:加载\lib\ext目录中,或者被java.ext.dir系统变量指定的所有类库,java程序可以直接使用,即\ext文件下的jar包;

  3. 应用程序类加载器:负责加载用户类路径上所指定的类库,java程序可以直接使用;

  4. 用户自定义类加载器,通过继承 java.lang.ClassLoader类的方式实现。

十、双亲委派模型

  当一个类收到了类加载请求时,不会自己先去加载这个类,而是将其委派给父类,由父类去加载,如果此时父类不能加载,反馈给子类,由子类去完成类的加载。

  使用双亲委派模型使得Java类具有明显的层级关系,确保各种类加载器之间的加载顺序,保证系统运行安全。举例来说,假如没有双亲委派模型,用户编写Object类自行加载,会使得出现多个Object类,导致程序出现混乱。

十一、Java内存模型

java内存模型(JMM)是线程间通信的控制机制.JMM定义了主内存和线程之间抽象关系。线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。

十二、栈帧的结构

1)局部变量表:局部变量表里存储的是基本数据类型、returnAddress类型(指向一条字节码指令的地址)和对象引用,这个对象引用有可能是指向对象起始地址的一个指针,也有可能是代表对象的句柄或者与对象相关联的位置。局部变量所需的内存空间在编译器间确定

2)操作栈:操作数栈的作用主要用来存储运算结果以及运算的操作数,它不同于局部变量表通过索引来访问,而是压栈和出栈的方式

3)动态连接:每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接.动态链接就是将常量池中的符号引用在运行期转化为直接引用。

4)返回地址:方法执行后只有两种退出方法:遇到任意一个返回字节码指令或者在运行中遇到异常,无论采用什么退出方式,都需要返回到方法被调用的位置,返回的位置保存在返回地址中。

十三、其他问题

  • 使用GC Root枚举根节点,由于整个方法区中枚举耗时过高如何解决?

在HotSpot中,通过OopMap这个数据结构来达到这个目的,在类加载完成之后,会在JIT编译时,将栈和寄存器存在引用的地方标记起来,GC直接对这些标记点进行扫描。

  • 什么是安全点?

OopMap虽然可以进行快速的GCRoot枚举,但是由于虚拟机指令太多,如果每个指令生成OopMap会浪费大量的空间,所以虚拟机会在特定的位置生成OopMap,这些位置称为安全点。

  • 安全点如何选择?

一般选择方法调用,异常处理,循环跳转的位置作为安全点。

  • 方法去中哪些类是无用的类?
  1. java堆中不存在该类的任何实例;
  2. 加载该类的classLoader都被回收了;
  3. 该类的class对象没有任何地方被引用到。
  • 对象常见内存分配方式?
  1. 指针碰撞:对象需要多大的内存在类加载完成之后就已经确定,相当于将已分配内存放在一边,将未分配内存放在另一边,中间用指针进行隔离,内存分配的时候移动指针;
  2. 空闲列表:维护一个表,纪录内存中未分配的内存。
  • 空间分配担保?

当新生代发生GC的时候,先判断老年代中剩余空间的大小是否大于新生代中所有对象的总大小,如果大于,则进行minor GC,如果不是则需要进行老年代空间分配担保,如果不允许进行老年代空间分配担保,则进行full GC,如果允许老年代空间分配担保,则判断老年代中剩余空间大小是否大于每次minor GC进入到老年代中对象大小的平均值。

十四、虚拟机指令

-XMS:JVM堆初始大小;

-XMX:JVM堆最大大小;

-XSS:线程栈的初始大小;

-XX:NewSize:新生代的大小;

-XX:MaxNewSize:新生代的最大大小;

-XX:NewRatio:新生代和老年代大小的比例;

-XX:SurvivorRatio:Eden区和一个Survivor区的大小比例;

-XX:InitialTernuringThreadhold:老年代阈值;

-XX:MaxTernuringThreadhold:老年代最大阈值;

转载于:https://www.cnblogs.com/winterfells/p/9431151.html

面试题——Java虚拟机相关推荐

  1. Java虚拟机常见面试题

    2019独角兽企业重金招聘Python工程师标准>>> 1.java引用的四种状态 强引用.软引用.弱引用.虚引用. 强引用 new一个Object存放在堆内存,然后用一个引用指向它 ...

  2. java上机面试题 039_深入 Java 虚拟机之面试总结篇

    在学习 JVM 相关知识,怎么让自己有动力看下去,且有思考性呢?笔者认为,开头用一些常用的面试题,来引入读者的兴趣比较好,这样才会有看下去的动力.所以,该篇文章会以面试+总结的方式,希望读者能先思考写 ...

  3. Java虚拟机(JVM)面试题(2022年总结最全面的面试题!!!)

    大家好啊,我是小于哥,Java虚拟无论是在年前面试还是在年后的金三银四面试都是必问的,比如: Java1.8和1.7内存模型有什么差距? 堆栈的区别是什么? 深拷贝和浅拷贝 Java会存在内存泄漏吗? ...

  4. 【2022最新Java面试宝典】—— Java虚拟机(JVM)面试题(51道含答案)

    目录 一.Java内存模型 1. 我们开发人员编写的Java代码是怎么让电脑认识的 2. 为什么说java是跨平台语言 3. Jdk和Jre和JVM的区别 4. 说一下 JVM由那些部分组成,运行流程 ...

  5. Java 虚拟机(JVM)面试题(2021最新版)

    点击下方公众号「关注」和「星标」 回复"1024"获取独家整理的学习资料! Java内存区域 说一下 JVM 的主要组成部分及其作用? JVM包含两个子系统和两个组件,两个子系统为 ...

  6. Java虚拟机(JVM)面试题大集合

    Java虚拟机(JVM) Java内存区域 说一下 JVM 的主要组成部分及其作用? 说一下 JVM 运行时数据区 深拷贝和浅拷贝 说一下堆栈的区别? 队列和栈是什么?有什么区别? HotSpot虚拟 ...

  7. Java虚拟机(JVM)面试题(总结最全面的面试题!!!)

    Java内存模型 我们开发人员编写的Java代码是怎么让电脑认识的 首先先了解电脑是二进制的系统,他只认识 01010101 比如我们经常要编写 HelloWord.java 电脑是怎么认识运行的 H ...

  8. Java虚拟机(JVM)超详细面试题

    文章目录 一.Java内存区域 1.1 说一下 JVM 的主要组成部分及其作用? 1.2 说一下 JVM 运行时数据区 1.3 详细的介绍下程序计数器? 1.4 详细介绍下Java虚拟机栈? 1.5 ...

  9. JVM——Java虚拟机架构

    Java虚拟机(Java virtualmachine)实现了Java语言最重要的特征:即平台无关性. 平台无关性原理:编译后的 Java程序(.class文件)由 JVM执行.JVM屏蔽了与具体平台 ...

最新文章

  1. 函数空间中的最佳逼近
  2. 写给那些在技术路上奔跑的人们!!!!!
  3. python爬取网页书籍名称代码_python爬取亚马逊书籍信息代码分享
  4. 提高代码性能及并发性的方法浅谈
  5. bert中的sep_基于向量的深层语义相似文本召回?你需要BERT和Faiss
  6. SpringCloud 应用在 Kubernetes 上的最佳实践 — 线上发布(可灰度)
  7. Vmware 中安装Unix
  8. Spring之后处理器
  9. RHEL5 配置yum
  10. python批量下载静态页面_Python selenium如何打包静态网页并下载
  11. .NET破解之谷歌地图下载助手-睿智版
  12. 一款很好用的在线作图工具ProcessOn,推荐大家使用
  13. vbox虚拟机与主机互传文件的四种方法
  14. 操作系统知识点总结(十四)文件保护:文件访问类型和访问控制
  15. 客户端服务器通信demo(续) -- 使用二进制协议 (附源码)
  16. STM32F103C8T6定时器产生PWM
  17. 如何查看office是否永久激活版本
  18. 在线协助设计软件,figma、sketch、xd哪个才是你的优先选择
  19. 数据集-20个免费的数据源/网站
  20. 前端(1)js:百度地图api使用

热门文章

  1. idbconnection mysql_继承IDbConnection连接不同数据库
  2. python如何爬取豆瓣_Python实战之如何爬取豆瓣电影?本文教你
  3. 关于一些电脑使用的小技巧
  4. asp.net捕获全局未处理异常的几种方法
  5. (2)基于Bootstrap的网页开发
  6. 一级域名301重定向到www二级域名
  7. [转]Spinner的常用技巧
  8. 利用U盘安装windows 7
  9. JavaScript选择器
  10. 用计算机听音乐和看电影教学反思,与计算机交朋友