1、说说你知道的几种主要的jvm参数
-server -Xmx3g -Xms3g -XX:MaxPermSize=128m
-XX:NewRatio=1 eden/old 的比例
-XX:SurvivorRatio=8 s/e的比例
-XX:+UseParallelGC
-XX:ParallelGCThreads=8
-XX:+UseParallelOldGC 这是JAVA6出现的参数选项
-XX:LargePageSizeInBytes=128m 内存页的大小, 不可设置过大, 会影响Perm的大小。
-XX:+UseFastAccessorMethods 原始类型的快速优化
-XX:+DisableExplicitGC 关闭System.gc()
2、-XX:+UseCompressedOops有什么作用?
1)允许在64位JVM中内存引用地址为32位,并且访问接近32GB的堆空间地址;
2)压缩64位指针,减少了在GC中花费的时间,可能性能略有下降;
3)jdk1.6.0_22默认关闭,之后的JVM默认启用;
3、Java 类加载器都有哪些?
引导类加载器(bootstrap class loader):用来加载Java核心库(jre/lib/rt.jar)。
扩展类加载器(extensions class loader):用来加载Java扩展库(jre/ext/*.jar)。
系统类加载器(system class loader):根据类路径(CLASSPATH)来加载Java类。
自定义类加载器(custom class loader):继承java.lang.ClassLoader类实现自己的类加载器。
4、简单说说你了解的类加载器,是否实现过类加载器?
一个类的生命周期:加载、验证、准备、解析、初始化、使用、卸载。其中加载、验证、准备、初始化、使用、卸载是按顺序执行的。
在加载阶段,虚拟机完成三件事:
1)通过一个类的全限定名类获取定义此类的二进制字节流。
2)将字节流的所代表的静态存储结构转化成方法区运行时的数据结构。
3)在内存中生成一个代表这个类的java.lang.Class的对象,作为方法区的这个类各种数据访问的入口。
验证是为了确保Class文件字节流中包含的信息符合当前虚拟机的要求,并且不会危害到虚拟机本身。
准备阶段是给类变量赋予初始值的阶段。这里的初始值是指变量默认的指,并不是用户赋予的初始值。
初始化阶段是类加载的最后一步。是给类变量赋予初始值。
类加载器的双亲委派原理:
加载器在加载类时,先检查当前类有没有被加载,如果没有被加载,则首先调用父类加载器加载,如果父类加载器加载失败,才交给当前的类加载器加载。
自定义类加载器
1)如果不想打破双亲委派模型,只需重写findClass方法即可
2)如果想打破双亲委派模型,就重写整个loadClass方法
5、JVM如何加载字节码文件?
类加载是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个这个类的java.lang.Class对象,用来封装类在方法区类的对象。
加载类的方式:
1)从本地系统直接加载
2)通过网络下载.class文件
3)从zip,jar等归档文件中加载.class文件
4)从专有数据库中提取.class文件
5)将源文件动态编译为.class文件(服务器)
6、JVM内存分哪几个区,每个区的作用是什么?
JVM内存区域分为五个部分:堆,方法区,虚拟机栈,本地方法栈,程序计数器。
堆: 是Java对象的存储区域,任何用new分配的对象实例和数组,都分配在堆上,可用-Xms -Xmx进行内存控制,从JDK1.7之后,运行时常量池从方法区移到了堆上。
方法区:用于存储被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据,在JDK1.7及以前称为永久代,JDK1.8永久代被移除。
虚拟机栈:虚拟机栈中执行每个方法时,都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
本地方法栈:与虚拟机栈作用相似,虚拟机栈为Java方法服务,本地方法栈为Native方法服务,执行每个本地方法时,都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
程序计数器:指示JVM下一条要执行的字节码指令。
其中方法区和堆被JVM中多个线程共享,比如类的静态常量就存放在方法区,供类对象之间共享,虚拟机栈、本地方法栈、pc寄存器是每个线程独立拥有的,不与其他线程共享。
7、一个对象从创建到销毁都是怎么在这些部分里存活和转移的?
1)用户创建一个对象,运行时JVM首先会去方法区寻找该对象的类型信息,没有则使用类加载器将字节码文件加载至内存中的方法区,并将对象的类型信息存放至方法区。
2)接着JVM在堆中为新的对象实例分配内存空间,这个实例持有指向方法区的对象类型信息的引用,引用指的是类型信息在方法区中的内存地址。
3)在此运行的JVM进程中,会首先起一个线程跑该用户程序,创建线程的同时也创建了一个虚拟机栈,虚拟机栈用来跟踪线程运行中的一系列方法调用过程,每调用一个方法就会创建并往栈中压入一个栈帧,栈帧用来存储方法的参数、局部变量和运算过程的临时数据。
4)JVM根据对象引用持有的堆中对象的内存地址,定位到堆中的实例,由于堆中实例持有指向方法区的对象类型信息的引用,从而获得方法的字节码信息,接着执行方法包含的指令。
5)将对象指向null
6)JVM GC
8、解释内存中的栈(stack)、堆(heap)和方法区(method area)的用法?
栈的使用:基本数据类型的变量,一个对象的引用,还有函数调用的现场保存都使用栈空间。
堆的使用:通过new关键字和构造器创建的对象则放在堆空间,堆是垃圾收集器管理的主要区域。
方法区的使用:方法区和堆都是各个线程共享的内存区域,用于存储已经被JVM加载的类信息、常量、静态变量、JIT编译器编译后的代码等数据;程序中的字面量(literal)如直接书写100、"hello"和常量都是放在常量池中,常量池是方法区的一部分。
栈空间操作最快但栈很小,通常大量的对象都放在堆空间,栈和堆的大小都可以通过JVM的启动参数来调整,栈空间不足会引发StackOverflowError,堆和常量池空间不足则会引发OutOfMemoryError。
9、JVM中哪个参数是用来控制线程的栈大小?
-Xss参数
10、简述内存分配与回收策略
一、对象优先在Eden分配
大多数情况下,对象在新生代Eden区分配。当Eden区没有足够空间时,JVM将发起一次Minor GC。
二、大对象直接分配到老年区
大对象是指需要大量连续内存空间的对象,典型的就是很长的字符串和数组。大对象易导致内存还有不少空间时提前触发垃圾收集以获取足够的连续空间。
设置参数:-XX:PretenureSizeThreshold=x。x表示大小的数值,大于设置值的对象直接在老年代分配。避免在Eden区和两个Survivor区之间发生大量的内存复制。
三、长期存活的对象将进入老年代
JVM给每个对象定义了一个对象年龄(Age)计数器,如果对象在Eden出现并经过一次Minor GC后仍存活,并能被Survivor容纳的话,将被移动到Survivor中,并且年龄设为1。对象在Survivor区每熬过一次Minor GC,年龄就增加1岁,当它的年龄增加到一定程度(默认为15岁)就会进入老年代中。对象晋升老年代的年龄阈值可通过参数-XX:MaxTenuringThreshold设置。
四、动态对象年龄判定
JVM并不总是要求对象的年龄必须达到MaxTenuringThreshold才能晋升到老年代,如果在Survivor区中相同年龄的对象所有大小之和超过Survivor空间的一半,年龄大于或等于该年龄的对象直接进入老年代,无需等到MaxTenuringThreshold中要求的年龄。
五、空间分配担保
只要老年代的连续空间大于新生代对象总大小或历次晋升的平均大小就会Full GC,否则Minor GC。
11、简述重排序,内存屏障,happen-before,主内存,工作内存?
从源代码到指令序列的重排序:
1) 编译器优化的重排序:编译器在不改变单线程语义情况下,重新安排语句的执行顺序
2) 指令级的并行重排序:现代处理器采用了指令级别的并行技术将多条指令重叠执行
3) 内存系统的重排序:由于处理器使用缓存读/写缓冲区,使得加载和存储看上去可能是乱序执行,对于编译器,JMM的编译器指令重排序规则会禁止特定类型的编译器重排列,对于处理器的重排序,JMM的处理器重排序规则会要求插入特定类型的内存屏障,通过内存屏障指令来禁止特定类型的处理器重新排序。
happens-before简介
在JMM中如果一个操作的结果需要对另一个操作可见,两个操作间必须存在happens-before关系。
1)程序顺序规则:一个线程中的每个操作,happens-before于该线程中的任意后续操作
2)监视器锁规则:对一个锁的解锁,happens-before于随后对这个锁的加锁
3)volatile变量规则: 对于一个volatile域的写,happens-before于任意后续对这个volatile域的读
内存屏障:
1)确保指令重排序时不会将其后面的代码排到内存屏障之前
2)确保指令重排序时不会将其前面的代码排到内存屏障之后
3)确保在执行到内存屏障修饰的指令时前面的代码全都执行完成
4)强制将线程工作内存中的值修改到主内存中
5)如果是写操作,则会导致其他线程工作内存的缓存数据无效
JVM将内存分为主内存和工作内存两个部分。
1)所有变量都存储在主内存中,对所有线程都共享。
2)每条线程都有自己的工作内存,保存的是主存中某些变量的拷贝,线程对变量的所有操作都必须在工作内存中进行,不能直接读写主内存中的变量。
3)线程之间无法直接访问对方工作内存中的变量,线程间变量的传递均需通过主内存来完成。
线程对内存间交互操作:
Lock(锁定):作用于主内存中的变量,把一个变量标识为一条线程独占的状态。
Read(读取):作用于主内存中的变量,把一个变量的值从主内存传输到线程的工作内存中。
Load(加载):作用于工作内存中的变量,把从主内存中得到的变量值放入工作内存的变量副本中。
Use(使用):作用于工作内存中的变量,把工作内存中一个变量值传递给执行引擎。
Assign(赋值):作用于工作内存中的变量,把一个从执行引擎接收到的值赋给工作内存中的变量。
Store(存储):作用于工作内存中的变量,把工作内存中的一个变量值传到主内存中。
Write(写入):作用于主内存中的变量,把从工作内存中得到的变量值放入主内存变量中。
Unlock(解锁):作用于主内存中的变量,把一个处于锁定状态的变量释放,之后可被其它线程锁定。
将变量从主内存读取到工作内存中,必须顺序执行read、load;
将变量从工作内存同步回主内存中,必须顺序执行store、write。
12、Java中存在内存泄漏问题吗?请举例说明
内存泄露指不再被程序使用的对象或变量一直占据在内存中。Java使用有向图的方式进行垃圾回收管理,可以消除引用循环的问题,例如有两个对象相互引用,只要它们和根进程不可达,那么GC可以回收它们。
java中内存泄露的发生场景
1)程序员可能创建了一个对象,以后一直不使用,这个对象却一直被引用,即这个对象无用但是却无法被垃圾回收器回收,这就是内存泄露,一定要让程序将各种分支情况都完整执行到结束,然后看某个对象是否被使用过,如果没有,才能判定这个对象属于内存泄露。
2)如果一个外部类实例对象的方法返回了一个内部类的实例对象,这个内部类对象被长期引用了,即使那个外部类实例对象不再被使用,但由于内部类持久外部类的实例对象,这个外部类对象将不会被垃圾回收,也会造成内存泄露。
3)当一个对象被存进HashSet集合中,就不能修改这个对象中的那些参与计算哈希值的字段了,否则,对象修改后的哈希值与最初存进HashSet集合中时的哈希值就不同了,在这种情况下,即使在contains方法使用该对象的当前引用作为参数去HashSet集合中检索对象,也将返回找不到对象的结果,这也会导致无法从HashSet集合中单独删除当前对象,造成内存泄露。
4)Hibernate的Session(一级缓存)中的对象属于持久态,垃圾回收器是不会回收这些对象的,然而这些对象中可能存在无用的垃圾对象,如果不及时关闭(close)或清空(flush)一级缓存就可能导致内存泄露。
13、简述 Java 中软引用(SoftReference)、弱引用(WeakReference)和虚引用?
强引用:类似“Object obj = new Object()”这类的引用,只要强引用还存在,GC永远不会回收被引用的对象。
软引用(SoftReference):描述一些还有用,但并非必需的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常前,将会把这些对象列进回收范围并进行第二次回收。如果这次回收还是没有足够的内存,才会抛出内存溢出异常。
弱引用(WeakReference):描述非必需对象,强度比软引用更弱,被弱引用关联的对象只能生存到下一次GC前。当执行GC时,无论当前内存是否足够,都会回收掉被弱引用关联的对象。
虚引用(PhantomReference):也称幽灵引用或幻影引用,是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是希望在这个对象被回收时收到一个系统通知。
14、32位JVM和64位JVM的最大堆内存分别是多数?32位和64位的JVM,int类型变量的长度?
32位JVM堆内存理论上可达到2^32,即4GB,但实际会比这个小很多。
64位JVM堆内存理论上可达到2^64,实际可指定到100GB。
32和64位JVM 中,int类型变量的长度相同,都是32位或者4个字节(一个字节8位)。
15、怎样通过Java程序来判断JVM是32位还是64位?
1)System.getProperty("sun.arch.data.model")
2)控制台输入java -version
3)java -d64 -version
16、什么情况下会发生栈内存溢出?
每个方法在执行时都会创建一个栈帧,用来存储局部变量表、操作数栈、动态链接、方法出口等信息。局部变量表又包含基本数据类型、对象引用类型(局部变量表编译器完成,运行期间不会变化)。
栈溢出就是方法执行时创建的栈帧超过了栈的深度。最有可能的就是方法递归调用。
17、双亲委派模型是什么?
JVM存在两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),使用C++实现(HotSpot虚拟机中),是虚拟机自身的一部分;另一种就是所有其他的类加载器,独立于虚拟机外部,并且全部继承自java.lang.ClassLoader。
某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。
使用双亲委派模型的好处在于Java类随着它的类加载器一起具备了一种带有优先级的层次关系。例如类java.lang.Object,它存在rt.jar中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的Bootstrap ClassLoader进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。
18、jinfo,jstack,jstat,jmap,jconsole怎么用?
jinfo:观察运行中的JAVA程序运行环境参数,参数包括Java System属性和JVM命令行参数,java class path等信息。格式:jinfo 进程pid
jps:用来显示本地java进程,并显示进程号。格式:jps 或 jps远程服务ip地址(默认端口1099)
jstat:一个极强的监视VM内存工具。用来监视JVM的各种堆和非堆的大小及其内存使用量。
jstack:可以观察到jvm中当前所有线程的运行情况和线程当前状态。格式:jstack 进程pid。当程序出现死锁时使用命令:jstack 进程ID > jstack.log,在jstack.log文件中搜索关键字“BLOCKED”,定位到引起死锁的地方。
jmap:观察运行中的jvm物理内存占用情况(如产生哪些对象及其数量)。格式:jmap [option] pid
option参数如下:
-heap:打印jvm heap情况
-histo:打印jvm heap直方图。其输出信息包括类名,对象数量,对象占用大小。
-histo:同上,但只打印存活对象的情况
-permstat:打印permanent generation heap情况
使用jmap进行heap dump: jmap -dump:format=b,file=<filename> <pid>
打印内存统计图:jmap -histo:live <pid>
jconsole:一个java GUI监视工具,以图表化的形式显示各种数据,并可通过远程连接监视远程的服务器VM。
19、JRE、JDK、JVM 及 JIT 之间有什么不同?
JVM:--java虚拟机(JVM)
JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。
Java虚拟机在执行字节码时,把字节码解释成具体平台上的机器指令执行。
JRE:--java运行时环境(JRE)
JRE是JVM的一个超集,用于执行java程序,运行时JRE变成了JVM。
JDK:--java开发工具箱(JDK)
指的是编写一个java应用所需要的所有jar文件和可执行文件。jdk把.java文件编译成为.class字节码文件。JRE是JDK的一部分。
JIT:--即时编译器(JIT)
是种特殊的编译器,它通过有效的把字节码变成机器码来提高JVM的效率。
20、操作系统中 heap 和 stack 的区别?
栈是一种线形集合,其添加和删除元素的操作应在同一端尾部完成。栈后进先出。
堆是栈的一个组成元素。Stack存取速度仅次于寄存器,存储效率比heap高,可共享存储数据。
21、虚拟内存是什么?
虚拟内存是WIN系统用来管理内存的一种方法,就是把内存中的信息分出去一部分存在硬盘上。