2019年java中高级java面试题(九)java内存模型和jvm

1、运行时数据区包含那几个部分?

Java运行时数据区分为下面几个内存区域:

 

程序计数器 

程序计数器是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。

java虚拟机栈

每当创建一个线程,JVM就会为该线程创建对应的Java栈,在这个Java栈中又会包含多个栈帧(Stack Frame),这些栈帧是与每个方法关联起来的,每运行一个方法就创建一个栈帧,每个栈帧会含有一些局部变量、操作栈和方法返回值等信息。下图是栈帧结构 

本地方法栈 

本地方法栈和虚拟机栈作用相似,本地方法执行的是native方法

java堆

堆是JVM所管理的内存中国最大的一块,是被所有Java线程锁共享的,不是线程安全的,在JVM启动时创建。堆是存储Java对象的地方,这一点Java虚拟机规范中描述是:所有的对象实例以及数组都要在堆上分配。Java堆是GC管理的主要区域,从内存回收的角度来看,由于现在GC基本都采用分代收集算法,所以Java堆还可以细分为:新生代和老年代;新生代再细致一点有Eden空间、From Survivor空间、To Survivor空间等。 

方法区

方法区是各个线程共享的内存区域,它用于存储类加载信息、类中的静态常量、类中定义为final类型的常量、类中的Field信息、类中的方法信息,当在程序中通过Class对象的getName.isInterface等方法来获取信息时,这些数据都来源于方法区。方法区是被Java线程锁共享的,不像Java堆中其他部分一样会频繁被GC回收,它存储的信息相对比较稳定,在一定条件下会被GC。 
 

运行时常量池

运行时常量池 ---- 存在于内存的元空间中

字符串常量池 ---- 存在于堆中

2、如何判断对象是否存活?

引用计数法 

给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器

值就减1;任何时刻计数器为0的对象就是不可能再被使用的。

可达性分析算法(根搜索算法)

过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为

引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的

在Java语言中,可作为GC Roots的对象包括下面几种:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象。
  • 方法区中类静态属性引用的对象。
  • 方法区中常量引用的对象。
  • 本地方法栈中JNI(即一般说的Native方法)引用的对象。

3、常用的垃圾回收算法

标记-清除算法 

标记-清除算法采用从根集合(GC Roots)进行扫描,对存活的对象进行标记,标记完毕后,再扫描整个空间中未被标记的对象,进行回收,此算法一般没有虚拟机采用。

复制算法 
将内存分成两块容量大小相等的区域,每次只使用其中一块,当这一块内存用完了,就将所有存活对象复制到另一块内存空间,然后清除前一块内存空间。这样一来就不容易出现内存碎片的问题。 

标记-整理算法 
思想:在完成标记之后,它不是直接清理可回收对象,而是将存活对象都向一端移动,然后清理掉端边界以外的内存。 
不会产生内存碎片,但是依旧移动对象的成本

分代收集算法

分代收集算法是目前大部分JVM的垃圾收集器采用的算法。它的核心思想是根据对象存活的生命周期将内存划分为若干个不同的区域。一般情况下将堆区划分为老年代(Tenured Generation)和新生代(Young Generation),在堆区之外还有一个代就是永久代(Permanet Generation)。老年代的特点是每次垃圾收集时只有少量对象需要被回收,而新生代的特点是每次垃圾回收时都有大量的对象需要被回收,那么就可以根据不同代的特点采取最适合的收集算法
 

 4、常见的垃圾回收器

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 追求高响应时间的选择。

 

垃圾回收器从线程运行情况分类有三种

串行回收,Serial回收器,单线程回收,全程stw;
并行回收,名称以Parallel开头的回收器,多线程回收,全程stw;
并发回收,cms与G1,多线程分阶段回收,只有某阶段会stw;
CMS收集器基于“标记-清除”算法实现,

优点:并发收集、低停顿

缺点:无法处理浮动垃圾导致Full GC产生容易出现大量空间碎片

G1从整体来看是基于“标记整理”算法实现的收集器;从局部上来看是基于“复制”算法实现的。

1、并行于并发:G1能充分利用CPU、多核环境下的硬件优势,使用多个CPU(CPU或者CPU核心)来缩短stop-The-World停顿时间。

2、分代收集

3、建立可预测的停顿时间模型
 

5、 GC什么时候被触发的

由于对象进行了分代处理,因此垃圾回收区域、时间也不一样。GC有两种类型:Minor GC和Full GC

一般情况下,当新对象生成,并且在Eden申请空间失败时,就会触发Minor GC,对Eden区域进行GC,清除非存活对象,并且把尚且存活的对象移动到Survivor区。然后整理Survivor的两个区。

Full GC 
对整个堆进行整理,包括Young、Tenured和Perm。Full GC因为需要对整个堆进行回收,所以比Scavenge GC要慢,因此应该尽可能减少Full GC的次数。在对JVM调优的过程中,很大一部分工作就是对于Full GC的调节。有如下原因可能导致Full GC: 

  • 年老代(Tenured)被写满; 
  •  持久代(Perm)被写满; 
  •  System.gc()被显示调用; 
  • 上一次GC之后Heap的各域分配策略动态变化

6、 jvm查看gc命令  

jstat -gc 12538 5000 


即会每5秒一次显示进程号为12538的java进成的GC情况

7、 类加载为什么要使用双亲委派模式,有没有什么场景是打破了这个模式?


在Java中判断两个类是否是同一个类,不仅仅是类的全限定名称相同,还需要加载它们的类加载器相同。使用双亲委派模式,加载Object类时会始终使用启动类加载器进行加载,而不会使用自定义类加载器,如果不使用双亲委派模式的话程序会混乱不堪。

JNDI服务打破了双亲委派模式。按照双亲委派模式,启动类加载器会加载JNDI,此时启动类加载器找到无法对各厂商具体实现,引入了ThreadContextClassLoader,父加载器会请求子加载器对其进行加载。


8、  类的实例化顺序?


大致的顺序是,先静态方法、再构造方法,先父类后子类。

(1)父类静态成员和静态初始化块,按代码顺序;

(2)子类静态成员和静态初始化块,按代码顺序;

(3)父类实例成员和实例初始化块,按代码顺序;

(4)父类构造方法;

(5)子类实例成员和实例初始化块,按代码顺序;

(6)子类构造方法。
 

 

9、 java内存模型

java内存模型屏蔽了各种硬件和操作系统的内存访问差异,让Java达到一致的内存访问效果。

java内存模型规定了所有变量都存储在主内存中,每条线程都有自己的工作内存,线程对变量的操作都在工作内存中进行。

为了主内存和工作内存的交互,java内存模型定义了8中原子操作

  • lock(锁定):作用于主内存的变量,把一个变量标识为一条线程独占状态。
  • unlock(解锁):作用于主内存变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
  • read(读取):作用于主内存变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用
  • load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。
  • use(使用):作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作。
  • assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
  • store(存储):作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作。
  • write(写入):作用于主内存的变量,它把store操作从工作内存中一个变量的值传送到主内存的变量中。

 Java内存模型是围绕着并发编程中原子性、可见性、有序性这三个特征来建立的,那我们依次看一下这三个特征:

  原子性(Atomicity):一个操作不能被打断,要么全部执行完毕,要么不执行。在这点上有点类似于事务操作,要么全部执行成功,要么回退到执行该操作之前的状态。

  基本类型数据的访问大都是原子操作,long 和double类型的变量是64位,但是在32位JVM中,32位的JVM会将64位数据的读写操作分为2次32位的读写操作来进行,这就导致了long、double类型的变量在32位虚拟机中是非原子操作,数据有可能会被破坏,也就意味着多个线程在并发访问的时候是线程非安全的。

可见性:一个线程对共享变量做了修改之后,其他的线程立即能够看到(感知到)该变量这种修改(变化)。

  Java内存模型是通过将在工作内存中的变量修改后的值同步到主内存,在读取变量前从主内存刷新最新值到工作内存中,这种依赖主内存的方式来实现可见性的。

无论是普通变量还是volatile变量都是如此,区别在于:volatile的特殊规则保证了volatile变量值修改后的新值立刻同步到主内存,每次使用volatile变量前立即从主内存中刷新,因此volatile保证了多线程之间的操作变量的可见性,而普通变量则不能保证这一点。

  除了volatile关键字能实现可见性之外,还有synchronized,Lock,final也是可以的。

  使用synchronized关键字,在同步方法/同步块开始时(Monitor Enter),使用共享变量时会从主内存中刷新变量值到工作内存中(即从主内存中读取最新值到线程私有的工作内存中),在同步方法/同步块结束时(Monitor Exit),会将工作内存中的变量值同步到主内存中去(即将线程私有的工作内存中的值写入到主内存进行同步)。

  使用Lock接口的最常用的实现ReentrantLock(重入锁)来实现可见性:当我们在方法的开始位置执行lock.lock()方法,这和synchronized开始位置(Monitor Enter)有相同的语义,即使用共享变量时会从主内存中刷新变量值到工作内存中(即从主内存中读取最新值到线程私有的工作内存中),在方法的最后finally块里执行lock.unlock()方法,和synchronized结束位置(Monitor Exit)有相同的语义,即会将工作内存中的变量值同步到主内存中去(即将线程私有的工作内存中的值写入到主内存进行同步)。

  final关键字的可见性是指:被final修饰的变量,在构造函数数一旦初始化完成,并且在构造函数中并没有把“this”的引用传递出去(“this”引用逃逸是很危险的,其他的线程很可能通过该引用访问到只“初始化一半”的对象),那么其他线程就可以看到final变量的值。

  有序性:对于一个线程的代码而言,我们总是以为代码的执行是从前往后的,依次执行的。这么说不能说完全不对,在单线程程序里,确实会这样执行;但是在多线程并发时,程序的执行就有可能出现乱序。用一句话可以总结为:在本线程内观察,操作都是有序的;如果在一个线程中观察另外一个线程,所有的操作都是无序的。前半句是指“线程内表现为串行语义(WithIn Thread As-if-Serial Semantics)”,后半句是指“指令重排”现象和“工作内存和主内存同步延迟”现象。

Java提供了两个关键字volatile和synchronized来保证多线程之间操作的有序性,volatile关键字本身通过加入内存屏障来禁止指令的重排序,而synchronized关键字通过一个变量在同一时间只允许有一个线程对其进行加锁的规则来实现,

在单线程程序中,不会发生“指令重排”和“工作内存和主内存同步延迟”现象,只在多线程程序中出现。

 
10、 内存泄漏与内存溢出区别,产生原因?


内存溢出 out of memory,是指程序在申请内存时,没有足够的内存空间供其使用

内存泄露 memory leak,是指程序在申请内存后,无法释放已申请的内存空间

内存溢出的原因:

1.内存中加载的数据量过于庞大,如一次从数据库取出过多数据;
2.集合类中有对对象的引用,使用完后未清空,使得JVM不能回收;
3.代码中存在死循环或循环产生过多重复的对象实体

4.启动参数内存值设定的过小
 

 

11、如何解决内存溢出?

(1)用jmap生成堆信息

jmap -dump:format=b,file=dumpFileName pid

 (2)将文件导入Visual VM中进行分析

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章