从jdk1.3开始,HotSpot为默认的JVM
JVM内存划分简单来讲分为两个区域(两个区域包含六块内存):线程私有内存(每个线程都有,彼此之间完全隔离),线程共享内存(所有线程共享此内存空间,此空间对所有线程可见)
线程私有内存又分为:程序计数器,虚拟机栈(JVM方法内存模型),本地方法栈(本地方法内存模型),HotSpotJVM将本地方法栈与虚拟机栈合二为一(即HotSpotJVM将本地方法和Java方法栈合二为一)
线程私有内存:
一、程序计数器
每一个具体的时刻,只有一个线程强占cpu资源,程序计数器记录该线程执行完成后所在程序行号,方便该线程下次进入cpu轮回
程序计数器是比较小的内存空间,当前线程所执行字节码的行号指示器,分为以下两种:
若当前线程执行的是java方法,计数器记录的是正在执行的JVM字节码的指令地址
若当前正在执行的是JVM方法,则程序计数器为0
clone(),Native本地方法,程序计数器为0
程序计数器是唯一一块不会产生OOM(out of memoryerror)异常的空间
二、虚拟机栈--Java方法执行的内存模型
每个方法存储的同时都会创建一个栈帧存储局部变量表,操作数栈,方法出口等信息。每个方法从调用直到执行完毕的过程,对应一个栈帧在虚拟机进出的过程
生命周期与线程相同:在创建线程的同时,创建此线程的虚拟机栈,线程执行结束,虚拟机栈与线程一起被回收
此区域一共会产生两种异常:
1.若线程请求速度的栈深度大于jvm允许的深度(-Xss设置栈容量),抛出StackOverFlowError异常(例如递归,不停的递归就会不停地创建栈帧,栈的深度就会越来越深,一定在某个时刻栈会溢出,就会抛出异常)
2.虚拟机在进行栈的动态扩展的时候,若无法申请到足够的内存,就会抛出OOM(OutOFMemoryError异常)
栈的空间指的就是局部变量表,存放new的地址,对象引用为4个字节
三、本地方法栈:本地方法(native方法)执行的内存模型,与虚拟机栈完全一样
在我们HotSpot虚拟机中,本地方法栈是和虚拟机栈是同一块内存区域
java内存区域划分>判断对象是否存活>垃圾回收算法>垃圾回收器>jvm性能检测
线程共享内存
四、GC堆
java堆(java Heap)是java管理的最大内存区域,在jvm启动时候创建,所有线程共享此内存
此内存存放的都是对象实例以及数组
java堆是垃圾回收器管理的最主要内存区域。java堆可以处在物理上不连续的内存空间。-Xmx设置堆最大值
-Xms设置堆最小值
若在堆中没有足够的内存完成对象实例分配并且堆无法再次扩展的时候,抛出OOM异常
五、方法区
用于储存已被jvm加载的类信息,常量、静态变量等数据,在jdk8以前,方法区也叫永久代,
在jdk8之后也称为元空间(Meta Space)
方法区无法满足内存分配需求的时候,抛出OOM
六、运行时常量池
运行时常量池方法区的一部分,存放字面量与符号引用,
字面量:字符串常量(jdk1.7移到堆中)、final常量、基本数据类型的值
符号引用:类、字段、方法的完全限定名、名称、描述符
OOM(内存溢出):内存中的对象确实还应该存活,但由于内存不够用而产生的异常
如何判断内存溢出还是泄露?
加大内存能解决的问题就是内存溢出
内存泄漏:例如死循环不断地add对象,此时无论你内存加多大,都无法解决问题,所以为内存泄露
一般而言:StackOverFlow异常为单线程
多线程为OOM
内存泄漏:无用对象无法被GC
判断对象是否已死?
堆中放的所有实例和数组,在垃圾回收之前,要判断对象是否已死,采用引用计数法
引用计数法:给每个对象附加一个引用计数器,每当有一个地方引用此对象的时候,计数器+1,每当有一个地方引用失效的时候,计数器-1;
在任意时刻,只要计数器值为0的对象就是不能再被使用的,即对象已死。
引用计数法实现简单,判定效率也比较高。python也是用引用计数法来管理内存
但是无法解决循环引用的问题 两个对象相互引用,你中有我,我中有你
Person per1 = new Person(); Person per2 = new Person(); per1.instance = per2; per2.instance = per1; per1 = per2 = null; 导致无用对象仍然判断存活而无法被回收
java并未采用此算法
java采用的是可达性分析法算(C#):
核心思想:通过一系列"GC ROOTS"的对象作为起点,从这些节点开始向下搜索,搜索走过的路径,称为"引用链",
当一个对象到任意一个GC ROOTS对象没有任何的引用链相连的时候(从GC ROOTS到对象不可达),证明对象已死
java中能作为GC ROOTS的对象包含以下四种:
1、虚拟机栈中引用的对象
2、本地方法栈中引用的对象
(1、2简称栈中引用的对象)
3、类静态变量引用的对象
4、常量引用的对象
任意一个对象到达GC ROOTS有路径则存活,没有路径则是死的
*************jdk1.2之后对引用的概念做了扩充**************
将引用分为(强度逐渐递减):
强引用:指的是程序中普遍存在,类似于 Object obj = new Object();在jvm中只要强引用还存在,垃圾回收器永远不会回收此对象实例,即便抛出OOM异常,也无法回收被强引用指向的对象
软引用:用来描述一些有用但不必须的对象(缓存),由于仅被软引用指向的对象,在系统即将发生内存溢出之前(OOM异常),会将所有软引用对象进行垃圾回收,若内存够用,这些对象仍然保留,不会被回收
在jdk1.2之后提供SoftReference(类)来实现软引用,软引用用来描述有用但不必须的对象
弱引用:仅被弱引用关联的对象最多只能生存到下一次GC开始之前,当垃圾回收器开始工作的时候,无论当前内存是否够用,都会回收掉仅被弱引用关联的对象,在jdk1.2之后,使用WeakReference
来实现弱引用
虚引用:幽灵引用或幻引用,一个对象是否有虚引用的存在,完全不会对对象的生存时间产生任何影响,也无法通过虚引用来取得一个实例对象
作为一个对象设置,虚引用唯一的一个目的,就是在这个对象被GC之前,收到一个系统的通知。在jdk1.2之后,提供phantomReference来描述虚引用(由此可以知道哪些类被回收)
对象的自我拯救--finalize
protected void finalize() throws Throwable{ }
在可达性分析算法不可达的对象也并非非死不可,所有不可达对象处于“缓刑”阶段
要宣告一个对象的彻底死亡,需要经历两次标记的过程:
1.若对象在可达性分析之后发现GC ROOTS不可达,此对象会进行第一次标记并且进行一次筛选过程
筛选的条件是此对象是否有必要执行finalize(),当对象没有覆写finalize方法或finalize()方法已经被jvm调用过,jvm会对此对象彻底宣判死亡
筛选成功(对象覆写了finalize方法并且未被调用过),会将此对象放入F-Queue,如果此对象在finalize()成功自救(此对象与GC ROOTS建立联系),则对象会在第二次标记时
被移除回收集合,成功存活;若对象在finalize中仍然与GC ROOTS不可达,宣告死亡
ffinalize在jdk1.9之后就不建议使用(jdk1.9@Deprecated)
finalize、finally、final的关系?
finally 一定会执行,若finally中含有return,则以它为主,若try中含有return则,先执行finally,再返回try中的缓存
final终接器,类中表不可继承,方法表不可重载,属性表示常量不可修改会进入常量池
-Xss(设置栈的大小)128k
-Xms(设置堆的最小值)10m
-Xmx(设置堆的最大值)
-Xmn(设置新生代内存大小)
回收方法区
方法区回收主要回收两部分内容:废弃常量和无用的类
String Str = "abc",直接赋值会进入常量池将其覆用 常量回收:无任何引用指向,并且内存不够用,会进行常量回收
判断该类为无用类,必须满足以下三个条件:
1.该类的所有实例都已经被回收(java堆中不存在该类的任何实例)
2.加载该类的类加载器已经被回收
3.该类的Class对象没有在任何其他地方被引用,也无法通过反射访问该类的所有内容
jmap java
垃圾回收算法:将堆空间分为新生代和老年代,对象默认在新生代产生,新生代对象朝生夕死,新生代对象的存活率很低(<= %2)
java采用分代回收算法,新生代采用复制算法,老年代采用标记--整理算法
新生代
新生代GC比老年代GC速度快10倍以上、发生频率高很多
新生代采用复制算法,为何老年代不采用复制算法,因为老年代对象存活率很高,若采用复制算法,复制开销远高于新生代,不适合复制算法
jdk内置工具使用:
jps 查看java进程型号及id编号
jmap 打印java共享对象内存的映射和堆内存细节
jstack java当前的线程快照(可用于查看死锁)
jvm
volatile
-可见性
-禁止指令重排(Double-Check-Singleton)
int x = 0;
int y = 1;
--------volatile boolean flag = true;--------
x = x+1;
y = y+2;
禁止指令重排:
1.volatile代码既不会提前也不会滞后
2.volatile之前的代码一定全部执行完毕,volatile之后的所有代码一定还未开始
线程可见三个条件:原子、可见、有序
类在方法区 public class JVM { public static void main(String[] args) { //主方法主线程,对应一个虚拟机栈,栈里面放了一个jvm对象引用和a(基本数据类型的局部变量),堆里面放的对象实例 //堆上放了所有对象实例和数组,堆的内存不够时,不够产生对象并且也无法再扩展时候,抛出OOM异常, // 所有线程共享此内存 //方法区:存放已被jvm加载的类的信息及静态变量,常量 JVM jvm = new JVM(); int a = 10; //10的整形常量在常量池 } } 对象产生:符号引用->类->对象实例 Test test = new Test();符号引用(包名和类名)从方法区加载出来
public class JVM { private int length = 1; public void stackTest(){ length++; //-Xss128k,一个整形四个字节,栈帧能开996个 //默认为29570 能递归万次数以上 stackTest(); } public static void main(String[] args) { JVM test = new JVM(); try{ test.stackTest(); } //此时为error catch (Throwable e){ e.printStackTrace(); System.out.println("栈的深度为:"+test.length); //将vm options调节为-Xss128k } } }
验证jvm是否采用引用计数法 class _Test{ private Object instance; private static int _1MB = 1024*1024; //一个_Test对象至少需要2M的内存 private byte[] bigSize = new byte[2*_1MB]; public static void main(String[] args) { //+1 _Test _test = new _Test();//GC Roots _Test _test1 = new _Test();//GC Roots //开了4M内存 //循环引用 _test.instance = _test1;//+1 _test1.instance = _test;// //_test = _test1 = null;//将最外层引用置为空,1+1-1=1 //将GC Roots置为空,则不可达,被回收 System.gc();; //-XX:+PrintGC 打印具体的垃圾回收日志 在编辑器里面的vmoptions里面配置 //[GC (System.gc()) 6717K->824K(123904K), 0.0026948 secs],说明这两个我对象已死,内存被回收,但理论上应该是在存在的 //因为并没有置为空,而是1,理论上对象不应该死 //将 _test = _test1 = null;注释掉,[GC (System.gc()) 6717K->4864K(123904K), 0.0028798 secs], //说明对象没有被回收,说明jvm并没用采用引用计数法来管理内存 } }
//finalize() class _Test2{ private static _Test2 test2;//静态属性属于 GC Roots public void isAlive(){ if(test2 != null){ System.out.println("I am alive"); } } @Override protected void finalize() throws Throwable { super.finalize(); System.out.println("finalize method execute"); test2 = this; } public static void main(String[] args) { test2 = new _Test2();//GC Roots test2 = null; System.gc(); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } if(test2 != null){ test2.isAlive(); } else{ System.out.println("I am dead"); } //------------------------- test2 = null; System.gc(); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } if(test2 != null){ test2.isAlive(); } else{ System.out.println("I am dead"); } } }