@yulongsun
2018-05-30T16:46:59.000000Z
字数 7725
阅读 1131
面试宝典
局部变量表
。 操作数栈
: 栈上分配
: 注意
:
- 当线程栈深度超过 虚拟机栈所允许的最大深度的时候,就会抛StackOverflowError.
- 如果虚拟机栈扩展时,无法获取到足够的内存,就会抛OOM.
java方法
,那么存储的是正在执行的虚拟机的字节码指令地址.native方法
,那么存储的时undefined.http://icyfenix.iteye.com/blog/715301
java堆中几乎存放了所有的对象,垃圾回收器在回收之前需要确定哪些对象还”活着“,哪些对象已经”死了“
- 真正宣告一个对象死亡,至少需要经历`两次`标记过程:
1. 如果对象在可达性分析后,没有于GC ROOTS相关联的引用,那么它将会被第一次标记,并进行筛选。
筛选的条件是:判断有没有必要之行finalize方法。
- 被覆盖过finalize的方法或者finalize被调用过的方法 会被认为没有比较之行。
2. 如果被认为有必要执行,那么这个对象将会被放置在F-Queue的队列中。JVM会创建一个低优先级的Finalizer线程去执行它。
finalize()方法是对象逃离死亡命运低最后一次机会。
稍后GC会对F-Queue队列中的对象进行第二次标记。如果对象[在finalize中重新与引用连上的任何一个对象建立关联],那么将会被移除清除队列。如果这个时候还没逃脱,那基本上就会被回收了。
http://www.jianshu.com/p/fa0085a0cdf9
优点:实现简单,效率高
缺点: 无法处理循环引用
原理:给对象添加一个引用,每当有一个地方引用这个对象的时候,计算器就+1,当引用失效的时候,引用就-1,当引用计算器=0的时候表示该对象不被引用。
需要注意的是:引用计数器很难解决对象之间循环引用,所以主流java虚拟机没有使用引用计数器来管理内存。
- 强引用:GC永远不会回收引用对象。
- 软引用SoftReference:在内存溢出之前,会进行第二次回收,如果回收之后还没有足够的内存,才会抛出OOM。
- 弱引用WeakReference:引用对象只能生存到下一次GC之前。
- 虚引用(幽灵引用、幻影引用)PhantomReference:唯一目的就是GC在回收时收到一个系统通知。
finalize只会被执行一次。
一个对象真正被回收需要经历两次标记过程:
如果一个对象在可达性分析后没有与GC ROOT相连接的引用链,那就被被第一次标记并且进行一次筛选:
如果finalize没有被重写或调用过,虚拟机会让认为“没必要执行”。
否则,会执行finalize方法
finalize:
如果调用finalize方法,那么这个对象将会被放到一个队列里面[虚拟机建立并执行Finalizer线程执行调用],等待被回收。
finalize是对象逃脱死亡命运的最后机会。只要与引用链上的任何一个对象建立链接即可。
- 回收内容:
- 废弃常量:没有地方引用
- 无用的类:
需要**同时满足**以下条件:
1. java堆中没有这个类的实例;
2. 加载该类的ClassLoader已经被回收;
3. 该类的Class字节码没有任何地方被引用。没有任何地方通过**反射**调用该类的方法。
- 最基础/最容易的算法
- 算法:分为两个阶段:标记阶段和清除阶段。
- 标记阶段:就是标记出所有要被回收的对象。
- 清除阶段:回收被标记的要被回收的内存区域。
- 最大缺点:容易产生内存碎片。
- 为了解决Mark-Sweep算法的缺点。
- 算法:将内存按容量划分成大小相等的两块,每次只使用其中的一块。
当内存用完了,就将还存活的对象复制到另一块内存上,然后将已使用的内存空间一次性清除掉,这样一来就容易出现内存碎片。
- 优点:算法简单,运行高效而且还不容易出现内存碎片。
- 缺点:内存使用代价高,因为能够使用的内存只有原来的一半。
- 为了解决Copy算法,充分利用内存空间。
- 算法:标记阶段和Mark-Sweep算法一样,但是完成标记之后,他不是直接清除被标记的内存区域,而是将存活对象往一端移动,然后清除端外的区域。
- 目前大多数jvm采用的算法
- 算法核心思想:根据对象的存活周期将内存划分为若干个区域。
一般情况下将堆内存分为:新生代和老年代。
- 新生代特点:每次内存垃圾回收时,有大量内存对象要被回收。
- 老年代特点:每次垃圾回收时,只有少量对象需要被回收。
- 目前大多数垃圾收集器:
- 对于新生代都采用Copying算法。
因为新生代每次都有大量对象被回收,也就是说复制的次数较少,但是实际中并不是按1:1来划分新生代的内存空间。
一般将新生代划分为一块较大的Eden区和两块较小的Survivor区(一般为8:1:1),每次使用Eden区和一块Survivor区。当进行回收时,一般都将Eden区和Survivor中还存活的对象复制到另一块Survivor中,然后清除Eden区和刚才使用过的Survivor区。
- 对于老年代都采用Mark-Compact算法。
因为老年代每次回收都只有少量对象回收。
参考资料:
https://www.ibm.com/developerworks/cn/java/j-lo-JVMGarbageCollection/
复制
算法.就是Serial收集器的多线程版本.
概念
:吞吐量 = 代码运行时间/(代码运行时间+垃圾回收时间)概念
:
并行:指多条垃圾线程在并行工作,用户线程处于等待状态.
并发:垃圾收集线程和用户线程同时运行,垃圾线程在另一个CPU上运行.
标记-整理
算法.标记-整理
算法.Concurrent Mark Sweep
标记-清理
算法.特点:
优点:并发收集、低停顿.
1. 面向`服务器端`."标记-整理"
2. 分代收集:不需要配合其他GC收集器就能管理整个GC堆。
3. 空间整合
4. 可预测的停顿
- 1. 对象优先在Eden区分配
- 2. 大对象直接进入老年代
- 3. 长期存活的对象进入老年代
加载、验证、准备、初始化、卸载 这五个步骤的顺序是确定的.
但是,解析 这一步可能在初始化之后完成,这是为了指出Java语言的运行时绑定.
什么情况下需要开始类加载的第一个阶段:加载?
1)new实例化对象的时候、读取或设置一个静态字段的时候、调用一个类的静态方法的时候
2)反射调用的时候.
3)初始化一个类的时候,如果发起其父类还没有被初始化的的时候,先触发父类的初始化方法
4)
5)
https://my.oschina.net/winHerson/blog/114180?p=%7B%7BtotalPage%7D%7D
类加载的过程:加载、验证、准备、解析、初始化
1)加载:
查找并加载类的二进制数据
2)链接:
验证:确保被加载的类符合JVM规范、没有安全性方面的问题.
准备:为静态变量分配内存,并将其初始化为默认值.
解析: 把虚拟机常量池中的符号引用转换为直接引用.
3)初始化
为类的静态变量赋予正确的初始化值.
在加载阶段,虚拟机主要完成以下3件事:
1. 通过一个类的全名获取定义此类的二进制字节流;
2. 将这个字节流所代表的静态数据结构转换成方法区的运行时数据结构;
3. 在内存中生成一个代表该类的Class对象,作为方法区这个类的各种数据的访问入口.
类加载器类型:
常用的系统提供的类加载器:
双亲委派模型
双亲加载模型的工作过程:
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求时,自加载器才会尝试自己去加载.
为什么要使用双亲加载模型?双亲加载模型的好处?
保证Java核心库的安全性。
(如果用户自己编写一个java.lang.Object的类,并放在程序的ClassPath中,那系统中将会出现多个不同的Object类,Java类型体系中最基础的行为也就无法保证)
资料:
http://www.cnblogs.com/sunniest/p/4574080.html
http://blog.csdn.net/huachao1001/article/details/52297075
双亲委派模型的实现
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
//1.检查请求的类是否已经被加载过了
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
// 如果父类为null,调用父类的加载器
c = parent.loadClass(name, false);
} else {
//如果父类不为null,则调用bootstap的类加载器
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
//如果父类加载器抛出ClassNotFoundException
//说明父类加载器无法完成加载请求
}
if (c == null) {
// 在父类加载器无法加载的时候
// 再调本身的findClass方法来进行类加载
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
- 内存模型的主要目标:定义程序中各个变量的访问规则。
- 线程对变量的所有操作(读取、赋值等)都在工作内存中进行,不能再主内存中操作。
- 线程间变量值的传递均需要通过主内存来完成。
- 虚拟机可能会让工作内存优先存储于寄存器和高速缓存中,因为程序运行时主要访问读写的是工作内存。
1,当一个变量的定义为volatile变量之后,此变量对所有线程是可见的。
- 基于volatile变量的运算在并发下是安全的???
java里面的运算并非原子操作,所以volatile变量在并发条件下也是不安全的。
2. 禁止指令重排序优化。
PSYoungGen/PSOldGen/PSPermGen区别
PS是Paraller Scavenge的简称。
PSYoungGen:新生代
PSOldGen:老年代
PSPermGen:永久代