什么?你也被问到JVM了

浅谈一下JVM里常见的几个问题吧


面对面试题常见几个问题
1.简单描述一下你了解的JVM
2.类的加载
3.谈谈GC垃圾回收吧
当然还有很多可以说的话题,但这里仅对比较基础常见的问题进行探讨。(主要还是太难的我不会我也不敢乱写啊)

JVM也叫java虚拟机,这对于每个学java的人都不陌生,由于我不会画画也不想找图,所以尽量都用语言来描述。大家可能都会知道的是jdk,jre与java虚拟机的关系,这不需要做过多陈述。我们一般谈的虚拟机默认都是hotspot虚拟机,这个可以输入java -version里看到。
在这里插入图片描述
hotspot是由某小公司Longview Technologies开发的,后来被sun公司收购并逐渐成为默认虚拟机。(据说还因为选择什么虚拟机争吵过)当然虚拟机肯定不止这一种,比如大名鼎鼎的IBM公司的J9等。
关于jdk其实也有不少故事可以讲,jdk的正式版本以动物命名(比如jdk6叫tiger),jdk几个版本之间的差异和历史,但这些不是今天的主要话题,有兴趣的可以自己查阅资料等。
JVM的结构一般都用图示的方式讲解,这里就不单独放图了,直接说几个主要的概念方便大家了解JVM吧。
一般谈JVM尤其是内存模型,避不开这几个话题:
1.程序计数器
2.虚拟机栈
3.本地方法栈
4.堆
5.方法区

1.首先说说程序计数器,这是一块绝对不存在OOM问题(OutOfMemory)的区域,是为了保证代码运行而存在。简单理解就是记录编译后字节码行数防止出错。
2.再谈谈虚拟机栈,栈的概念都不陌生,特点就是后进先出嘛,虚拟机栈主要是为了存放栈帧,栈帧包含了局部变量表,操作数栈,动态链接,方法出口等信息,其实就是在描述方法。后进先出也就不难理解了,一个方法先执行肯定要放到底下,每一个被方法体内调用的方法就在它上面后进先出,栈顶一定是正在执行的方法,最后都执行完全部弹出栈。但这里会有一个栈内存溢出的可能存在,比如两个方法互相调用,栈里就会反复进入这两个方法,那么很快啊,就装满了。递归同样也可能会引起这个问题。
3.本地方法栈与栈最大的区别就是需要它来使用一些不属于java的本地方法,一般使用native关键字修饰
在这里插入图片描述
这时本地方法栈会去找本地方法接口(JNI java native interface),这便不再受JVM的约束。
4.堆是一块用来放实例和数组的区域,其实应该带着方法区一起探讨,方法区又可以叫非堆,这个名字显然是怕大家把他们混为一谈,所以不难看出其实他俩关系很密切才怕被认错。方法区存放的是一些静态变量(static),常量(final),类信息等,所以不难看出,这一部分应该是长期存在的。那么这里就出现了我们应该探讨的第三个问题:GC垃圾回收。

既然叫垃圾回收,肯定是回收一些不需要的,堆常常被划分为:新生区,老年区,永久代(8以后改为元空间),对于永久代其实就是应该长久存在的,所以为了区分,被称为了非堆。新生区又可以划分为Eden区(伊甸园区),幸存from,幸存to。(幸存区与Eden占比是1:8,因为百分之八十的情况对象都是用完就死)一般实例先会进入到Eden,经过GC回收后依旧存活的就送给幸存区,幸存区的to与from是一个动态的概念,简单来说就是空的是to,幸存的实例一直从from走向to,Eden也一直把幸存的实例再交给这两块区域,直到他们都满了之后依旧存活下来的就会去到老年区,老年区内会有重GC,不同于之前的轻GC,重GC也叫Full GC,是对全局的垃圾回收,轻GC只停留在新生区,如果最后老年区也满了,就会发生OOM问题了。这里的GC回收是采用了复制算法,指的是在新生区内,分出两块一样的区域(幸存区)来进行接收,这样的好处是不会产生内存碎片,但为此分出的内存会使得空间上消耗更大。
为什么说更大,因为还存在一些别的方式来进行回收,有一些简单粗暴的标记清除法,其实就是扫描一次标记一下,在扫描一次清除掉垃圾。这样的确没有内存上的额外开销,但会造成内存碎片问题,并且两次扫描其实也是对效率的影响。还有一种则是标记压缩,类似于上一种,但多了一次扫描解决内存碎片,当然这也更影响效率。
所以其实没有十全十美的办法,一般也是根据情况的分代回收算法。
还要说说两种判断垃圾的算法,引用计数法和可达性算法。
引用计数顾名思义是给每个实例计数,如果引用次数为0则就视为垃圾。(python在用)
可达性分析(可达鸭:嘎?)指的是可以达到的分析,对于一个引用无法达到的实例,自然会认为是一个垃圾。(这一块我感觉我还得多品品再写详细一点嘎)

最后在小小的谈一下堆这部分,因为这里其实是最容易涉及内存问题的,所以调优也都是针对此处进行的,设置最大内存和初始化内存,获取dump文件辅助调优等。

最后说说类加载吧。
类加载的过程分为了加载(类加载器创建class对象),验证(确保class文件内容符合虚拟机要求),准备(给静态变量分配方法区内存并赋默认值),解析(把常量池中符号引用改为直接引用),初始化(给静态变量赋值并执行静态代码块)
类加载器包括四种:启动类加载器(名字很多比如bootstrap classload,引导类加载器等等),扩展类加载器,系统类加载器,最后还有自定义的类加载器。
对于加载机制,主要就是双亲委派机制,顾名思义,首先要交给父类加载器,这样一直交给启动类加载器后,如果启动类加载器没办法执行,则交给下一级来执行,这样层层委派。这里见到过一个例子,如果你手写了一个lang包下叫做String的类,你写了什么方法来执行,由于双亲委派,都会先被引导类加载器看到并执行我们熟知的String,然后String类本身没有你写的方法,所以你的调用就会失败。

这里内容不够详细的部分,日后再做补充吧

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值