2.1 java代码是如何运行 

首先编写的java代码通过javac编译为.class文件,

然后通过java命令来运行.class文件(这个时候就启动了一个jvm进程,启动了JVM虚拟机);

在虚拟机里面运行.class字节码文件;

2.2 画出JVM的运行原理图?(内存图)

 2.3 请介绍以下JVM的内存结构划分

 2.4 JVM哪些区域是线程私有的,哪些区域是线程共享的?

1、堆、元空间(方法区)是线程共享的;存在线程安全问题

2、其他区域是线程私有的(线程之间是隔离的,每个线程用自己的);不存在线程安全问题;

2.5 程序计数器的特点和作用

1、程序计数器是一个较小的内存空间,几乎可以忽略

2、是当前线程所执行的字节码的行号指示器;(可以知道当前线程执行到哪一行);

3、java多线程执行时,每条线程都有一个独立的程序计数器,各条线程之间计数器互不影响;

4、该区域是”线程私有“的内存,每个线程独立存储;

5、该区域不会出现OOM

6、没有GC回收;随着线程的启动而启动,随着线程的销毁而销毁;

2.6 JVM运行时数据区 虚拟机栈的特点及作用?

一个方法一个栈帧,栈帧里面有局部变量表,操作数栈,动态链接,返回地址;

1、线程私有

2、方法执行会创建栈帧,存储局部变量表等信息;

3、方法执行入虚拟机栈,方法执行完出虚拟机栈(先进后出);

4、栈的深度大于虚拟机所允许的深度就会出现StackOverflowError;递归调用的时候比较常见;

5、栈需扩展而无法申请空间OutOfMemoryError(比较少见),hotspot虚拟机没有;

hotspot虚拟机的栈空间不是动态扩展的,是在创建的时候就申请空间,如果在创建的时候申请不到空间就会报出OOM,但是hotspot不会出现此问题;

6、栈里面运行方法,存放方法的局部变量名,变量名所指向的值(常量值、对象值等)都存放在堆上的;

7、栈一般不设置大小,栈所占用的空间很小;

8、生命周期:随着线程的创建而创建,线程的结束而销毁

9、该区域不存在GC回收;

2.7 JVM运行时数据区 本地方法栈的特点及作用?

1、与虚拟机栈基本类似;

2、区别在于本地方法栈为Native方法服务;

3、HotSpot虚拟机将虚拟机栈和本地方法栈合并;

4、有StackOverflowError和OutOfMemoryError(比较少见);

5、随线程而生,随线程而灭;

6、GC不会回收该区域;

2.8 JVM运行时数据区java堆的特点及作用?

1、线程共享的一块区域;

2、虚拟机启动时创建;

3、虚拟机所管理的内存中最大的一块区域;

4、存放所有实例对象或数组;

5、GC垃圾收集器主要管理的区域;

6、可分为新生代和老年代;1/3 ; 2/3

7、新生代分为:Eden、From 、To,  8:1:1

8、可以通过-Xms、-Xms调节堆大小;

9、当堆无法扩展的时候就会出现java.lang.OutOfMemoryError:Java heap space;

10、如果从分配内存的角度看,所有线程共享的java堆中可以划分出多个线程私的分配缓冲区(TLAB),以提升对象分配时的效率;

补充:TLAB:线程本地分配缓冲区,是Java中内存分配的一个概念

        他是java堆中分配出来的针对每一个线程的内存区域,专门在该区域为该线程创建的对象分配内存;主要目的:多线程并发环境下需要进行内存分配的时候,减少线程之间对于内存分配区域的竞争,加速内存分配的速度,Java中每个线程都会有自己的缓冲区称作TLAB,每个TLAB都只有一个线程可以操作,TLAB结合bump-the-pointer(指针碰撞)技术可以实现快速的对象分配,而不需要任何的锁进行同步,也就是说,在对象分配的时候不用锁住整个堆,而只需要在自己的缓冲区分配即可。

        如果没有启用 TLAB,多个并发执行的线程需要创建对象、申请分配内存的时候,有可能在 Java 堆的同一个位置申请,这时就需要对拟分配的内存区域进行加锁或者采用 CAS 等操作,保证这个区域只能分配给一个线程,加锁之后就降低了效率;

2.9 JVM中对象如何让在堆中分配

1、指针碰撞:内存规整的情况下;

2、空闲列表:内存不规整的情况下;

        选择哪种分配方式由Java堆是否规整决定的,而Java堆是否规整又由所采用的垃圾收集器是否带有空间压缩整理的能力决定;

3、本地线程分配缓冲TLAB: 

        对象创建在虚拟机中频繁发生,即使仅仅修改一个指针所指向位置,在并发的情况下也并不是线程安全的,可能出现正在给对象A分配内存,指针还没来得及修改,对象B又同时使用了原来的指针来分配内存的情况;

解决方案:

1、同步锁定:JVM是采用CAS配上失败重试的方式保证更新操作的原子性;

2、线程隔离,把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在java堆中预先分配一块内存,称为本地线程分配缓存TLAB,哪个线程要分配内存,就在哪个线程的本地缓冲区中分配,只有本地缓冲区用完了,分配新的缓冲区的时才需要同步锁定;

 CAS

注:t1,t2线程是同时更新同一变量56的值

        因为t1和t2线程都同时去访问同一变量56,所以他们会把主内存的值完全拷贝一份到自己的工作内存空间,所以t1和t2线程的预期值都为56。

        假设t1在与t2线程竞争中线程t1能去更新变量的值,而其他线程都失败。(失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次发起尝试)。t1线程去更新变量值改为57,然后写到内存中。此时对于t2来说,内存值变为了57,与预期值56不一致,就操作失败了(想改的值不再是原来的值)。

        (上图通俗的解释是:CPU去更新一个值,但如果想改的值不再是原来的值,操作就失败,因为很明显,有其它操作先改变了这个值。)当要在某个区域给某个线程分配缓冲区的时候,发现已经被其他线程分配,那么分配失败;

2.10 JVM堆内存中的对象布局?

在HotSpot虚拟机中,一个对象的存储结构分为3个区域:

对象头、实例数据、对齐填充;

对象头:

        第一部分用于存储对象自身的运行时数据。哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等;

        第二部分是类型指针,即对象指向它的类的元数据指针,虚拟机通过这个指针确定这个对象是哪个类的实例;如果是java数组,对象头中还必须有一块用于记录数组长度的数据;因为普通对象可以通过java对象元数据确定大小,而数组对象不可以;

实例数据

        程序代码中所定义的成员变量类型的字段内容(包括父类继承下来的和子类中定义的);

对齐填充

        不是必须需要,主要是占位,保证对象大小是某个字节的整数倍;HotSpot虚拟机,任何对象的大小都是8字节的整数倍;

2.11 JVM什么情况下会发生堆内存溢出

        java堆中用于存储对象,只要不断的创建对象,并且保持GC Roots到对象之间有可达路径来避免垃圾回收机制清理这些对象,那么随着对象的数量增加,当达到堆的容量限制就会出现内存溢出;

2.12 JVM如何判断对象可以被回收

垃圾收集器在堆进行回收钱,首先要确定这些对象之中哪些还存活,哪些已经死去;

java通过可达性分析算法来判断对象是否存活;

        该算法的基本思路:通过系一列称为:“GC Roots”的根对象作为起始节点,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为“引用链”,如果某个对象到GC Roots间没有任何引用链相连(称为不可达)则证明此对象是不可能再被使用的对象,就可以被垃圾收回器回收;

 2.13 哪些对象可以作为GC Roots?

1、虚拟机栈(栈帧中的局部变量表)中引用的对象

2、方法区/元空间中的类静态属性引用的对象;

3、方法去/元空间中的常量引用的对象;

4、所有被同步锁(synchronized关键字)持有的对象

等等;

2.14 谈谈Java中不同的引用类型

强引用:Object object = new Object();只要有引用指向,就算内存溢出也不会回收;

软引用:内存充足的时候不回收,内存不足则回收;一般用在缓存当中

                SoftReference  softReference  = new SoftReference(object);

弱引用: WoftReference 不管内存是否充足,只要GC一运行就会税后该引用对象;threadLocal里面使用了弱引用;

                WoftReference  woftReference  = new WoftReference(object);

虚引用:可以忽略,形同虚设,他的作用就是该引用对象被GC回收的时候出发一个系统通知,或者出发进一步的处理;

2.15 JVM堆内存分代模型

大部分对象朝生夕死,少数对象长期存活;

 2.16 请介绍以下JVM堆中新生代的垃圾回收过程?

        首先会在Eden区存放对象,当存放满了之后就开始进行Minor GC,将存活的对象放到From区,当下一次Eden区满的时候,进行 Minor GC,这个时候会同时对Eden和From区域进行回收,然后将存活的对象放在To区域,当一个对象在survive年龄达到15岁,就把这个对象放在老年代

JVM里垃圾回收针对的是:新生代,老年代,还有元空间/方法区(永久代)

        不会针对方法的栈帧进行回收,方法一旦执行完毕,栈帧出栈,里面的局部变量直接就从内存里清理掉,也就是虚拟机栈不存在垃圾回收;

 代码里创建出来的对象,一般就是两种:

1、一种是短期存活,分配在java对内存之后,迅速使用完就会被垃圾回收;

2、一种是长期存活的,需要一直生存在java对内存里,让陈旭后续不停的区使用;

第一种短期存活的对象,实在java对内存的新神代里分配;

第二种长期存活的对象,通过新神代S0,S1区来回被垃圾回收15次后,进入java堆内存的老年代中,这里的15次就是对象的年龄;

CMS垃圾收集器是15,并行收集器就默认15

2.17 JVM对象动态年龄判断是什么回事?(高频)

        上一道面试题说到S区的对象年龄达到15岁的时候就会晋升到老年代区域;

        但是虚拟机并不是永远的要求对象的年龄必须达到MaxTenuring Threshold=15才能晋升到老年代;

        结论:动态年龄判断:Surivor区的对象年龄从小到大进行累加,当累加到X年龄的时候综合大于50%(可以使用-XX:TargetSurvivorRatio=?来设置保留多少空闲空间,默认值是50),那么比X大的都会晋升到老年代; 

动态年龄判断过程:

 2.18 什么是老年代空间分配担保机制(什么是JVM的空间分配担保机制)?

将要把对象放到老年代的时候就会触发空间担保机制;

流程:

 在执行Minor GC的时候为什么要判断老板年代可用空间和新生代对象总大小?

 老年代空间担保机制的目的就是:为了避免频繁的进行Full GC;Full GC影响效率

如果Full GC之后,老年代还是没有足够的空间存放Minnor GC过后的剩余存活对象,就会到只OOM;

2.19 什么情况下对象会进入老年代?

1、躲过了15次GC之后进入老年代

2、动态对象年龄判断

3、老年代空间担保机制;当Minor GC之后,S区放不下之后就会进入老年代

4、大对象直接进入老年代;

大对象:

 如果大对象存活时间比较久,那么如果不直接存入老年代就会在S区来回复制,增大开销;

 可以通过设置:-XX:PretenureSizeThreshold来设置多大的对象直接进入老年代;

新生代使用PerNew;老年代使用CMS; 

2.20 JVM运行时数据区 元空间的特点及作用?

 2.21 JVM本机直接内存的特点及作用

 NIO的时候使用到了本地内存,所以就存在本地内存OOM的情况;

 2.22 说几个JVM内存相关的核心参数

 

 2.23 如何计算一个对象的大小

 使用lucene进行计算

 

 

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐