Java虚拟机的部分面试题,自己整理的,当然实际工作中并没有太大接触,但是了解jvm的一些简单基础知识还是对自己有用的.
简述什么叫JVM
JVM是Java Virtual Machine缩写。它是一种计算设备的规范,是一个虚拟的计算机,通过在实际的计算机上模拟各种计算机功能实现。有一套字节码指令集,一组寄存器,一个栈,一个垃圾回收堆和一个存储方法域。JVM屏蔽了与具体操作系统平台相关的信息,使java程序只需要生成字节码就可以在多种平台上不加修改地运行。JVM在执行字节码时,还是要把字节码解释成具体平台上的机器指令行。
简述运行时数据区(其实也就是JVM的内存结构)包括哪几部分
根据《Java虚拟机规范》的规定,运行时数据区通常包括这几个部分:程序计数器(Program Counter Register)、Java栈(VM Stack)、本地方法栈(Native Method Stack)、方法区(Method Area)、堆(Heap)。然后大概回答一下每个部分的作用.主要讲一下堆,栈和方法区吧,
java堆内存是是用来存储对象本身的以及数组,记住所有通过new关键字new出来的对象都在堆中,这部分空间也是Java垃圾收集器管理的主要区域。另外,堆是被所有线程共享的,所以线程不安全,在JVM中只有一个堆。
------------------我是分隔线--------------------------
JVM栈是线程私有的(线程安全),每个线程创建的同时都会创建JVM栈,JVM栈中存放的为当前线程中局部基本类型的变量(java中定义的八种基本类型:boolean、char、byte、short、int、long、float、double)、部分的返回结果以及Stack Frame,非基本类型的对象在JVM栈上仅存放一个指向堆上的地址。
------------------我是分隔线--------------------------
方法区域存放了所加载的类的信息(名称、修饰符等)、类中的静态变量、类中定义为final类型的常量、类中的Field信息、类中的方法信息,当开发人员在程序中通过Class对象中的getName、isInterface等方法来获取信息时,这些数据都来源于方法区域,同时方法区域也是全局共享的,在一定的条件下它也会被GC,当方法区域需要使用的内存超过其允许的大小时,会抛出OutOfMemory的错误信息。
各个地方都可以设置启动内存大小,详
线程私有的当然就是线程安全的:
程序计数器
虚拟机栈
本地方法栈线程共享的:
堆
方法区
直接内存
java对象是怎么创建的
分配内存会出现什么问题
分配内存都是在堆中进行操作的,堆是线程共享的,所以会产生线程安全问题,jvm虚拟机采用两种方式来保证线程安全问题的:
1.CAS+失败重试,CAS 是乐观锁的一种实现方式。乐观锁顾名思义就是比较乐观,想着没有其他线程来修改,则buji不进行加锁,如果有其他线程则会冲突,失败就重试,直到成功为止。虚拟机采用 CAS 配上失败重试的方式保证更新操作的原子性。
2.TLAB: 为每一个线程预先在 Eden 区分配一块内存。JVM 在给线程中的对象分配内存时,首先在 TLAB 分配,当对象大于TLAB 中的剩余内存或 TLAB 的内存已用尽时,再采用CAS 进行内存分配。
什么情况下会栈内存溢出,堆内存溢出?
循环调用和死循环;
创建一个很大对象,如List和Array.较大的全局变量;
JVM体系结构,以及说出一些类加载器?
JVM体系结构由类加载器,执行引擎和运行时数据区组成
类加载器主要分为:
引导类加载器Bootstrap ClassLoader,扩展类加载器Extension ClassLoader,
系统类加载器APP ClassLoader 和自定义加载器 Custom ClassLoader。
这里会问了解双亲委派模型吗?网上很多,了解一下就好了,https://www.imooc.com/article/34493jvm怎么确定哪些对象应该进行回收
对象是否会被回收的两个经典算法:引用计数法,和可达性分析算法。
引用计数法就是给对象中添加一个引用计数器,每当有一个地方引用它,计数器就加1;当引用失效,计数器就减1;任何时候计数器为0的对象就是不可能再被使用的。这个方法实现简单,效率高,但是目前主流的虚拟机中并没有选择这个算法来管理内存,其最主要的原因是它很难解决对象之间相互循环引用的问题。 所谓对象之间的相互引用问题,就是他们因为互相引用对方,导致它们的引用计数器都不为0,于是引用计数算法无法通知 GC 回收器回收他们。
可达性分析算法算法的基本思想就是通过一系列的称为 “GC Roots” 的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的。
Java中引用的定义很传统:
JDK1.2之前,如果reference类型的数据存储的数值代表的是另一块内存的起始地址,就称这块内存代表一个引用。
JDK1.2以后,Java对引用的概念进行了扩充,将引用分为强引用、软引用、弱引用、虚引用四种(引用强度逐渐减弱)
谈谈垃圾收集算法
标记-清除算法
顾名思义,算法分为“标记”和“清除”阶段,主要会产生大量不连续的碎片;
复制算法
顾名思义,算法将内存分为大小相同的两块,每次只使用其中的一块,当这一块内存使用完之后,将还存活的对象(就是不需要回收的对象)复制到另外一块去,然后在把使用的空间一次清理掉.这样回收就只对某一块区间进行回收.
标记-整理算法
同标记清除差不多,但不是直接回收对象,而是让所有存活的对象向一段进行移动,然后直接清理掉另外一端边界以外的内存.
分代收集算法
这种算法就是根据对象的存活周期将内存分为几块,然后根据不同的几块选择不同的垃圾收集算法,现在的大部分虚拟机都是使用该算法,将堆内存分为新生代,老年代,新生代又被划分为三个区域:Eden、From Survivor、To Survivor。这样划分使 JVM 能够更好的管理堆内存中的对象,包括内存的分配以及回收。
Minor GC(复制算法) 对象在新生代产生,新生代是 GC 收集垃圾的频繁区域,Eden区经过多次gc会慢慢被复制到From Survivor,To Survivor区,可以简单的理解为,在Eden区的对象,如果经过3次GC还未被回收,就会被复制到From Survivor,如果From Survivor区域满了,就会将不需要回收的对象复制到To Survivor,然后再清空FromSurvivor好比要是多次还未被回收,改对象就会被复制到老年代,这里的参数几次几次都可以通过配置进行配置的,当然并不是所有的对象都是在新生代生成的,有时候较大的对象 ( 即需要分配一块较大的连续内存空间 ) 则是直接进入到老年代.(大对象就是需要大量连续内存空间的对象(比如:字符串、数组)为了避免为大对象分配内存时由于分配担保机制带来的复制而降低效率。)
Full GC 是发生在老年代的辣鸡搜集算法,所采用的是标记-清除(也可以是标记整理)算法,老年代的对象,一般是经过多次 Minor GC存活下来的对象,一般很难会被回收,举个例子,好比发生13次 Minor GC之后才会发生一次Full GC,而且一次 Full GC 要比进行一次 Minor GC 的时间更长.如果,老年代 需要为一个很大的对象开辟一块区域的时候,也会触发一次FullGc
堆内存大小都可以进行配置
以上只是简单概括主流的java虚拟机的回收算法,JVM中已经实现的部分GC收集器.针对不同的区域也可以使用不同的回收算法.
1.8中移除整个永久代,取而代之的是一个叫元空间(Metaspace)的区域(永久代使用的是JVM的堆内存空间,而元空间使用的是物理内存,直接受到本机的物理内存限制)。为什么?
1、字符串等存在永久代中,可能会出现性能问题和内存溢出。
2、类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。
3、永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。
常见的垃圾收集器
Serial收集器(复制算法)
新生代单线程收集器,标记和清理都是单线程,优点是简单高效。是client级别默认的GC方式,可以通过-XX:+UseSerialGC来强制指定。
Serial Old收集器(标记-整理算法)
老年代单线程收集器,Serial收集器的老年代版本。
ParNew收集器(停止-复制算法)
新生代收集器,可以认为是Serial收集器的多线程版本,在多核CPU环境下有着比Serial更好的表现。
Parallel Scavenge收集器(停止-复制算法)
并行收集器,追求高吞吐量,高效利用CPU。吞吐量一般为99%, 吞吐量= 用户线程时间/(用户线程时间+GC线程时间)。适合后台应用等对交互相应要求不高的场景。是server级别默认采用的GC方式,可用-XX:+UseParallelGC来强制指定,用-XX:ParallelGCThreads=4来指定线程数。
Parallel Old收集器(停止-复制算法)
Parallel Scavenge收集器的老年代版本,并行收集器,吞吐量优先。
CMS(Concurrent Mark Sweep)收集器(标记-清理算法)
高并发、低停顿,追求最短GC回收停顿时间,cpu占用比较高,响应时间快,停顿时间短,多核cpu 追求高响应时间的选择。
jvm会在什么时候进行垃圾回收的动作
1会在cpu空闲的时候自动进行回收
2在堆内存存储满了之后
3主动调用System.gc()后尝试进行回收,(注意是尝试,并不一定会执行)