1. what is jvm?
jvm大家了解到的Java Virtual Machine。java的跨平台,一次编译,到处执行。每一种操作系统,执行相关程序的时候,因为操作系统环境的不同,会造成代码不能跨平台执行。而java可以做到,原因在哪里?就在于不同操作系统有不同版本的jvm。
语言的执行过程:
源代码(.java)----->编译(字节码 .class)----->解释(成为机器码,01010100110)---->机器码
jvm发展史
1、Sun Classic
只能使用纯解释器来执行java代码,若要使用jit编译器则要使用第三方外挂,且使用了jit则解释器就不执行工作了,此时的编译器不智能,编译器不得不对每一个方法、每一行代码都进行编译,而无论它们执行的频率是否具有编译的价值。
2、Exact Vm
支持编译器和解释器混合工作模式,使用了准确试内存管理虚拟机可以知道内存中,某个位置的具体数据类型 由于使用了准确式内存管理,Exact VM可以抛弃以前Classic VM基于handle的对象查找方式每次定位对象都少了一次间接查找的开销,提升执行性能。
3、Sun HotSpot VM
(1)、可以通过执行计数器找出最具有编译价值的代码,根据执行计数器判断是否达到阈值,没达到就解释执行,否则提交编译请求通知jit编译器以方法为单位进行编译
(2)、通过编译器与解释器恰当地协同工作,可以在最优的程序响应时间与最佳执行性能中取得平衡,即编译的时间压力也会相对减少,这样有助于引进更多的代码优化技术输出质量更高的本地代码
jvm的内存区域划分
1、运行时区域划分
jvm 运行时内存区域分为两种,线程隔离和线程共享
线程隔离分为:栈和程序计数器
线程共享分为:堆和方法区
各个区域介绍
1、程序计数器
是当前线程所执行的字节码行号指示器,字节码解释器工作就是通过改变计数器的值来选取要执行的字节码指令
每条线程都需要有一个独立的程序计数器,各个线程之间计数器互不影响,独立存储
如果线程正在执行的是Native方法,这个计数器值则为空
2、栈
java虚拟机栈、本地方法栈
java虚拟机栈为虚拟机执行java方法(字节码)服务,
本地方法栈为虚拟机使用到的Native服务
每个方法执行的同时会创建一个栈帧用于存储局部变量表,操作数栈,动态链接,方法出等信息,每个方法从调用到执行完成就是一个栈帧的入栈到出栈的过程局部变量表所需的内存空间在编译期间完成分配,其中64位的long和double类型的数据会占2个局部变量空间,其余的数据类型只占用1个。当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。
数栈也要操作栈,主要是在方法计算时存放的栈
3、堆
堆就是存放对象实例的几乎所有的对象实例都在这里分配内存
Java堆是垃圾收集器管理的主要区域;内存回收的角度来看Java堆中还可以细分为:新生代和老年代;新生代细致一点的有Eden空间、From Survivor空间、To Survivor空间。这两块survivor空间大小一致。
java堆的内存空间可以是固定的也可以是可扩展的通过通过**-Xmx设置最大内存和-Xms设置初始内存**)
例如java -Xms10m -Xmx100m Hello
4、方法区
方法区又叫静态区:用于存储已被虚拟机加载的类信息,常量池,静态变量,即是编译器编译器编译器编译后的代码等数据别名叫non-heap非堆
java虚拟机中,方法区除了不需要连续的内存和选择固定大小或可扩展外还可以选择不实现垃圾回收
这区域的内存回收目标主要是针对常量池的回收和对类型的卸载,条件相当苛刻。
在jdk1.7中永久代的配置参数-XX:PermSize5m(初始化永久代内存大小),-XX:MaxPermSize10m(最大永久代内存大小)
在jdk1.8中Metaspace的配置参数:-XX:MetaspaceSize=10m(初始化大小),-XX:MaxMetaspaceSize=10m(最大大小)
java中的常量池技术,是为了方便快捷地创建某些对象而出现的,当需要一个对象时,就可以从池中取一个出来(如果池中没有则创建一个),在需要重复创建相等变量时节省了很多时间
异常oom
程序计数器
没有指定任何OutOfMemoryError情况
java虚拟机栈\本地方法栈区域
如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;如果扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常
堆
如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常
报错后dump出信息: -XX:+HeapDumpOnOutOfMemoryError
-Xms5m -Xmx5m -XX:+HeapDumpOnOutOfMemoryError
方法区
当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常
对象是的引用和可达性分析
1、对象的四类引用
java的引用分为 :强引用、软引用、弱引用、虚引用四种引用强度依次减弱
强引用在程序中的正常创建对象的引用,垃圾回收器不会回收强引用对象
2、软引用 有用但非必须地对象jdk中提供了SoftReference类来实现软引用;系统在发生内存溢出异常之前,会把只被软引用的对象进行回收。
用途?可以做缓存。
3、弱引用 非必须对象了WeakReference类来实现软引用,比软引用弱一些;垃圾回收不论内存是否不足都会回收只被弱引用关联的对象。
4、虚引用 对被引用对象的生存时间不影响;无法通过虚引用来取得一个对象实例;为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知;jdk提供PhantomReference类来实现虚引用
例如
/**
* Java引用分为:强引用>软引用>弱引用>虚引用
*
*/
public class ReferenceTest {
public static void main(String[] args) {
System.out.println("===========强引用========");
//强引用
Person p = new Person();
System.gc();//手动执行垃圾回收
System.out.println(p);
//软引用
System.out.println("===========软引用========");
SoftReference<Person> sp = new SoftReference<Person>(new Person());
System.gc();
System.out.println(sp.get());
System.out.println("---------------软引用在内存溢出的表现-------------------------");
try {
List<HeapOOM.OOMObject> list = new ArrayList<HeapOOM.OOMObject>();
while (true) {
list.add(new HeapOOM.OOMObject());
}
} finally {
System.out.println("内存溢出之后的软引用是否存在:");
System.out.println(sp.get());
System.out.println("---------------软引用在内存溢出的表现-------------------------");
//弱引用
System.out.println("===========弱引用========");
WeakReference<Person> wp = new WeakReference<Person>(new Person());
System.gc();
System.out.println(wp.get());
System.out.println("===========虚引用========");
//虚引用
ReferenceQueue<Person> referenceQueue = new ReferenceQueue<Person>();
Person person = new Person();
PhantomReference<Person> pp = new PhantomReference<Person>(person, referenceQueue);
person = null;
System.out.println(referenceQueue.poll());
System.gc();
System.out.println(pp.get());
try {
//gc后等1秒看结果
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(referenceQueue.poll());
System.out.println("===================================");
Properties properties = System.getProperties();
for (Map.Entry<Object, Object> me : properties.entrySet()) {
System.out.println(me.getKey() + "=" + me.getValue());
}
System.out.println("=================获取传递个JVM的参数=========================");
System.out.println(System.getProperty("zookeeper.root.logger"));//-Dzookeeper.root.logger=INFO,stdout,R
Person p1 = new Person();
Person nP = p1;//
p1 = null;
System.out.println(nP);
}
}
}
class Person {
String name = "张三";
@Override
public String toString() {
return name;
}
}
引用的可达性分析
判定一个对象不被引用的方法就是可达性分析在可达性分析之前还有一种方式判定对象是否被引用====》引用计数器法
引用计数器法
引用计数算法基本思想:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。
可达性分析
通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连(即不可达)时,则证明此对象是不可用的
常见的gc root对象:
- 虚拟机栈(栈帧中的本地变量表)中引用的对象。
- 方法区中类静态属性引用的对象。
- 方法区中常量引用的对象。
- 本地方法栈中JNI(Java Native Interface即一般说的Native方法)引用的对象。
垃圾回收的执行过程
如果我们判定一个对象不可达,就应该将该对象进行gc垃圾回收掉,但是jvm在进行垃圾回收之前会对这些对象进行一轮的筛选,如果相关对象此时重新和引用链的对象建立起了关联,那么是可以逃脱被gc掉的命运,但是不是所有的对象都有着特权,只有我们在编写类的时候,复写Object类中的一个方法finalize(),也就是说在该方法重重新建立了引用,就可以起死回生
不可达的对象真正死亡需要两次标记:
当不可达时标记第一次标记,当对象覆盖finalize()方法并且finalize()方法没有被虚拟机调用过,此对象将会放置在一个叫做F-Queue的队列之中,稍后由一个由虚拟机自动建立的、低优先级的Finalizer线程去触发这个方法,但并不承诺会等待它运行结束再执行垃圾回收。
finalize()方法是对象逃脱死亡命运的最后一次机会,稍后GC将对F-Queue中的对象进行第二次小规模的标记,如果对象要在finalize()中重新与引用链上的任何一个对象建立关联那么他被移除出“即将回收”的集合,否则就被回收了。
例如
public class FinalizeObj {
public static FinalizeObj obj;
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("FinalizeObj finalize called !!!");
obj = this;//在finalize方法中复活对象
}
@Override
public String toString() {
return "I am FinalizeObj";
}
public static void main(String[] args) throws InterruptedException {
obj = new FinalizeObj();
obj = null; //将obj设为null
System.gc();//垃圾回收
System.out.println(":-------------------");
Thread.sleep(1000);//
if(obj == null) {
System.out.println("obj is null");
} else {
System.out.println("obj is alive");
}
System.out.println("第2次调用gc后");
obj = null;//由于obj被复活,此处再次将obj设为null
System.gc();//再次gc
Thread.sleep(1000);
if(obj == null) {
//对象的finalize方法仅仅会被调用一次,所以可以预见再次设置obj为null后,obj会被垃圾回收,该语句会被调用
System.out.println("obj is null");
} else {
System.out.println("obj is alive");
}
}
}