JVM面试
深入理解Java内存模型:http://www.cnblogs.com/skywang12345/p/3447546.html
http://www.infoq.com/cn/articles/java-memory-model-1
原理
类加载的五个过程:加载、验证、准备、解析、初始化。
双亲委派模型:Bootstrap ClassLoader、Extension ClassLoader、ApplicationClassLoader。
分派:静态分派与动态分派。
1. Java的内存模型以及GC算法
1. 并发
定义:即,并发(同时)发生。在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行,但任一个时刻点上只有一个程序在处理机上运行。
并发需要处理两个关键问题:线程之间如何通信及线程之间如何同步。
(01) 通信 —— 是指线程之间如何交换信息。在命令式编程中,线程之间的通信机制有两种:共享内存和消息传递。
(02) 同步—— 是指程序用于控制不同线程之间操作发生相对顺序的机制。在Java中,可以通过volatile,synchronized, 锁等方式实现同步。
2.主内存和本地内存
主内存 —— 即main memory。在java中,实例域、静态域和数组元素是线程之间共享的数据,它们存储在主内存中。
本地内存 —— 即local memory。 局部变量,方法定义参数 和 异常处理器参数是不会在线程之间共享的,它们存储在线程的本地内存中。
3.重排序
定义:重排序是指“编译器和处理器”为了提高性能,而在程序执行时会对程序进行的重排序。
说明:重排序分为——“编译器”和“处理器”两个方面,而“处理器”重排序又包括“指令级重排序”和“内存的重排序”。
关于重排序,我们需要理解它的思想:为了提高程序的并发度,从而提高性能!但是对于多线程程序,重排序可能会导致程序执行的结果
不是我们需要的结果!因此,就需要我们通过“volatile,synchronize,锁等方式”作出正确的实现同步。
4.内存屏障
定义:包括LoadLoad, LoadStore, StoreLoad, StoreStore共种内存屏障。内存屏障是与相应的内存重排序相对应的。
屏障类型 |
指令示例 |
说明 |
LoadLoad Barriers |
Load1; LoadLoad; Load2 |
确保Load1数据的装载,之前于Load2及所有后续装载指令的装载。 |
StoreStore Barriers |
Store1; StoreStore; Store2 |
确保Store1数据对其他处理器可见(刷新到内存),之前于Store2及所有后续存储指令的存储。 |
LoadStore Barriers |
Load1; LoadStore; Store2 |
确保Load1数据装载,之前于Store2及所有后续的存储指令刷新到内存。 |
StoreLoad Barriers |
Store1; StoreLoad; Load2 |
确保Store1数据对其他处理器变得可见(指刷新到内存),之前于Load2及所有后续装载指令的装载。StoreLoad Barriers会使该屏障之前的所有内存访问指令(存储和装载指令)完成之后,才执行该屏障之后的内存访问指令。 |
作用:通过内存屏障可以禁止特定类型处理器的重排序,从而让程序按我们预想的流程去执行。
5. happens-before
定义:JDK5(JSR-133)提供的概念,用于描述多线程操作之间的内存可见性。如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须存在happens-before关系。
作用:描述多线程操作之间的内存可见性。
[程序顺序规则]:一个线程中的每个操作,happens- before 于该线程中的任意后续操作。
[监视器锁规则]:对一个监视器锁的解锁,happens- before 于随后对这个监视器锁的加锁。
[volatile变量规则]:对一个volatile域的写,happens- before 于任意后续对这个volatile域的读。
[传递性]:如果A happens- before B,且B happens- before C,那么A happens- before C。
6. 数据依赖性
定义:如果两个操作访问同一个变量,且这两个操作中有一个为写操作,此时这两个操作之间就存在数据依赖性。
作用:编译器和处理器不会对“存在数据依赖关系的两个操作”执行重排序。
7.as-if-serial
定义:不管怎么重排序,程序的执行结果不能被改变。
8. 顺序一致性内存模型
定义:它是理想化的内存模型。有以下规则:
(01) 一个线程中的所有操作必须按照程序的顺序来执行。
(02) 所有线程都只能看到一个单一的操作执行顺序。在顺序一致性内存模型中,每个操作都必须原子执行且立刻对所有线程可见。
9. JMM
定义:Java Memory Mode,即Java内存模型。它是Java线程之间通信的控制机制。
说明:JMM对Java程序作出保证——如果程序是正确同步的,程序的执行将具有顺序一致性。即,程序的执行结果与该程序在顺序一致性内存模型中的执行结果相同。
10. 可见性
可见性一般用于指不同线程之间的数据是否可见。
在java中, 实例域、静态域和数组元素这些数据是线程之间共享的数据,它们存储在主内存中;主内存中的所有数据对该内存中的线程都是可见的。而局部变量,方法定义参数 和 异常处理器参数这些数据是不会在线程之间共享的,它们存储在线程的本地内存中;它们对其它线程是不可见的。
此外,对于主内存中的数据,在本地内存中会对应的创建该数据的副本(相当于缓冲);这些副本对于其它线程也是不可见的。
11. 原子性
是指一个操作是按原子的方式执行的。要么该操作不被执行;要么以原子方式执行,即执行过程中不会被其它线程中断。
同步机制:
1.volatile
1.1 作用
如果一个变量是volatile类型,则对该变量的读写就将具有原子性。如果是多个volatile操作或类似于volatile++这种复合操作,这些操作整体上不具有原子性。volatile变量自身具有下列特性:
[可见性]:对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写入。
[原子性]:对任意单个volatile变量的读/写具有原子性,但类似于volatile++这种复合操作不具有原子性。
1.2 volatile的内存语义
volatile写:当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存。
volatile读:当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量。
1.3 JMM中的实现方式
JMM针对编译器制定的volatile重排序规则表:
是否能重排序 |
第二个操作 |
||
第一个操作 |
普通读/写 |
volatile读 |
volatile写 |
普通读/写 |
NO |
||
volatile读 |
NO |
NO |
NO |
volatile写 |
NO |
NO |
1.4 volatile和 synchronize对比
在功能上,监视器锁比volatile更强大;在可伸缩性和执行性能上,volatile更有优势。
volatile仅仅保证对单个volatile变量的读/写具有原子性;而synchronize锁的互斥执行的特性可以确保对整个临界区代码的执行具有原子性。
2.锁
2.1 作用
锁是java并发编程中最重要的同步机制。
2.2 锁的内存语义
(01) 线程A释放一个锁,实质上是线程A向接下来将要获取这个锁的某个线程发出了(线程A对共享变量所做修改的)消息。
(02) 线程B获取一个锁,实质上是线程B接收了之前某个线程发出的(在释放这个锁之前对共享变量所做修改的)消息。
(03) 线程A释放锁,随后线程B获取这个锁,这个过程实质上是线程A通过主内存向线程B发送消息。
2.3 JMM如何实现锁
公平锁
公平锁是通过“volatile”实现同步的。公平锁在释放锁的最后写volatile变量state;在获取锁时首先读这个volatile变量。根据volatile的happens-before规则,释放锁的线程在写volatile变量之前可见的共享变量,在获取锁的线程读取同一个volatile变量后将立即变的对获取锁的线程可见。
非公平锁
通过CAS实现的,CAS就是compare and swap。CAS实际上调用的JNI函数,也就是CAS依赖于本地实现。以Intel来说,对于CAS的JNI实现函数,它保证:(01)禁止该CAS之前和之后的读和写指令重排序。(02)把写缓冲区中的所有数据刷新到内存中。
3.final
3.1 特性
对于基本类型的final域,编译器和处理器要遵守两个重排序规则:
(01) final写:“构造函数内对一个final域的写入”,与“随后把这个被构造对象的引用赋值给一个引用变量”,这两个操作之间不能重排序。
(02) final读:“初次读一个包含final域的对象的引用”,与“随后初次读对象的final域”,这两个操作之间不能重排序。
对于引用类型的final域,除上面两条之外,还有一条规则:
(03) final写:在“构造函数内对一个final引用的对象的成员域的写入”,与“随后在构造函数外把这个被构造对象的引用赋值给一个引用变量”,这两个操作之间不能重排序。
注意:
写final域的重排序规则可以确保:在引用变量为任意线程可见之前,该引用变量指向的对象的final域已经在构造函数中被正确初始化过了。其实要得到这个效果,还需要一个保证:在构造函数内部,不能让这个被构造对象的引用为其他线程可见,也就是对象引用不能在构造函数中“逸出”。
3.2 JMM如何实现final
通过“内存屏障”实现。
在final域的写之后,构造函数return之前,插入一个StoreStore障屏。在读final域的操作前面插入一个LoadLoad屏障。
2. jvm性能调优都做了什么
3. 介绍JVM中7个区域,然后把每个区域可能造成内存的溢出的情况说明
内存溢出和合理分配:http://blog.csdn.net/ye1992/article/details/9344807
http://flychao88.iteye.com/blog/2226205
http://www.itzhai.com/jvm-note-automatic-memory-management-mechanism.html#read-more
器池堆,栈栈区区
三个私有区:指令计数器,线程池,本地线程池
四个共享区:方法区,常量池,直接内存区,本地线程栈
堆是运行时数据区域,所有类实例和数组的内存均从此处分配。堆是在 Java 虚拟机启动时创建的
在JVM中堆之外的内存称为非堆内存(Non-heap memory)
参数- XX:+ HeapDumpOnOutOfMemoryError
先通过内存映像分析工具(如 Eclipse Memory Analyzer) 对 Dump 出来的堆转储快照进行分析,重点是确认内存中的对象是否是必要的,也就是要先分清楚到底是出现了内存泄漏( Memory Leak) 还是内存溢出( Memory Overflow)。
如果是内存泄露,可进一步通过工具查看泄露对象到 GC Roots 的引用链。
如果不存在泄露,换句话说,就是内存中的对象确实都还必须存活着,那就应当检查虚拟机的堆参数(- Xmx 与- Xms)
在 HotSpot 虚拟机中并不区分虚拟机栈和本地方法栈,因此,对于 HotSpot 来说,虽然- Xoss 参数(设置本地方法栈大小)存在,但实际上是无效的,栈容量只由- Xss 参数设定。
如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出 StackOverflowError 异常。如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出 OutOfMemoryError 异常。
限制于单线程中的操作,尝试了下面两种方法均无法让虚拟机产生 OutOfMemoryError 异常,尝试的结果都是获得 StackOverflowError 异常
在单个线程下,无论是由于栈帧太大还是虚拟机栈容量太小,当内存无法分配的时候,虚拟机抛出的都是 StackOverflowError 异常。
2GB内存容量( 操作系统限制)减去 Xmx( 最大堆容量),再减去 MaxPermSize( 最大方法区容量),程序计数器消耗内存很小,可以忽略掉。如果虚拟机进程本身耗费的内存不计算在内,剩下的内存就由虚拟机栈和本地方法栈“瓜分”了。
如果是建立过多线程导致的内存溢出,在不能减少线程数或者更换 64 位虚拟机的情况下,就只能通过减少最大堆和减少栈容量来换取更多的线程。
运行时常量池是方法区的一部分
在 JDK 1.6 及之前的版本中,由于常量池分配在永久代内,我们可以通过- XX: PermSize 和- XX: MaxPermSize 限制方法区大小,从而间接限制其中常量池的容量
" PermGen space", 说明运行时常量池属于方法区( HotSpot 虚拟机中的永久代)的一部分。
而使用 JDK 1.7 运行这段程序就不会得到相同的结果, while 循环将一直进行下去。
在 JDK 1.6 中, intern() 方法会把首次遇到的字符串实例复制到永久代中,返回的也是永久代中这个字符串实例的引用,而由 StringBuilder 创建的字符串实例在 Java 堆上,所以必然不是同一个引用
而 JDK 1.7( 以及部分其他虚拟机,例如 JRockit) 的 intern() 实现不会再复制实例,只是在常量池中记录首次出现的实例引用,因此 intern() 返回的引用和由 StringBuilder 创建的那个字符串实例是同一个。
方法区用于存放 Class 的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等。
一个类要被垃圾收集器回收掉,判定条件是比较苛刻的。在经常动态生成大量 Class 的应用中,需要特别注意类的回收状况。
DirectMemory 容量可通过- XX: MaxDirectMemorySize 指定,如果不指定,则默认与 Java 堆最大值(- Xmx 指定)一样
如果读者发现 OOM 之后 Dump 文件很小,而程序中又直接或间接使用了 NIO, 那就可以考虑检查一下是不是这方面的原因。
4. 介绍GC 和GC Root不正常引用。
GC(Garbage Collector) roots,特指的是垃圾收集器(Garbage Collector)的对象,GC会收集那些不是GC roots且没有被GC
roots引用的对象。
GC root有几下种:
- Class - 由系统类加载器(system class loader)加载的对象,这些类是不能够被回收的,他们可以以静态字段的方式保存持有其它对象。我们需要注意的一点就是,通过用户自定义的类加载器加载的类,除非相应的
java.lang.Class
实例以其它的某种(或多种)方式成为roots,否则它们并不是roots,. - Thread - 活着的线程
- Stack Local - Java方法的local变量或参数
- JNI Local - JNI方法的local变量或参数
- JNI Global - 全局JNI引用
- Monitor Used - 用于同步的监控对象
- Held by JVM - 用于JVM特殊目的由GC保留的对象,但实际上这个与JVM的实现是有关的。可能已知的一些类型是:系统类加载器、一些JVM知道的重要的异常类、一些用于处理异常的预分配对象以及一些自定义的类加载器等。然而,JVM并没有为这些对象提供其它的信息,因此就只有留给分析分员去确定哪些是属于"JVM持有"的了。
5. 自己从classload 加载方式,加载机制说开去,从程序运行时数据区,讲到内存分配,讲到String常量池,讲到JVM垃圾回收机制,算
法,hotspot。反正就是各种扩展
6. jvm 如何分配直接内存, new 对象如何不分配在堆而是栈上,常量池解析
7. 数组多大放在 JVM 老年代(不只是设置 PretenureSizeThreshold ,问通常多大,没做过一问便知)
8. 老年代中数组的访问方式
9. GC 算法,永久代对象如何 GC , GC 有环怎么处理
10. 谁会被 GC ,什么时候 GC
11. 如果想不被 GC 怎么办
12. 如果想在 GC 中生存 1 次怎么办
13、普遍认为尽量不要使用finalize()进行资源释放,为什么?
在finalize时可能会导致对象复活
finalize的执行时间是没有保障的,它完全由GC线程决定,极端情况下,若不发生GC,则finalize没有机会执行
一个糟糕的finalize会严重影响GC性能
回收对象-->>FinalizerThread的执行队列-->>
但是,在某些场合,使用finalize可以起到双保险作用:在MySQL的JDBC驱动中,com.mysql.jdbc.ConnectionImpl就实现
了finalize
14、GC的两种判定方法:引用计数与引用链
JVM面试的更多相关文章
- jvm面试常见题
背景:jvm相关题目面试必问,后面要深入的进行总结. JVM 面试知识整理 jvm调优命令 调优工具 Minor GC ,Full GC 触发条件 Minor GC触发条件:当Eden区满时,触发Mi ...
- JVM(七),JVM面试小知识
七.JVM面试小知识 1.JVM三大性能调优参数 -Xms -Xmx -Xss 的含义 2.java内存模型中堆和栈的区别 3.不同JDK版本中的intern()方法的区别
- Java基础技术JVM面试【笔记】
Java基础技术JVM面试[笔记] JVM JVM 对 java 类的使用总体上可以分为两部分:一是把静态的 class 文件加载到 JVM 内存,二是在 JVM 内存中进行 Java 类的生命周期管 ...
- 【搞定Jvm面试】 Java 内存区域揭秘附常见面试题解析
本文已经收录自笔者开源的 JavaGuide: https://github.com/Snailclimb ([Java学习+面试指南] 一份涵盖大部分Java程序员所需要掌握的核心知识)如果觉得不错 ...
- 【搞定Jvm面试】 JVM 垃圾回收揭秘附常见面试题解析
JVM 垃圾回收 写在前面 本节常见面试题 问题答案在文中都有提到 如何判断对象是否死亡(两种方法). 简单的介绍一下强引用.软引用.弱引用.虚引用(虚引用与软引用和弱引用的区别.使用软引用能带来的好 ...
- 2019年JVM面试都问了什么?快看看这22道面试题!(附答案解析)
一. Java 类加载过程? Java 类加载需要经历一下 7 个过程: 1. 加载 加载是类加载的第一个过程,在这个阶段,将完成一下三件事情: • 通过一个类的全限定名获取该类的二进制流. • 将该 ...
- 【搞定Jvm面试】 JDK监控和故障处理工具揭秘
本文已经收录自笔者开源的 JavaGuide: https://github.com/Snailclimb ([Java学习+面试指南] 一份涵盖大部分Java程序员所需要掌握的核心知识)如果觉得不错 ...
- jvm(n):JVM面试
Jvm内存结构,一般是面试官对Java虚拟机这块考察的第一问. Java虚拟机的内存结构一般可以从线程共有和线程私有两部分起头作答,然后再详细说明各自的部分,类似树状结构的作答,好处就是思路清晰,面试 ...
- [jvm][面试]JVM 调优总结
https://blog.csdn.net/wfh6732/article/details/57422967 堆大小设置JVM 中最大堆大小有三方面限制:相关操作系统的数据模型(32-bt还是64-b ...
随机推荐
- 【转】你必须了解的Session的本质
有一点我们必须承认,大多数web应用程序都离不开session的使用.这篇文章将会结合php以及http协议来分析如何建立一个安全的会话管理机制.我们先简单的了解一些http的知识,从而理解该协议的无 ...
- 保存iptables的防火墙规则的方法【转载】
转自: 保存iptables的防火墙规则的方法 - 51CTO.COMhttp://os.51cto.com/art/201103/249504.htm 保存iptables的防火墙规则的方法如下: ...
- C#入门经典(3-窗体应用程序-第二章要点)
新建一个窗体用用程序,拖一个按钮,加事件和Text属性.打开Form1Designer.cs.
- 荐 android 如何打包自定义控件(转)
荐 android 如何打包自定义控件(转) 目录[-] 方式一:将项目打包成jar包 方式二:项目作为一个library 设计自定义的控件对android开发人员来说,是家常便饭了,但是多次做项 ...
- linux下提示bash:command not found
新安装的linux系统,如果进行精简安装可能会出现bash:command not found 的提示,大家在安装的时候可以选择默认安装basic的组件,一般即可.到时候可以再升级. 如果新装的系 ...
- php查找文件内容
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><?ph ...
- VS2015安装提示出现“安装包丢失或损坏”解决方法
原因:microsoft root certificate authority 2010.microsoft root certificate authority 2011证书未安装,导致文件校验未通 ...
- RSA非对称加密Java实现
原文 加密基础方法类 import java.security.MessageDigest; import sun.misc.BASE64Decoder; import sun.misc.BASE64 ...
- git archive
git archive --format zip --output ../g.zip 3.4.2 git archive --format=tar \ --remote=ssh://remote_se ...
- EXCEL读写NPOI--导出功能
第一步:将NPOI中的一下三个文件复制到项目中