java程序跑在jvm上面,jre-jvm,在操作系统之上,最底下是硬件系统。
一、JVM的体系架构图
jvm调优说的是“方法区”和“堆“,而”栈“、”本地方法栈“、”程序计数器“不存在垃圾回收一事。
虚拟机试图使用最大内存为电脑内存的1/4, 而jvm初始化内存为1/64(-Xms1024m -Xmx1024m -XX:+PrintGCDetails)
二、类加载器
(1) 在什么时候才会启动类加载器?
类加载器并不需要等到某个类被“首次主动使用”时再加载它,JVM规范允许类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了.class文件缺失或存在错误,类加载器必须在程序首次主动使用该类时才报告错误(LinkageError错误)如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误。
(2) 从哪个地方去加载.class文件
在这里进行一个简单的分类。例举了5个来源
(1)本地磁盘
(2)网上加载.class文件(Applet)
(3)从数据库中
(4)压缩文件中(ZAR,jar等)
(5)从其他文件生成的(JSP应用)
定义: 类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个 java.lang.Class对象,用来封装类在方法区内的数据结构
作用:加载类文件,引用在栈中,具体实例在堆中
虚拟器自带三种类加载器启动类(根)加载器扩展类加载器应用程序加载器(系统类加载器)
(3) 类加载过程
其中类加载的过程包括了加载、验证、准备、解析、初始化五个阶段。在这五个阶段中,加载、验证、准备和初始化这四个阶段发生的顺序是确定的,而解析阶段则不一定,它在某些情况下可以在初始化阶段之后开始。另外注意这里的几个阶段是按顺序开始,而不是按顺序进行或完成,因为这些阶段通常都是互相交叉地混合进行的,通常在一个阶段执行的过程中调用或激活另一个阶段。
三、双亲委派机制
当某个类加载器需要加载某个`.class`文件时,它首先把这个任务委托给他的上级类加载器,递归这个操作,如果上级的类加载器没有加载,自己才会去加载这个类。
四、沙箱安全机制 Java安全模型核心
沙箱:限制程序的运行时环境
域Domain概念
将java代码限定在虚拟机特定的运行范围内,严格限制代码对本地系统资源的访问,这样措施保证对代码的有效隔离,防止对本地系统的破坏
五、Native
调用底层c,c++语言库 - native -> JNI -> 本地方法接口 -> 本地方法库
本地方法栈 , 一般用的不多,硬件开发用的多
native:凡是带了native关键字的,说明java的作用范围达不到了,回去调用底层C语言的库
会进入本地方法栈
调用本地方法本地接口JNI
JNI作用:扩展java的使用,融合不同的语言为java所用!最初:C、C++
在最终执行的时候,通过JNI加载本地方法库中的方法
六、pc寄存器
程序计数器Program Counter Register
每个线程都有一个程序计数器,线程私有,就是一个指针,指向方法区中的方法字节码(用来存储指向下一条指令的地址, 也即即将要执行的指令代码)非常狭小的空间 --可以忽略不计
七、方法区(Method Area)
方法区是被所有线程共享,所有字段和方法字节码,以及一些特殊方法,如构造函数,接口代码也在此定义,简单说,所有定义的方法的信息都保存在该区域,此区域属于共享区间。
静态变量,常量,类信息(构造方法,接口定义)运行时的常量池也存在方法区中,但是实例变量存在堆内存中,和方法区无关
八、栈(stack)
栈:先进后出,后进先出,堆:先进先出,后进后出
栈内存,主管程序的运行,生命周期和线程同步
线程结束,栈内存也就释放,对于栈来说,不存在垃圾回收问题
一旦线程结束,栈内存释放
程序正在执行的,一定在栈顶部
栈:8大基本类型+对象引用+实例的方法
栈帧:父帧子帧,每一个在执行的方法都会产生栈帧
栈满报错:StackOverflowError
对象实例化示意图:
九、堆(Heap)
一个JVM只有一个堆内存,堆内存的大小是可以调节的。
类加载器读取了类文件后,一般会把类,方法,常量,变量等放到堆中,保存我们所有引用类型的真实对象
3个区域
新生区(伊甸园): 所有的对象都new在了伊甸园去,从中出生
养老区-- 老年区 --从新生区(from to )中顺下来的,干不掉,杀不死,幸存区,哪个空,哪个就是to
永久区:此区域不存在垃圾回收,但是也会崩( 启动了大量第三方jar) --
永久区是一个常驻内存区域,用于存放JDK自身携带的Class Interface的元数据,存储的是java运行时的一些环境或类信息,这个区域不存在垃圾回收!
一个启动类,加载了大量的第三方jar包,Tomcat部署了太多的应用,大量动态生成的反射类,永久区可能会崩掉
幸存区不会出现满了溢出,GC垃圾回收,主要是在伊甸园区和养老区
堆内存满了,会报OOM(java.lang.OutOfMemoryError: Java heap space)
dk1.6之前永久代,常量池是在方法区
jdk1.7永久代,慢慢退化,去永久代,常量池在堆中
jdk1.8无永久代,常量池在元空间
关闭虚拟机就会释放这块内存
gc垃圾回收主要针对新生区(伊甸园区)和养老区
设置分配内存和初始化内存大小,并打印
-Xms1024m -Xmx1024m -XX:+PrintGCDetails
-Xms 设置初始化内存分配大小 默认内存的1/64
-Xmx 设控制最大分配内存 默认是内存的1/4
-XX:+PrintGCDEtails 打印GC垃圾回收信息
-XX:+HeapDumpOnOutOfMemoryError 打印内存溢出信息
出现内存溢出处理方法:
(1)首先调大堆内存大小
(2)分析内存,看一下哪里出现问题,用专业的工具
能够看到代码第几行出现问题,内存快照分析工具Jprofiler
JProfiler作用:
分析Dump内存文件,快速定位内存泄漏
获得堆中的数据
获得大的对象
十、GC垃圾回收
JVM在进行GC时,并不是对这三个区域统一回收。大部分时候,回收的都是新生代
新生代
幸存区(from,to)
老年区
GC两种类:轻GC(普通的GC),重GC(全局GC)
题目:
JVM的内存模型和分区,详细到每个区放什么
堆里的分区有哪些?Eden、from、to,老年区,说说他们的特点
GC的算法有哪些?标记清除法、标记压缩、复制算法、引用计数器,说一下他们之间的特点,怎么用?
轻GC和重GC分别在什么时候发生?轻GC在伊甸园区满的时候触发,重GC在整个新生区满的时候触发。
(1)引用计数法
(2)复制算法
幸存区from和to是来回切换的,哪个空,哪个就是to
1)每次GC都会将Eden活的对象移到幸存区中,一旦Eden区被GC后,就会空的!
2)当一个对象经历了15次GC(默认),都还没有死,就会进入养老区,-XX:MaxTenuringThreshold=5,
可以通过MaxTenuringThreshold参数设定进入老年代的时间
绿色代表活着的数据,从Eden复制到幸存区To里面,同时幸存区From中的幸存者也复制到To里面,这时原幸存区From变为To,原To变为From,Eden变成空的了;若From中的数据有经历过15次GC,活着的就进入养老区,死掉的回收掉。 好处:没有内存碎片
坏处:浪费内存空间,多了一半空间永远是空to。假设对象100%存活(极端情况)
复制算法最佳使用场景:对象存活度较低的时候;新生区
(3)标记清除算法
扫描对象,对活着对象进行标记,然后对没有标记的对象,进行清除
优点:不需要额外的空间
缺点:两次扫描,严重浪费时间,会产生内存碎片
(4)标记压缩算法:在标记清除算法基础上,向一端移动存活对象,以防止碎片产生
总结
内存效率:复制算法>标记算法>标记压缩算法(时间复杂度)
内存整齐度:复制算法=标记压缩算法>标记清除算法
内存利用率:标记压缩算法=标记清除算法>复制算法
根据不同区域选择最合适的算法,回收垃圾
年轻代:由于存活低,采用复制算法
老年代:由于区域大,存活率高,采用标记清除+标记压缩混合实现,在清除过程中,碎片过多时,可以压缩后,再继续清除。
补录:
GC
- 垃圾回收为自动,手动只能提醒
- GC作用于堆+方法区
- GC大部分针对新生代
- 轻GC ----- 普通GC
- 重GC ----- 全局GC
- GC算法
- 复制算法 ---
- GC算法-复制算法
- 该算法将内存平均分成两部分,然后每次只使用其中的一部分,当这部分内存满的时候,将内存中所有存活的对象复制到另一个内存中,然后将之前的内存清空,只使用这部分内存,循环下去
- 幸存区01, from...to..., 0和1互相不断交换,进行gc进行复制算法
- 若一直没有死进入到养老区
- 优点:实现简单,不产生内存碎片
- 缺点:浪费一半的内存空间
- 标记清除算法 -----扫描对象,对活着的对象进行标记, 对没有标记的对象进行清除
- 优点:不需要额外空间,优于复制算法
- 两次扫描,浪费时间,会存在内存碎片
- 标记压缩算法 ----- 再优化,压缩:防止碎片的产生, 方法: 向一端移动活的对象,多了一个移动成本
- 标记清除压缩算法 ----- 先标记清除几次再进行压缩,等碎片多了之后
- 引用计数算法 ------ 每个对象一个计数器,一般不用,因为计数器有消耗,用过多次的不删,0次的就删除了 ---引用出现+1,引用删除-1
- 总结:
- 内存效率:时间复杂度:复制算法 > 标记清除 >标记压缩
- 内存整齐度:复制算法=标记压缩>标记清除
- 内存利用率 ----- 标记压缩=标记清除 > 复制算法
- 分代收集算法(JVM调优): 没有最好的算法,只有最合适的
- 年轻代 ----- 存活率低,复制算法
- 老年代 ----- 存活率高, 标记清除与标记压缩混合实现
JMM:
1、什么是JMM(百度百科)
JMM:(Java Memory Model)java内存模型
2、它是干嘛的?官方文档、博客和相关视频
作用:缓存一致性协议,用于定义数据读写的规则(遵守,找到这个规则)。抽象概念, 理论
定义了线程工作内存和主内存之间的抽象关系
线程之间的共享变量存储在主内存中 ----MAIN MEMORY
每个线程都有一个私有的本地内存 ---- LOCAL MEMORY
与并发相关
OOM的种类和原因
java.lang.OutOfMemoryError
为什么java需要采用分代回收思想?
防止对象全部进入老年代,撑爆内存,采用分代回收,就是尽量在新生代就把对象回收掉,让少量的对象进入到老年代,减少STW次数,提高性能。
https://www.cnblogs.com/yang-lq/p/8651494.html
在生成.class文件所在目录使用命令javap,可以生成jvm可以识别的指令,查看计算机java的执行情况
javap -c hello.class
javap -c hello.class > app.txt //将编译成的指令写到app.txt文档中