jvm整理
1.java文件编译过程
所有的.java源文件javac编译为.class文件后由Jvm编译为cpu认识的文件如0100101
java源文件代码——>字节码——>机器码
2.java为什么为跨平台语言
java文件通过jvm实现跨平台通用,不同的操作系统有不同的jvm实现对字节码的编译,通过这些jvm可以使同一份java源码在不同 操作系统去调用相同的接口,进而实现跨平台的属性,java跨平台的本质就是jvm跨平台。
其它语言也可实现跨平台,但大多在代码层面实现跨平台,在不同的操作系统下的代码也各有不同。
3.Jdk与Jre与Jvm的区别
jdk中包含了jre,jre中包含的jvm
4.Jvm结构(jdk1.6)
方法区与堆为线程共享数据区(这两个区域主要存储的是数据)
程序技术器、本地方法栈、虚拟机栈为线程私有的(每个线程都有自己的这三个区域)
4.1方法区(永久代存储区域)
jdk1.8后方法区就不存在了,更改为元空间存储到直接内存(堆外内存)中
方法区内主要存储类的信息(类中的变量,常量,方法等),编译后的代码(.class字节码)
方法区作为线程共享的区域,允许所有任务执行时进行调用
4.2 java堆(内存管理区域——GC堆)
堆中主要存储New的对象
4.2.1堆分为新生代与老生代(永久代为方法区)
4.2.1.1新生代
新生代分为Eden与两个Survivor区域,两个Survivor分别为From Survivor、To Survivor
比例为Edem : From Survivor : To Survivor = 8 : 1 : 1 (默认分配比例,可以通过参数 –XX:SurvivorRatio 来设定)
两个Survivo区会进行复制保留算法,所以总有一个区的,所以新生代的最大空间利用率为90%
4.2.1.2老生代
而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须采用“标记-清理”或者“标记-整理”算法。
4.2.1.3 对象在堆中存储的过程
新生代与老生代内存分配比例为1:2
新生代中的对象大部分存活时间短(比如有些对象调用次数为0或引用后就再无作用,就判定这些对象死亡)
一个对象被创立时一般会直接存储在新生代的Edem区中,特殊情况别大对象(需要大量连续的存储空间的对象)直接存储老年代中
对于新生代中存活的对象(经过内存回收(Minor-CG)处理后保留的对象),年龄+1,若此时Survivor区域有空间可以保留这个对象,对象保存到Survivor中,若Survivo中无空间,则在这个对象年龄大于Survivor中其它对象之一时进行替换,当对象年龄大于15时(晋升到老年区的年龄可以设置,默认为15),这个对象便可存储到老年代中。
4.3程序计数器
程序计数器的分配内存较小,主要作用为线程进行标记并保存当前线程的执行字节码的位置,Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,一个处理器都只会执行一条线程中的指令,当处理器切换其它线程执行时,被切换的线程中的程序计数器就会保存这个线程当前执行的字节码位置,当切换到这个线程恢复执行时,会通过程序计时器保留的字节码位置继续执行,这样会更有利于实现资源的利用(每个线程都有属于自己的程序计数器)
4.4本地方法栈
本地方法栈与 Java 虚拟机栈类似,它们之间的区别只不过是本地方法栈为本地方法服务,保存的是本地方法要执行所需的必要参数。本地方法一般是用其它语言(C、C++ 或汇编语言等)编写的,并且被编译为基于本机硬件和操作系统的程序,对待这些方法需要特别处理。本地方法执行是在os中执行的,并非在JVM中执行的,所以使用的是os的程序计数器而非JVM的程序计数器。本地方法栈只是存储了线程要运行这个方法的必要信息,比如出口,入口,动态链接,局部变量表,操作数栈等。本地方法栈占用内存由本地方法决定,固定无变动。
4.5java虚拟机栈
java虚拟机栈会为用户编写的每一个方法执行时创建一个栈帧,用于保存局部变量表、操作数栈、动态链接、方法出口信息。
栈帧结构:
1.局部变量:8个基本结构类型、对象的引用地址、returnAddress(其值指向字节码的指针)
2.操作数栈:进行具体操作,将操作后的结果转存到局部变量表中去,比如一个局部变量为a=8*3,此时就会在操作数栈中进行操作后 得出值,然后将值赋予a,后再将a存放到局部变量表中。
3.动态链接:在方法中存在其它的方法链接比如xxService.add(),此时需要连接到其它的类中的方法去,这就是动态链接。
4.出口:程序正常执行时为return,执行异常时为抛出的异常。
PS:当一个方法执行时调用其它方法,java也会为被调用的方法创建一个栈帧。
当方法进行递归时,也会创建栈帧
5.JVM垃圾处理系统(内存回收机制)
java程序执行过程中会不断产生大量的垃圾垃圾但内存的空间时固定的,进行垃圾回收处理也就是内存回收——GC
5.1 垃圾回收机制(GC)
在程序执行过程中,对于一些已经死去的对象(没有引用指向的对象),对于程序来说他们就是内存垃圾,GC会不定时的去清理这些垃圾,不可达的对象会立马被回收(根搜索无法达到的对象),在代码层面可以使用System.gc(),实现建议执行垃圾回收处理,但是否执行不可得知,只是”建议“执行,这也是垃圾回收机制的一大缺点。
5.2 垃圾的确定方法(搜索垃圾方法)
5.2.1 引用计数法
在对象被创立时,会给对象实行引用计数,当对象被引用一次,计数+1。当计数为0时,就确定这个对象为垃圾,进行回收
缺点:当一个对象与另一个对象循环引用时,引用计数法无法实行标记回收,例如:
void funD(){
Xx a=new Xx();
Xx b=new Xx();
object.a=b;
object.b=a;
a==null;
b==null;
return 1;
}
funD();
当执行完funD时按理此时对象a,b将不会再进行引用,且此时a,b皆为空,应该进行回收处理,但它们互相引用将使他们的计数不为0,无法进行回收。
5.2.2可达性分析算法
由GC Roots对象为起点进行搜索(由对象组成的树结构,GC为根节点),通过搜索,若GC对象与其它对象无法达到,则对这些对象进行标记第一次,再次对标记第一次对象进行搜索,若这些对象覆盖了finalize 方法,则不对这个对象进行回收,若无finalize 方法覆盖,标记这个对象为垃圾。
PS:finalize 方法只可使用一次,若通过finalize 方法保留下的对象,再次经历可达性算法搜索时,也会被标记为垃圾。
5.3方法区的回收(永久代对象回收)
主要回收废弃的一些常量与不再使用的类
常量的回收:若常量不存在引用,若在垃圾回收器在回收时判断有必要实现对这个常量进行回收时,就会对这个常量进行回收。
类的回收满足以下三个条件:
1.这个类创建的实例都已被回收
2.这个类不会再被引用
3.加载该类的ClassLoader已被回收
5.4java中类的引用
5.4.1强引用
例如:Object obj=new Object();
作为强引用,该对象或该变量不希望被回收
5.4.2软引用
利用SoftReferencesf 类实现弱引用
例如:Object obj=new Object();
SoftReferencesf = new SoftReference(obj);
obj = null; // 使对象只被软引用关联
作为软引用,在内存不足及内存空间接近临界值时,会对该对象实行回收
5.4.3弱引用
利用WeakReference类实现
例如:Object obj = new Object();
WeakReferencewf = new WeakReference<>(obj);
obj = null; // 使对象只被弱引用关联
作为弱引用,如果被GC发现会立即进行回收。
5.4.4虚引用
利用PhantomReference类实现
例如:Object obj = new Object();
PhantomReferencepf = new PhantomReference(obj);
obj = null; // 使对象只被虚引用关联
作为虚引用,随时会被GC回收,它可以跟踪对象被GC回收的活动通过判断引用队列中是否有虚引用来了解被引用对象是否被回收
5.5垃圾回收算法
5.5.1标记清除算法
对于需要回收的对象,会对其进行标记,然后进行回收标记的对象的空间。
缺点:回收的效率不稳定,因为标记的对象分布与数量不稳定。
回收后未进行整理,导致碎片化严重
5.5.2标记复制算法
与标记清除法一样,先对可回收对象进行标记,但标记整理法将内存分为两半,将清除后保留的对象整理到另一半内存中
缺点:因为需要有一半的内存空间用于Copying后对象的保留整理,所以这样的回收机制始终会有一半的内存是空的,这样造成了内存效率低。且若保留的对象过多时,Copying的效率也会变低。
优点:对于整理后的内存空间,不易产生碎片
5.5.3标记整理算法
融合了上述两种算法的优点,实现对回收对象的标记、回收、整理。可以实现高效率的内存利用,降低内存碎片化。但缺点是在整理过程中需要大量移动对象,处理的效率较低。
5.5.4分代收集算法
java堆将对象分为两个区储存——老生代与新生代,新生代中的对象存活时间短,老生代中对象存活时间长,在回收过程中新生代中回收的对象通常比老生代中回收的对象多的多。所以在新生代中使用标记复制算法,在老生代中使用标记整理算法。
5.5.4.1老生代—标记整理算法
老生代中的对象通过标记后,在GC进行回收时就会回收被标记的对象
5.5.4.2新生代—标记复制算法
新生代中将内存分为8:1:1的Edem区、From Survivor区、To Survivor区。当GC回收被标记的对象后使用Copying算法,将Edem区与两个Survivor区其中之一存活的对象复制保留在空的那块Survivor区。这也导致两块Survivo区必有一块为空。然后两块区互换,为空的那块Survivo区作为下一次的To区。
5.5.5分区收集算法
分区算法则将整个堆空间划分为连续的不同小区间, 每个小区间独立使用, 独立回收. 这样做的好处是可以控制一次回收多少个小区间 , 根据目标停顿时间, 每次合理地回收若干个小区间(而不是整个堆), 从而减少一次 GC 所产生的停顿。
5.6 GC回收方式
5.6.1部分收集(Partial GC)
新生代收集:Minor GC(复制–>清除–>互换)
在Minor GC过程中需要老年代区做担保,若一块Survivo区不能保存Minor GC存留下的对象,则将溢出内存的对象保存在 老年代区
老生代收集:Major GC
混合收集:Mixed GCG1收集器采用混合收集(分区收集算法)
5.6.2整堆收集(Full GC)
收集整个Java堆和方法区的垃圾收集,回收老年代和新生代,老年代对象其存活时间长,因此 Full GC 很少执行,执行速度会比 Minor GC慢很多。
触发Full GC的条件:
1.执行System.gc();时
2.老年代空间不足
3.老年ne代空间无法为Minor GC做担保时
6.内存分配策略
6.1大对象进老年代
大对象是指需要连续内存空间的对象,最典型的大对象是那种很长的字符串以及数组。经常出现大对象会提前触发垃圾收集以获取足够的连续空间分配给大对象。-XX:PretenureSizeThreshold,大于此值的对象直接在老年代分配,避免在 Eden 和 Survivor 之间的大量内存复制。
6.2长期存活对象进老年代
为对象定义年龄计数器,对象在 Eden 出生并经过 Minor GC 依然存活,将移动到 Survivor 中,年龄就增加 1 岁,增加到一定年龄则移动到老年代中。
6.3一般对象优先在Edem上进行分配
大多数情况下,对象在新生代 Eden 上分配,当 Eden 空间不够时,发起 Minor GC。
6.4动态年龄分配
在经过Minor GC后存活的对象,年龄+1,当年龄到达设定值,对象有新生代转存到老生代
空间分配担保
在Survivor区无法保存Minor GC后存留的对象,则使用老年代来存储这些对象,也被称为担保