总结
Java与c++的区别?
- c/c++编译生成的可执行文件跟操作系统和指令集架构都有关系
- Java的一大特点就是摆脱了硬件平台的束缚,一次编写到处运行,
java虚拟机的功能比Java 要强大,实际上是跨语言的平台,不同的编
程语言编写的程序也可以编译成字节码文件,如果符合虚拟机规范也可
以在虚拟机上运行,实际上能在jvm平台执行的字节码格式都是一样的
,统称为jvm字节码
JDK和JRE的区别?
JDK是用于支持Java程序开发的最小环境,JRE是支持Java程序运行的标
准环境
- Java程序设计语言,Java虚拟机,JavaAPI类库组成JDK
- Java虚拟机,Java SE API组成JRE
什么是虚拟机?
是一台虚拟的计算机,也就是一个软件,用来执行一系列虚拟计算机指令
- 系统虚拟机 VisualBox VMware 完全对物理计算机的仿真,提供一个
可运行完整操作系统的软件平台,实际上模拟的是硬件 - 程序虚拟机 Java虚拟机 专门为执行单个计算机程序而设计,Java虚
拟机中执行的指令称为Java字节码指令,Java字节码是可以运行在任何
支持Java虚拟机的硬件平台和操作系统上的二进制文件,字节码的执行
实际上是被翻译成机器代码而执行的过程。实际上模拟的是JVM
栈式架构和寄存器架构的区别?
Java编译器输入的指令流基本上是一种基于栈的指令集架构
说一下JVM的主要组成部分及其作用?
JVM包含两个子系统和两个组件,两个子系统为Class loader(类装载)、
Execution engine(执行引擎),两个组件为Runtime data area(运行
时数据区)、Native Interface(本地接口)
- Class loader(类装载):根据给定的全限定名类名(如:java.lang.
Object)来装载class文件到Runtime data area中的method area - Execution engine(执行引擎):执行classes中的指令
- Native Interface(本地接口):与native libraries交互,是其它
编程语言交互的接口 - Runtime data area(运行时数据区域):这就是我们常说的JVM的内存
Java程序运行机制是什么样的?
Java程序运行机制步骤
- 首先利用IDE集成开发工具编写Java源代码,源文件的后缀为.java
- 再利用编译器(javac)将源代码编译成字节码文件,也叫前端编译器,
字节码文件的后缀名为.class。因为操作系统并不能识别字节码,所以
Java虚拟机中的执行引擎中的JIT编译器要将字节码翻译成机器指令被
CPU执行,所以JIT被称为后端编译器 - 运行过程Java虚拟机能够将字节码解释成具体平台的机器码
说一下JVM运行时数据区?
Java虚拟机在执行Java程序的过程中会把它所管理的内存区域划分为若干
个不同的数据区域
- 程序计数器(Program Counter Register):当前线程所执行的字节
码的行号指示器,字节码解析器的工作是通过改变这个计数器的值,来选
取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复
等基础功能,都需要依赖这个计数器来完成。任何时间一个线程都只有一
个方法在执行,也就是当前方法,程序计数器会存储当前线程正在执行的
Java方法的虚拟机字节码指令地址,如果执行native方法则未指定值。是
唯一不存在OOM的区域 - Java 虚拟机栈(Java Virtual Machine Stacks):描述Java方法
执行的线程内存模型。其内部保存一个个栈帧,对应一次次的Java方法调
用,栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息 - 本地方法栈(Native Method Stack):与虚拟机栈的作用是一样的,
只不过虚拟机栈是服务Java方法的,而本地方法栈是为虚拟机调用Native
方法服务的 - Java 堆(Java Heap):Java 虚拟机中内存最大的一块,是被所有线
程共享的,几乎所有的对象实例都在这里分配内存。还可以分配多个线程私
有的分配缓冲区(TLAB),以提升对象分配时的效率 - 方法区(Methed Area):用于存储已被虚拟机加载的类信息、常量、静
态变量、即时编译后的代码等数据
运行时数据区中哪些线程私有哪些线程共享?
- 线程私有 程序计数器、Java虚拟机栈、本地方法栈
- 线程共享 堆、方法区
为什么程序计数器是线程私有?
多线程在一个特定时间只会执行一个线程的方法,CPU会不停地做任务切换,
这样必然导致中断和恢复,为了能够准确记录各个线程正在执行的当前字节
码指令的地址,最好就是为每个线程分配一个PC寄存器
栈中可能出现的异常是什么?
HotSpot虚拟机的栈容量是不可以动态扩展的,所以不会出现虚拟机栈无法
扩展而导致的OOM异常,只要申请栈空间成功了就不会OOM,如果申请时就
失败会出现OOM
如何设置栈空间?
设置线程最大栈空间,注意不区分Java虚拟机栈和本地方法栈。1m=1024k
HotSpot虚拟机不支持扩展
1 | -Xss256k |
如何设置堆空间?
堆空间的最小值和最大值,参数都是在 VM options 中设置
1 | -Xms20m -Xmx20m |
如何设置方法区空间?
1 | -XX:MetaspaceSize=100m -XX:MaxMetaspaceSize |
什么是运行时常量池?
运行时常量池是方法区的一部分。Class文件除了类的版本信息、字段、方法、
接口等描述信息外,还有一项信息就是常量池表,用于存放编译期生成的各
种字面量、符号引用和直接引用,在类加载后存放到方法区的运行时常量池
方法区的演进过程是怎样的?
- jdk1.6以前 有永久代,静态变量存放在永久代上
- jdk1.7 有永久代但是逐步去永久代,字符串常量池、静态变量移除保留在堆中
- jdk1.8及以后 无永久代,类型信息、字段、方法、常量保存在本地内存的元
空间中,当字符串常量池、静态变量仍在堆中
元空间和方法区的本质区别是什么?
元空间不在虚拟机设置的内存中,而是使用本地内存,因此元空间依赖于内存
大小。JVM在启动时会分配一个内存大小,因此永久代是存在OOM的
堆和栈有什么区别?
- 申请方式 stack:由系统自动分配,声明在函数中一个局部变量int b,
系统自动在栈中为b 开辟空间。heap:需要程序员自己申请,并指明大小,
对于Java 需要手动new Object()的形式开辟 - 申请后系统的响应
- stack:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将
报异常提示栈溢出 - heap:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收
到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点
,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序。另
外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的
将多余的那部分重新放入空闲链表中
- 申请大小的限制
- stack:栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话
的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在 WINDOWS 下
,栈的大小是2M(默认值也取决于虚拟内存的大小),如果申请的空间超
过栈的剩余空间时,将提示 overflow。因此,能从栈获得的空间较小 - heap:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统
是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是
由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可
见,堆获得的空间比较灵活,也比较大
- 申请效率的比较
- stack:由系统自动分配,速度较快。但程序员是无法控制的
- heap:由 new 分配的内存,一般速度比较慢,而且容易产生内存碎片,
不过用起来最方便
- heap和stack中的存储内容
- stack:在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调
用语句的下一条可执行语句)的地址, 然后是函数的各个参数,在大多数的
C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变
量是不入栈的。当本次函数调用结束后,局部变量先出栈,然后是参数,最
后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由
该点继续运行。 - heap:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容有程
序员安排
栈帧包含什么?
每个方法在执行的时候就会创建一个栈帧,它包含局部变量表、操作数栈、动态
链接、方法出口等信息,局部变量表又包括基本数据类型和对象的引用
对象的创建有哪几种方式?
- 使用new关键字 调用了构造函数
- 使用Class的newInstance方法 调用了构造函数
- 使用Constructor类的newInstance方法 调用了构造函数
- 使用clone方法 没有调用构造函数
- 使用反序列化 没有调用构造函数
对象创建的流程是怎样的?
- 当Java虚拟机遇到一条new指令时,首先检查这个指令的参数是否能在常
量池中定位到一个类的符号引用 - 检查这个符号引用代表的类是否已经被加载、解析和初始化过,如果没有
先执行相应类加载过程 - 对象所需的内存大小在类加载完成后便可确定,内存分配完成后将分配到
的空间(不包括对象头)都初始化为零值,这步操作保证对象的实例字段在
Java代码中不赋初始值就可以使用 - 接着是做一些必要的对象设置,比如确定对象是哪个类的实例,如何找到
类的元数据信息,对象的哈希码,对象的GC分代年龄等
对象的实例化过程是怎样的?
调用完构造器对象才算创建完成,new之后是默认赋值,调用构造器是显式赋
值,是否执行构找函数是由new指令后面是否跟随invokespecial指令决定
,Java编译器会在遇到new关键字的地方同时生成这两条字节码指令
对象的内存是如何分配的?
若Java堆中内存是绝对规整的,使用“指针碰撞“方式分配内存;如果不是规整
的,就从空闲列表中分配,叫做”空闲列表“方式。Serial、ParNew带有压缩整
理过程的收集器使用指针碰撞,CMS基于清除算法的收集器使用空闲列表
并发情况如何分配内存?
- 对分配内存空间的动作进行同步处理(采用 CAS + 失败重试来保障更新
操作的原子性) - 把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在Java
堆预先分配一小块内存,称为本地线程分配缓冲(Thread Local Allocation
Buffer, TLAB)。哪个线程要分配内存,就在哪个线程的 TLAB 上分配。只有
TLAB 用完并分配新的TLAB时,才需要同步锁。通过-XX:+/-UserTLAB参数来
设定虚拟机是否使用TLAB
对象的访问定位有哪几种情况?
栈帧中的对象引用访问到内部对象实例有两种方式
- 句柄访问 可以理解为指向指针的指针,维护着对象的指针。句柄不直接指
向对象,而是指向对象的指针(句柄不发生变化,指向固定内存地址),再
由对象的指针指向对象的真实内存地址,好处是reference中存储的是稳定
句柄地址,在对象被移动时只会句柄中的实例数据指针,而reference本身
不需要修改 - 直接接指针 指向对象,代表一个对象在内存中的起始地址。好处是速度快,
节省一次指针定位的时间开销,HotSpot采用这种方式
内存泄漏和内存溢出的区别?
- 内存溢出 没有空闲内存,并且垃圾收集器也无法提供更多内存。没有空闲
内存有两种情况,一是Java虚拟机的堆内存设置不够,二是代码中创建了大
量大对象,并且长时间不能被垃圾收集器收集 - 内存泄漏 只有对象不会再被程序用到,但是GC又不能回收的情况才叫内
存泄漏,尽管内存泄漏并不会立刻引起程序崩溃,但是一旦发生内存泄漏,
程序中的可用内存就会逐步被蚕食直至耗尽所有内存,最终出现OOM
内存泄漏的原因是什么?如何解决?
java导致内存泄露的原因很明确:长生命周期的对象持有短生命周期对象的
引用就很可能发生内存泄露,尽管短生命周期对象已经不再需要,但是因为
长生命周期对象持有它的引用而导致不能被回收,这就是java中内存泄露
的发生场景。
解决方法:通过工具查看泄漏对象到GC Roots的引用链,找到泄漏对象是
通过怎样的引用路径、与哪些GC Roots相关联,才导致垃圾收集器无法回
收它们,根据泄漏对象的类型信息以及它到GC Roots引用链的信息,一
般可以准确定位这些对象创建的位置,进而找到内存泄漏的代码的具体
位置
工作内存分配在哪?
TLAB(Thread Local Allocation Buffer)本地线程分配缓冲区,每个线
程会首先向OS申请一大块内存作为私有的内存缓冲区,当创建对象需要申请
内存时,优先从内存缓冲区中进行分配,由于是线程私有的,不存在并发问
题,速度会非常快。这样只有当内存缓冲区不够用时,线程才会同步的向OS
申请内存,大大减少了锁的争用
什么是垃圾?
垃圾就是指在运行程序中没有任何指针指向的对象,这个对象就是需要被回
收的垃圾,如果不及时对内存中的垃圾进行清理,那么这些垃圾对象所占的
内存空间会一直保留到应用程序结束,被保留的空间无法被其他对象使用
c++和Java在垃圾回收上的区别?
- 早期的c/c++时代,垃圾回收基本是手工进行,可以使用new进行内存申请,
并使用delete进行内存释放,但是给开发人员带来频繁申请和释放内存的管理
负担,万一有一块内存忘记delete了那么这块内存就永远没有被清除 - Java、C#、Python、Ruby等语言都使用了自动垃圾回收
自动内存管理是什么?
自动内存管理,开发人员无需手动参与内存的分配和回收,降低内存泄漏和内存
溢出的风险,更加专注于业务的开发。但是也弱化了Java开发人员在程序出现内
存溢出时定位问题和解决问题的能力,所以了解JVM的自动内存分配和内存回收
原理就尤为重要
GC是什么?为什么要GC?
GC是垃圾收集的意思(Gabage Collection),内存处理是编程人员容易出现问
题的地方,忘记或错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java
提供的GC功能可以自动监测对象是否超过作用域从而达到自动回收内存的目的
,Java 语言没有提供释放已分配内存的显示操作方法
垃圾回收机制是什么?
在Java中,程序员是不需要显示的去释放一个对象的内存的,而是由虚拟机自行
执行。在JVM中,有一个垃圾回收线程,它是低优先级的,在正常情况下是不会
执行的,只有在虚拟机空闲或者当前堆内存不足时,才会触发执行,扫面那些
没有被任何引用的对象,并将它们添加到要回收的集合中,进行回收
垃圾回收的优点是什么?
- java程序员在编写程序时不再考虑内存管理的问题
- 有效的防止了内存泄露,可以有效的使用可使用的内存
判断对象是否可回收的方法有哪些?
- 引用计数算法 对每个对象保存一个整型的引用计数器属性,用于记录对象被
引用的情况,如果对象的引用计数器的值为0那么这个对象就不再使用可以被回收 - 可达性分析算法 从 GC Roots开始向下搜索,搜索所走过的路径称为引用
链。当一个对象到GC Roots没有任何引用链相连时,则证明此对象是可以被回
收的
引用计数算法的优缺点?
- 优点是实现简单,垃圾对象容易辨识,判定效率高,回收没有延迟性
- 缺点是需要单独的字段存储计数器,这样增加存储空间的开销,每次赋值
都需要更新计数器,伴随加法和减法操作增加时间开销,尤其是无法处理循
环引用的情况,导致Java的垃圾回收期中没有使用这种算法
Java为什么使用可达性分析算法?
可达性分析算法不仅同样具备实现简单和执行高效等特点,而且有效解决在引用
计数器中循环利用的问题防止内存泄漏的发生,这种类型的垃圾收集也叫追踪性
垃圾收集
GC Roots有哪几种类型?
如果要使用可达性分析算法来判断内存是否可以回收,那么分析工作必须在一
个能保障一致性的快照中进行,这点不满足分析结果的正确性就无法保证,这
点也是导致GC时必须”stop the world”的一个重要原因,及时在CMS 收集器
中枚举根节点也是必须要停顿的
- 虚拟机栈中引用的对象
- 方法区类静态属性引用的变量
- 方法区常量池引用的对象
- 本地方法栈JNI引用的对象
Java中都有哪些引用类型?
- 强引用 是普通的对象引用关系,发生gc 的时候不会被回收
1
Object obj=new Object();
- 软引用 有用但不是必须的对象,在发生内存溢出之前会被回收
1
SoftReference<Object> sf=new SoftReference<Object>();
- 弱引用 有用但不是必须的对象,在下一次GC时会被回收
1
WeakReference<Object> wr=new WeakReference<Object>();
- 虚引用 无法通过虚引用获得对象,它主要用来跟踪对象被垃圾回收的活动
,虚引用的用途是在gc 时返回一个通知,必须和引用队列一起使用
对象的finalization机制是什么?
- Java语言提供了对象终止机制来允许开发人员提供对象被销毁之前的自定义
处理逻辑 - 当垃圾回收器发现没有一个引用指向一个对象,即垃圾回收此对象之前总会
先调用这个对象的finalize()方法,这个方法只能被调用一次 - 在finalize()时可能导致对象复活,比如把自己(this)赋给某个类变量或
对象的成员变量
方法区的回收过程是怎样的?
废弃的常量和不再使用的类型
说一下JVM有哪些垃圾回收算法?
这些算法都属于追踪式垃圾收集算法
- 标记-清除算法:第一步:利用可达性去遍历内存,把存活对象和垃圾对象
进行标记,第二步:在遍历一遍,将所有标记的对象回收掉。特点:效率不行
,标记和清除的效率都不高,标记和清除后会产生大量的不连续的空间分片
,可能会导致之后程序运行的时候需分配大对象而找不到连续分片而不得不
触发一次GC - 复制算法:将内存按照容量大小分为大小相等的两块,每次只使用一块,
当一块使用完了,就将还存活的对象移到另一块上,然后在把使用过的内存
空间移除,特点:不会产生空间碎片,内存使用率极低 - 标记-整理算法:第一步:利用可达性去遍历内存,把存活对象和垃圾对象
进行标记,第二步:将所有的存活的对象向一段移动,将端边界以外的对象都
回收掉,特点:适用于存活对象多,垃圾少的情况,需要整理的过程,无空间
碎片产生 - 分代算法:根据对象存活周期的不同将内存划分为几块,一般是新生代和老
年代,在新生代中,有大量对象死去和少量对象存活,所以采用复制算法,只
需要付出少量存活对象的复制成本就可以完成收集;老年代中因为对象的存活
率极高,没有额外的空间对他进行分配担保,所以采用标记清理或者标记整理
算法进行回收
分代收集理论是什么?
垃圾收集器大多遵循分代收集理论进行设计,建立在两个分代假说之上
- 弱分代假说 绝大多数对象都是朝生夕灭的
- 强分代假说 熬过多次垃圾收集过程的对象就越难以消亡
奠定多个垃圾收集器的一致的设计原则:收集器应该将Java堆划分出不同的区域
,然后将回收对象依据其年龄分配到不同区域
一次完整的GC过程?
分为新生代和老年代,新生代默认占总空间的1/3,老年代默认占2/3。 新生代使
用复制算法,有3个分区:Eden、To Survivor、From Survivor,它们的默认占
比是 8:1:1。 当新生代中的Eden 区内存不足时,就会触发Minor GC
- 在Eden 区执行了第一次GC 之后,存活的对象会被移动到其中一个Survivor
分区 - Eden 区再次GC,这时会采用复制算法,将Eden 和from 区一起清理,存活
的对象会被复制到to 区 - 移动一次,对象年龄加 1,对象年龄大于一定阀值会直接移动到老年代
- Survivor 区相同年龄所有对象大小的总和 >Survivor 的一半时,大于
或等于该年龄的对象直接进入老年代 - Major GC,指的是老年代的垃圾清理但并未找到明确说明何时在进行Major GC
- FullGC,整个堆的垃圾收集,触发条件: 每次晋升到老年代的对象平均大小
大于老年代剩余空间
垃圾回收算法有哪些?
有四种垃圾回收算法,分别是标记清除法、标记整理法、复制算法、分代收集算法
标记-清除算法是什么?优缺点是什么?
标记无用对象,然后进行清除回收。标记过程就是使用可达性分析算法
- 优点 实现简单,不需要对象进行移动
- 缺点 效率并不高,需要停止整个应用程序,导致用户体验差,这种方式清理
出来的空闲内存是不连续的,产生内存碎片,需要维护一个空闲列表
标记-复制算法是什么?优缺点是什么?
它把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾收集时,遍历
当前使用的区域,把存活对象复制到另外一个区域中,最后将当前使用的区域的
可回收的对象进行回收
- 优点 按顺序分配内存即可,实现简单、运行高效,不用考虑内存碎片
- 缺点 可用的内存大小缩小为原来的一半,对象存活率高时会频繁进行复制
标记-整理算法是什么?优缺点是什么?
在新生代中可以使用复制算法,但是在老年代就不能选择复制算法了,因为老年
代的对象存活率会较高,这样会有较多的复制操作,导致效率变低。标记-清除
算法可以应用在老年代中,但是它效率不高,在内存回收后容易产生大量内存
碎片。标记-压缩算法的最终效果等同于标记-清除算法执行完成后再进行一次
内存碎片整理,因此也可以称为标记-清除-压缩算法,二者的本质区别是前
者是一种移动式的回收算法,后者是非移动式算法。
- 优点 解决了标记-清理算法存在的内存碎片问题
- 缺点 从效率来说标记-整理算法要低于复制算法,移动对象的过程中如果
对象被其他对象引用,则还需要调整引用的地址,移动过程中需要全程暂停
用户应用程序即STW
根节点枚举具体过程是什么?
- 所有收集器在根节点枚举这一步骤都是必须暂停用户线程,查找引用链的
过程还是可以与用户线程一起并发,但根节点枚举必须保证在一个一致性快
照中进行–一致性就是整个枚举期间执行子系统看起来就是冻结在某个时间
点一样 - 不需要检查所有上下文和全局的引用位置,通过一组称为OopMap的数据
结构找到对象引用
安全点的作用是什么?
在特定位置才会进行GC,这些位置就是安全点。强制要求必须执行到安全点才
能够暂停,并且生成OopMap。安全定位置的选取基本上以是否具有让程序长
时间执行的特征为标准进行选定。这个特征就是指令序列的复用,例如方法
调用、循环跳转、异常跳转等,只有具有这些功能的指定才会产生安全点
GC发生时,检查所有线程都跑到最近的安全点的方法有哪些?
- 抢先式中断 首先中断所有线程,如果还有线程不在安全点就恢复线程,
让线程跑到安全点,目前没有虚拟机采用 - 主动式中断 设置一个中断标志,各个线程运行到中断点的时候主动轮
询这个标志,如果中断标志为真,则将自己进行中断挂起,轮询标志的
地方和安全点是重合的
安全区域是什么?
安全点机制保证程序执行时在不太长时间进入垃圾收集过程。但是程序不执
行时,也就是没有分配处理器时间,比如线程处于Sleep状态或Blocked状
态,这时无法响应中断请求。安全区域是指能够确保在某一段代码片段之中
引用关系不会再发生变换,这个区域中任意地方开始垃圾收集都是安全的
记忆集合卡表的作用是什么?
为解决对象跨代引用所带来的问题,垃圾收集器在新生代建立了名为记忆
集的数据结构,用以避免老年代加进GC Roots扫描范围
写屏障的作用是什么?
写屏障可以看成在虚拟机层面对引用类型字段赋值这个动作的AOP切面
并发标记的两种解决方法是什么?
- 增量更新 当黑色对象插入新的指向白色对象的引用关系时,就将这个
新插入的引用记录下来,等并发扫描结束后再将这些记录过的引用关系中
的黑色对象为根,重新扫描。CMS使用这种方式 - 原始快照 当灰色对象要删除指向白色对象的引用关系时,将这个删除
的引用记录下来,并发扫描结束后一记录过的灰色对象为根,重新扫描,
G1和Shenandoah使用这种方式
说一下JVM有哪些垃圾回收器?
用于回收新生代的收集器包括Serial、PraNew、Parallel Scavenge,回
收老年代的收集器包括Serial Old、Parallel Old、CMS,还有用于回收
整个Java堆的G1收集器
垃圾收集器是如何组合的?
Serial收集器是什么样的?
是一个单线程收集器,不仅是只会使用一个处理器或一条收集线程完成收集
工作,更重要的是进行垃圾收集时,必须暂停所有工作线程,直到收集结束
,与Serial Old搭配使用,也是一个单线程收集器。新生代采用标记-复制
算法收集,老年代采用标记-整理算法收集
Serial收集器的优点是什么?
- 简单高效,对于限定单个CPU 的环境来说,Serial收集器由于没有
线程交互的开销,专心做垃圾收集自然可以获得最高的单线程收集效率
,运行在Client模式下的虚拟机是个不错的选择 - -XX:+UseSerialGC 参数可以指定年轻代和老年代都是用串行收集器
- 对于交互性较强的应用而言,这种垃圾收集器是不能接受的
- 如果应用的实时性要求不是那么高,只要停顿的时间控制在N毫秒之内
,大多数应用还是可以接受的
ParNew收集器是什么样的?
实际上是Serial收集器的多线程版本,除了使用多条线程进行垃圾收集外
其余行为与Serial收集器一样。目前与CMS搭配使用。和ParNew的最大区
别是GC自动调节策略,虚拟机会根据系统的运行状态收集性能监控信息
,动态设置这些参数,以提供最优停顿时间和最高的吞吐量
垃圾收集器语境下的并行和并发是什么?
- 并行 多条垃圾收集线程可以同时工作,默认用户线程处于等待状态
- 并发 同一时间垃圾收集器线程与用户线程都在运行
Parallel Scavenge收集器是什么?
新生代并行收集器,追求高吞吐量,高效利用CPU,采用标记-复制算法。
1 | 吞吐量 = 用户线程时间/(用户线程时间+GC线程时间) |
高吞吐量可以高效率的利用CPU时间,尽快完成程序的运算任务,适合后台
应用等对交互相应要求不高的分析任务场景
Serial Old收集器是什么?
老年代单线程收集器,Serial收集器的老年代版本,采用标记-整理算法
Parallel Old收集器是什么?
老年代并行收集器,吞吐量优先,Parallel Scavenge收集器的老年代版本
,采用标记-整理算法
CMS收集器是什么?
老年代并行收集器,以获取最短回收停顿时间为目标的收集器,具有高并发
、低停顿的特点,追求最短GC回收停顿时间,是以牺牲吞吐量为代价来获得
最短回收停顿时间的垃圾回收器。是第一款真正意义支持并发的垃圾收集器
,首次实现让垃圾收集线程和用户线程同时工作,所以整体的回收是低停
顿的。采用标记-清除算法。运作过程:初始标记,并发标记,重新标记,
并发清除
CMS运作过程是什么样的?
CMS的优缺点是什么?
CMS为什么不使用标记-整理算法?
CMS收集器的垃圾收集算法是标记-清除算法,会产生垃圾碎片,如果使用
Compact整理内存的话,但是还要保证用户线程能够继续执行,用户的运
行资源不能受影响,所以标记-清除算法更合适
G1收集器是什么?
Java堆并行收集器,G1收集器是JDK1.7提供的一个新收集器,G1收集器基
于“标记-整理”算法实现,也就是说不会产生内存碎片。此外,G1收集器不
同于之前的收集器的一个重要特点是:G1回收的范围是整个Java堆(包括
新生代,老年代),而前六种收集器回收的范围仅限于新生代或老年代。
运作流程主要包括以下:初始标记,并发标记,最终标记,筛选回收,
G1将整个堆分为大小相等的多个Region(区域),G1跟踪每个区域的
垃圾大小,在后台维护一个优先级列表,每次根据允许的收集时间,
优先回收价值最大的区域,已达到在有限时间内获取尽可能高的回收
效率
详细说一下CMS的回收过程?
CMS(Concurrent Mark Sweep,并发标记清除)收集器是以获取最短回收停
顿时间为目标的收集器(追求低停顿),它在垃圾收集时使得用户线程和GC
线程并发执行,因此在垃圾收集过程中用户也不会感到明显的卡顿
- 初始标记 主要是标记 GC Root 开始的下级(注:仅下一级)对象,这
个过程会 STW,但是跟 GC Root 直接关联的下级对象不会很多,因此这个
过程其实很快 - 并发标记 根据上一步的结果,继续向下标识所有关联的对象,直到这条
链上的最尽头。这个过程是多线程的,虽然耗时理论上会比较长,但是其它
工作线程并不会阻塞,没有 STW - 重新标记 就是要再标记一次。为啥还要再标记一次?因为第2 步并没有
阻塞其它工作线程,其它线程在标识过程中,很有可能会产生新的垃圾 - 并发清除 清除阶段是清理删除掉标记阶段判断的已经死亡的对象,由于
不需要移动存活对象,所以这个阶段也是可以与用户线程同时并发进行的
CMS的问题是什么?
- 并发回收导致CPU资源紧张 在并发阶段,它虽然不会导致用户线程停顿
,但却会因为占用了一部分线程而导致应用程序变慢,降低程序总吞吐量。
CMS默认启动的回收线程数是:(CPU核数 + 3)/ 4,当CPU核数不足四
个时,CMS对用户程序的影响就可能变得很大 - 无法清理浮动垃圾 在CMS 的并发标记和并发清理阶段,用户线程还在继
续运行,就还会伴随有新的垃圾对象不断产生,但这一部分垃圾对象是出现
在标记过程结束以后,CMS无法在当次收集中处理掉它们,只好留到下一次
垃圾收集时再清理掉。这一部分垃圾称为“浮动垃圾” - 并发失败 由于在垃圾回收阶段用户线程还在并发运行,那就还需要预留
足够的内存空间提供给用户线程使用,因此CMS不能像其他回收器那样等到
老年代几乎完全被填满了再进行回收,必须预留一部分空间供并发回收时
的程序运行使用 - 内存碎片问题
详细说一下G1的回收过程?
G1从整体来看是基于标记-整理算法实现的回收器,但从局部(两个Region
之间)上看又是基于标记-复制算法实现的
- 初始标记(会STW):仅仅只是标记一下 GC Roots 能直接关联到的对象
,并且修改TAMS指针的值,让下一阶段用户线程并发运行时,能正确地在可
用的Region中分配新对象。这个阶段需要停顿线程,但耗时很短,而且是借
用进行Minor GC的时候同步完成的,所以G1收集器在这个阶段实际并没有
额外的停顿 - 并发标记:从GC Roots 开始对堆中对象进行可达性分析,递归扫描整个
堆里的对象图,找出要回收的对象,这阶段耗时较长,但可与用户程序并发
执行。当对象图扫描完成以后,还要重新处理在并发时有引用变动的对象 - 最终标记(会STW):对用户线程做短暂的暂停,处理并发阶段结束后仍
有引用变动的对象 - 清理阶段(会STW):更新Region的统计数据,对各个Region的回收价
值和成本进行排序,根据用户所期望的停顿时间来制定回收计划,可以自
由选择任意多个Region构成回收集,然后把决定回收的那一部分Region
的存活对象复制到空的Region中,再清理掉整个旧Region的全部空间。
这里的操作涉及存活对象的移动,必须暂停用户线程,由多条回收器线
程并行完成的
深拷贝和浅拷贝的区别?
- 浅拷贝(shallowCopy)只是增加了一个指针指向已存在的内存地址
- 深拷贝(deepCopy)是增加了一个指针并且申请了一个新的内存,使这
个增加的指针指向这个新的内存,使用深拷贝的情况下,释放内存的时候不
会因为出现浅拷贝时释放同一个内存的错误 - 浅复制:仅仅是指向被复制的内存地址,如果原地址发生改变,那么浅
复制出来的对象也会相应的改变 - 深复制:在计算机中开辟一块新的内存地址用于存放复制的对象
full gc触发条件?
- System.gc()方法的调用
- 老年代空间不足
- 永久代空间不足
符号引用和直接引用的区别?
- 符号引用 符号引用十一组符号来描述所引用的目标,可以是任何形式的字
面量,只要使用时能无歧义地定位到目标即可 - 直接引用 直接引用是直接指向目标的指针、相对偏移量或者一个能间接定
位到目标的句柄,引用的目标必定已经在虚拟机的内存中存在
类加载的过程?
Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转
换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这个过程称
为虚拟机的类加载机制
- 加载 通过一个类的全限定名获取定义此类的二进制字节流,将该二进制
流的静态存储结构转为方法区的运行时数据结构,在堆中为该类生成一个
class对象 - 链接 分为三部分:验证、准备、解析
- 验证 确保Class文件的字节流中包含的信息符合Java虚拟机规范中的全部
约束要求,保证这些信息不回被当做代码运行后危害虚拟机自身的安全,比
如魔数、版本号 - 准备 为类中定义的静态变量分配内存并设置类变量初始值
- 解析 将常量池内的符号引用替换为直接引用的过程
- 初始化 调用类构造器的过程,所有类变量的赋值操作和静态代码块的语
句合并
类加载器有哪些?
通过一个类的全限定性类名获取该类的二进制字节流叫做类加载器,四类加
载器并不是继承关系,不继承自ClassLoader
- 启动类加载器 用来加载java核心类库
- 扩展类加载器 用来加载java的扩展库,继承自ClassLoader
- 系统类加载器 它根据java的类路径来加载类,继承自ClassLoader
- 自定义类加载器 继承自ClassLoader
双亲委派机制是什么?
当一个类加载器收到一个类加载的请求,他首先不会尝试自己去加载,而是将
这个请求委派给父类加载器去加载,只有父类加载器在自己的搜索范围类查
找不到给类时,子加载器才会尝试自己去加载该类,优势是避免类的重复加
载,保护程序安全,防止核心API被随意篡改,这就是沙箱安全机制
怎么打破双亲委派模型?
自定义类加载器,继承ClassLoader类,重写loadClass方法和findClass方
法,单独重写findClass()方法并没有打破双亲委派模型
- JNDI 通过引入线程上下文类加载器,有了线程上下文类加载器,就可以完
成父类加载器请求子类加载器完成类加载的行为。打破的原因,是为了 JNDI
服务的类加载器是启动器类加载,为了完成高级类加载器请求子类加载器(
即上文中的线程上下文加载器)加载类 - Tomcat 应用的类加载器优先自行加载应用目录下的 class,并不是先委派
给父加载器 - OSGi 实现模块化热部署,为每个模块都自定义了类加载器,需要更换模块
时,模块与类加载器一起更换
如何查看JVM 参数默认值?
- jps -v 可以列出正在运行的虚拟机进程,并显示虚拟机执行主类名称以及
这些进程的本地虚拟机唯一ID - 使用 -XX:+PrintFlagsFinal 可以看到 JVM 所有参数的值
- jinfo 可以实时查看和调整虚拟机各项参数
如何排查OOM 问题?
- 增加两个参数 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=
/tmp/heapdump.hprof,当 OOM 发生时自动dump 堆内存信息到指定目录 - 同时jstat 查看监控JVM 的内存和GC 情况,先观察问题大概出在什么区域
- 使用 MAT 工具载入到 dump 文件,分析大对象的占用情况,比如
HashMap 做缓存未清理,时间长了就会内存溢出,可以把改为弱引用 - jmp 用于生成堆转储快照(dump文件)
可视化工具有哪些?
- VisualVM 是功能最强大的运行监视和故障处理程序之一
Jvm 怎么dump 内存?
- dump jvm 内存
1
2dump pid 为 4738 的 java 进程的内存到 app_mem_dump.bin 文件
jmap -dump:format=b,file=app_mem_dump.bin 4738 - dump jvm 线程栈
1
2dump pid 为 4738 的 java 进程的线程栈到 app_thread_dump.txt 文件
jstack 4738 > app_thread_dump.txt - 堆内存溢出时,保存内存快照
1
2-XX:+HeapDumpOnOutOfMemoryError参数表示当JVM发生OOM时,自动生成DUMP文件。
-XX:HeapDumpPath=${目录}参数表示生成DUMP文件的路径,也可以指定文件名称 - VisualVM 是功能最强大的运行监视和故障处理程序之一,可以生成快照
JVM 内存模型?
Java 内存模型(JMM)就是在底层处理器内存模型的基础上,定义自己的多线
程语义。它明确指定了一组排序规则,来保证线程间的可见性,这一组规则被
称为Happens-Before,JMM规定,要想保证B操作能够看到A操作的结果(无
论它们是否在同一个线程),那么A和B之间必须满足Happens-Before 关系
- 单线程规则:一个线程中的每个动作都happens-before 该线程中后续的
每个动作 - 监视器锁定规则:监听器的解锁动作 happens-before 后续对这个监听器
的锁定动作 - volatile 变量规则:对 volatile 字段的写入动作 happens-before
后续对这个字段的每个读取动作 - 线程 start 规则:线程 start() 方法的执行 happens-before 一个启
动线程内的任意动作 - 线程 join 规则:一个线程内的所有动作 happens-before 任意其他线
程在该线程 join() 成功返回之前 - 传递性:如果 A happens-before B, 且 B happens-before C, 那么
A happens-before C
Java 提供了几种语言结构,包括 volatile, final 和 synchronized,
它们旨在帮助程序员向编译器描述程序的并发要求
- volatile - 保证可见性和有序性
- synchronized - 保证可见性和有序性; 通过管程(Monitor)保证一组动
作的原子性 - final - 通过禁止在构造函数初始化和给 final 字段赋值这两个动作的重
排序,保证可见性
synchronized 不保证同步块内的代码禁止重排序,因为它通过锁保证同一时
刻只有一个线程访问同步块(或临界区),也就是说同步块的代码只需满足
as-if-serial 语义 - 只要单线程的执行结果不改变,可以进行重排序
排查OOM 的方法?
- 增加两个参数,当OOM 发生时自动dump 堆内存信息到指定目录
1
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/heapdump.hprof
- jstat 查看监控JVM 的内存和GC 情况,先观察问题大概出在什么区域
- 使用MAT 工具载入到dump 文件,分析大对象的占用情况,比如HashMap
做缓存未清理,时间长了就会内存溢出,可以把改为弱引用
JVM 中的常量池?
JVM常量池主要分为Class文件常量池、运行时常量池,全局字符串常量池,以
及基本类型包装类对象常量池
- Class文件常量池。class文件是一组以字节为单位的二进制数据流,在java
代码的编译期间,我们编写的java文件就被编译为.class文件格式的二进制数
据存放在磁盘中,其中就包括class文件常量池 - 运行时常量池:运行时常量池相对于class常量池一大特征就是具有动态性,
java规范并不要求常量只能在运行时才产生,也就是说运行时常量池的内容并不
全部来自class常量池,在运行时可以通过代码生成常量并将其放入运行时常量
池中,这种特性被用的最多的就是String.intern() - 全局字符串常量池:字符串常量池是JVM所维护的一个字符串实例的引用表
,在HotSpot VM中,它是一个叫做StringTable的全局表。在字符串常量池中
维护的是字符串实例的引用,底层C++实现就是一个Hashtable。这些被维护的
引用所指的字符串实例,被称作”被驻留的字符串”或”interned string”或
通常所说的”进入了字符串常量池的字符串” - 基本类型包装类对象常量池:java中基本类型的包装类的大部分都实现了常
量池技术,这些类是Byte,Short,Integer,Long,Character,Boolean, 另外
两种浮点数类型的包装类则没有实现。另外上面这5种整型的包装类也只是在对
应值小于等于127时才可使用对象池,也即对象不负责创建和管理大于127的这
些类的对象
说一下 JVM 调优的命令?
- jps 显示指定系统内所有的HotSpot虚拟机进程
- jstat是用于监视虚拟机运行时状态信息的命令,它可以显示出虚拟机进程
中的类装载、内存、垃圾收集、JIT编译等运行数据 - jmap:jmap(JVM Memory Map)命令用于生成heap dump文件,如果不使用
这个命令,还阔以使用-XX:+HeapDumpOnOutOfMemoryError参数来让虚拟机
出现OOM的时候·自动生成dump文件。jmap不仅能生成dump文件,还阔以查询
finalize执行队列、Java堆和永久代的详细信息,如当前使用率、当前使用
的是哪种收集器等 - jhat:jhat命令是与jmap搭配使用,用来分析jmap生成的dump,jhat内置
了一个微型的HTTP/HTML服务器,生成dump的分析结果后,可以在浏览器中查
看。在此要注意,一般不会直接在服务器上进行分析,因为jhat是一个耗时并
且耗费硬件资源的过程,一般把服务器生成的dump文件复制到本地或其他机
器上进行分析 - jstack:jstack用于生成java虚拟机当前时刻的线程快照。jstack来查看
各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做什么事情,或
者等待什么资源。如果java程序崩溃生成core文件,jstack 工具可以用来获
得core 文件的java stack 和nativestack 的信息,从而可以轻松地知道
java程序是如何崩溃和在程序何处发生问题
为什么 GC Roots 可以作为根节点
作为GC Roots的节点主要在全局性的引用(例如常量或类静态属性)与执行上
下文(例如栈帧中的本地变量)中。虚拟机、本地方法栈这都是局部变量,某个
方法执行完,某些局部使用的对象可以被回收