1:JVM全称:Java Virtual Machine
- JVM是一种用于计算设备的规范
按照这种规范实现的虚拟机:
- 1:HotSpot
– 其实在我们java1.8中就是使用这个虚拟机
- 2:JRockit
- 3:J9M
- 前面两种都是被oracle收购
2:JVM的体系结构:
- 可以看到JVM是运行在操作系统上面,而于硬件系统进行交互的是操作系统,所以JVM于硬件没有直接的交互
3:根据《Java 虚拟机规范(Java SE 7 版)》规定,Java 虚拟机所管理的内存如下图所示。
1:runtime中灰色是没有回收;橙色有垃圾回收,且99%都在堆heap中
2:其实class文件有相对应的标识,并不是你随便闯一个class就可以被装载
3:类加载器相当于是jvm一个入口,在底层给操作系统进行交互的是执行引擎,这两个也就是出入口
3.1:类加载器:
- 类加载器 负责加载class,class文件在文件开头有特定的文件表示,并且ClassLoader只负责class文件的加载,至于她是否可以运行,则由Execution Engine决定;
- 从原理图可以看到,通过ClassLoader 类加载器加载class文件后生产的CarClass 并不能直接使用,需要new,实例化;并且CarClass 被称为元数据模版;这样结合我们实际使用情况就很容易理解了;
- 简单总结:类加载加载class文件在内存生产这个类的元数据模版,然后我们可以通过这个元数据模版来new实例化不同的对象
类加载器一共有四种:
-
虚拟机自带的加载器
启动类加载(bootstrap) C++:不是java的加载器,比如:在jvm启动的时候jdk那些jar的加载,其实这个是jdk自带,不属于java加载器,防止jdk中的核心被更改,
– bootstrap加载器防止原生jdk被串改,自己创建个java.lang包,创建String类;编写输出的主方法:是会看到报错:
找不到main函数;
– 原因:在这个主函数启动的时候,先是找的是启动类加载器找的String;java原生的在启动器加载器加载;像我们这样想要篡改原生的String是无法进行
同时这个就是java的沙箱安全机制; -
扩展类加载器(Extension) Java
应用程序类加载器(AppClassLoader)JAVA:也叫系统类加载器,加载当前应用的classpath的所有类 -
用户自定义加载器:JAVA.lang.ClassLoader,用户可以定制类的加载方式
类加载器的架构图:
3.2:Execution Engine 执行引擎:
- 与类加载器(入口)相反的就是出口:Execution Engine 执行引擎:
- 执行引擎负责解释命令,提交给操作系统执行
3.3:Native Interface本地接口与Native Method
- 这俩可以一起
例子:
如果执行:new Thread().start();
执行后线程启动没启动?:线程是操作系统去启用,所以答案是不确定 - 查看start()方法源码:
- 可以看到 相关操作 肯定是在start0();
- 进入start0()
- 可以看到这个方法无方法体,且是被标识的是native
- native标识:代表这个方法是本地方法,都没有方法体,等于是给操作系统发一个消息;让操作系统启一个线程;所以操作系统可能因为资源 没启动,也有可能启动,所以不确定;
- 又因为这个方法是本地方法,所以会被加入本地方法栈中;然后在调用本地方法接口;因为需要调用操作系统的接口,所以也是需要本地方法库
3.4:程序计数器
- 简单来说就是这个是每个线程私有的,就是一个指针,指向方法区中的方法字节码(用来存储指向下一条指令的地址,也就是即将执行的指令代码),由执行引擎来读取下一条指令,同时这个内存空间很小;
- 程序计数器的作用,保证了每个线程的程序方法按顺序来执行
接下来的区域需要关联记忆
3.5:方法区(Methodo Area)
-
重点共享区间:简单来说都是可以共享的信息,比如加载进来的元数据模版的信息就被加载进这个方法区; 所以这个区域的生命周期比较长,垃圾回收比较少;
-
方法区中的东西什么时候回收?:因为里面的东西一般都是一些运行环境,所以一般在jvm虚拟机停下来的时候回收;
-
注意这个方法区,埋个坑,因为他与他下面的堆内存和java栈密切相关
3.6 Java栈(虚拟机栈)
生命周期快,当线程销毁了 栈也销毁了;
栈的结构:
栈的容量是有限的–与其相关的就是:栈溢出错误:java.lang.StackOverFlowError
比如循环调用自己就会报错这个错误;解决的方法就是找到错误的代码;
栈+堆+方法区的交互关系:
- 接下来才讲解堆结构,简单描述与此图相关的堆内:在堆结构中的新生区中,new出来的实例全部都在这个新生区中的伊甸区,其实方法区也就是堆结构中的永久区的一个实现,所以方法区和堆是可以联系在一起
- 可以看到 ClassLoader类加载器将calss文件转化为内存中方法区的元数据模版,通过元数据模版来创建出来的实例存放在java堆中,这个实例的所以保存在java栈中,所以我们能根据这个指针找到堆中的实例,然后也可以找到方法区中的元数据模版也就是这个类->通过object.getClass()
3.7 对体系结构:
- 1堆的大小是可以调节,通过参数可以进行调优
- 可以看到分为三个区,其中每个区都是有不同的作用;
java7及以前的Heap版本:
简单描述:
新生区中:
- 1:伊甸区:new出来的实例全在这里,都是在这个区new出来的
- 2:幸存0区:从经历伊甸区gc(轻量级的垃圾回收)留下来的,一般十之八九都会被gc;在幸存1区经历过gc留下来的
- 2:幸存1区:从0区在经历gc(轻量级的垃圾回收)留下来的,
幸存0区和1区其实也是叫做from/to区,而且是相对的,因为默认是from/to 来回15次 还幸存的会进入养老区
from/to 是相对的额;to总为空,
养老区:
- 在0 1 区经历默认15次gc留下来的就开始进入养老区; 所以这个对象一般都是一直在用的不会被垃圾回收
- 不过在养老区也有gc,Major gc,全面的gc,更加深层次的gc,当养老区快满的时候就会进行更深层次的gc;不过在养老区快满了,通过 Major gc也没办法回收内存;直接就会爆over of memory Error 内存溢出OOM错误
java.lang.OutOfMemoryError:Java heap space 异常,
1 说明Java虚拟机的堆内存不够。
原因;
1:java虚拟机的内存设置不够 可以通过参数-Xms -Xmx来调整
2:代码中创建了大量的大对象,并且长时间不能被gc回收
比如在实际我们写的代码中能进入养老区的对象:
比如连接池对象常驻,会进入养老区
线程池,池对象;
永久区:
永久区与养老区是两种,并不会从养老区放入永久区,因为永久区其实就是java体系中的方法区;
- 简单来说这里是: 存放jdk自带,元数据模版 … 环境,和jar包 ;存储的是运行环境必须的类信息;被装载到这里的不会被gc回收吊,关闭JVM才能释放次内存
总结就是永久区就是jvm的环境,所以不会回收 - java.lang.OutOfMemoryError:PermGen space 异常,
1 引入的jar包太多,爆满;以前会现在不会,因为现在用的是maven工程,会根据你实际情况下载所对应的jar包,不会各种版本的jar包都出现 然后把永久区的jar包出现 - ==java 8 无永久区 ==在元空间;元空间依然在方法区中,
一个方法区接口依然有两个不同实现,一个是永久区,一个是元空间
所以在java 8 之后都是叫元空间
堆结构内存相关图:
- Sun HotSpot 内存管理:
可以看:堆物理上只是被分为新生区和养老区,永久区是虚线哪里,对应的是java体系架构的方法区,主要存放的是元数据模版;所以逻辑上是三个区,物理上是两个区
所以方法区和堆其实是可以联系起来的;
那永久区和方法区的关系是什么:
比如在写service层的接口时,一个接口可能会有多个实现,多个实现之间可能不一样
所以按照上面来谈:永久区可以说是方法区的具体实现,方法区就是永久区的接口,
在新生区中eden区的内存会更大些,因为eden区放new出来的对象,大概的比例8:1:1,s0,s1少是因为gc后幸存下来非常少;
- 这些比例我们都可以在堆的参数调优可一看明白
- 养老区与新生区中每一块比 都大,因为养老区中的幸存的对象很顽强,一般都是常驻内存,经常使用的对象;