3.JVM
1.虚拟机?
可以执行java字节码的虚拟机进程,
.java文件经过编译成能被java虚拟机执行的.class文件
能跨平台?
java编译成响应的字节码,再不同系统的jvm被执行,所有跨平台的是java程序,而不是JVM
2.jvm组成
作用
1.程序在执行之前要把代码编译成字节码.class文件 2.jvm通过类加载器将字节码文件加载到内存中, 3.而字节码文件时jvm的指令集,并不能直接交给底层操作系统去执行, 4.而交给解析器执行调用本地接口方法实现执行底层系统指令
a 类加载器
在jvm启动或者类运行时,将需要class文件加载到JVM中
b 内存区
见3
c 执行引擎
执行引擎的任务负责执行class文件中包含的字节码指令,
d 本地方法调用
3.jvm内存区-运行数据区
1)方法区(元空间):
jdk8已经被取消,改成了元数据,
各个线程共享的一个区域,用于存储虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据
jdk1.8后,字符串和常量放到堆内存中,新增了MetaSpace
大小没有限制,根据内存大小,动态改变;也可以通过-XX:MetaspaceSize-初始化大小;-XX:MaxMetaspace-最大值,
2)堆内存
各个线程共享的一个区域,垃圾收集器管理的主要区域。
3)本地方法栈
为jvm提供native方法的服务
jdk里native修饰的方法(本地),会调用本地c语言函数
4)虚拟机栈
线程私有,
每个方法在执行的时候都会创建一个栈帧用于存储局部变量,操作数,方法出口等信息
每个方法调用,都意味着虚拟机栈中入栈和出栈的过程
程序计算器
线程私有,
如果执行的时候java方法,记录当前线程正在执行的虚拟机字节码指令的地址,如果本地方法则为undefined
该内存区域是唯一一个在java虚拟机没有规定任何OutOfMemoryError情况的区域。
栈帧:用于支持虚拟机经行方法调用和方法执行的数据结构,它是虚拟机运行时数据区的虚拟机栈的栈元素。 每一个方法对应一块栈帧内存区域, 栈帧里由
局部变量,
操作数栈
动态链接,将符号引用转为直接引用
方法出口
a 浅拷贝和深拷贝
浅拷贝:新增一个指针,指向原有内存地址
深拷贝:新增一个指针,指向原有内存地址的复制的内存地址
b 堆栈区别
物理地址,堆分配的地址对象时不连续的,因此性能慢,而栈分配的是连续的,性能高
因为堆是不连续所有内存大小是不固定,只有在运行期确定,而栈是连续的,在编译期就确定了;堆存放的是内存对象,而栈存的是局部变量,操作数栈,方法出入口,
堆内存对于线程是共享的,栈是线程私有
3.nio直接内存?
直接内存(Direct Memory),并不是虚拟机运行时数据区的一部分,也不是 Java 虚拟机规范中农定义的内存区域。在 JDK1.4 中新加入了 NIO(New Input/Output) 类,引入了一种基于通道(Channel)与缓冲区(Buffer)的 I/O 方式,它可以使用 native 函数库直接分配堆外内存,然后通脱一个存储在 Java 堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在 Java 堆和 Native 堆中来回复制数据
4.创建对象的方式
方式 | 注释 |
---|---|
new关键字 | 调用构造函数 |
Class的newInstance | 调用构造函数 |
Constructor的newInstance | 调用构造函数 |
clone | 未调用构造函数 |
反射 | 未调用构造函数 |
1)对象创建流程
new 类名
-> 虚拟机遇到一条new指令,检查常量池中是否已经加载了相应的类,
->虚拟机为对象分配内存
->将分配的内存初始化为零值(不包括对象头)
->调用对象init方法
2)为对象分配内存?
指针碰撞
如果java堆的内存是规整的,所有用过的一边放一边,而空闲的放另一边,分配内存时,将位于中间的指针指示器将往空闲空间挪动一段与对象大小相等的距离
空闲列表
如果不规则,虚拟机必须维护一个列表记录哪些内u才能可用,在分配的时候从列表中找到一块足够大的空间划分给对象实例
3)处理并发问题?
同步处理,采用cas和失败重试 更新操作的原子性
本地线程分配缓存
4)对象的访问定位?
句柄访问,在堆内存中划分一块内存作为句柄池,引用中存储对象的句柄地址,句柄池存储对象实例指针和对象类型指针,
直接指针
引用中存储的是对象地址,对象地址内存存类型数据地址,指向方法区
5.内存泄露异常
内存泄漏指的是不在使用的对象或者变量,一直被占用在内存中,没有被回收,
理论上java有垃圾回收机制,不再使用的对象,会被gc自动回收,从内存中清楚
出现情况:
长生命周期的对象持有短生命周期的对象的引用就很有可能发生内存泄漏
6.垃圾回收机制
定义
java中,程序员不需要手动地去释放对象内存,而是有虚拟机自行执行,在JVM中有一个低优先级的垃圾回收线程,在虚拟机空闲或者内存不足的,才会触发,扫描那些没有任何引用的对象,并将它们添加到回收的集合中,进行回收;
1)引用类型
强引用:发生gc的时候不会被回收,需要弱化从而使gc回收
软引用:在发生内存溢出时会被回收
弱引用:在下次gc的时会被回收
虚引用:无法通过虚引用获取对象
2)判断对象是否可以被回收
引用计算器法:
为每个对象创建一个引用计数,有对象引用时+1,引用释放计数-1,当计数器为0,就可以被回收,
缺点:循环引用解决不了
可达性分析法:
从GC roots开始向下搜索,搜索所走的路径叫做引用链,当一个对象到Gc Roots没有任何引用链相连,则证明此对象可以被回收;
GC roots根节点:线程栈中本地变量,静态变量,本地方法栈的变量等等
3)在Java中,对象什么时候可以被垃圾回收
当对象对当前使用这个对象的应用程序变得不可触及的时候,这个对象就可以被回收了
gc roots向下搜索,没有任何引用链
4)永久代中会发生垃圾回收?
不会发生在永久代,但是永久代满了或超过临界值,会触发垃圾回收机制,但是永久代也是会被回收的
5) JVM 有哪些垃圾回收算法
标记清除算法
顾名思义,标记无用对象,然后再进行清楚回收,
缺点:标记清除效率低,无法清除垃圾碎片,会产生不连续内存碎片
复制算法
它把内存空间分成两个相等的区域,每次只使用其中一个区域,垃圾收集时,遍历当前使用区域,把存活对象复制到另外一个区域中,然后将可用的数据回收
缺点:可用内存大小缩小为原来的一半,对象存活率高时会频繁经行复制。
标记整理法
复制算法,不太适用于老年代,老年代的存活率较高,这样会有较多的复制操作,导致效率变低,
缺点:需要局部调动,效率低
分代收集算法
当前商业虚拟机都采用分代收集算法,将存活周期划分,年轻代,老年代和永久代
[图片上传失败...(image-f4628-1585402664191)]
6)JVM中有哪些垃圾回收器
serial收集器(复制算法)
新生代单线程收集器,标记清理都是单线程,简单高效
ParNew收集器(复制算法)
serial收集器多线程版本
serial old收集器(标记整理)
老年代收集器,单线程
parallel old收集器
老年代收集器,并行
CMS收集器(标记)
老年代并行收集器,牺牲系统的吞吐量来追求收集速度
采用标记清除算法,会产生大量内存碎片
场景:使用于延时要求高的服务,用户线程不允许长时间停顿。
g1(标记整理)
garbagr first,面向服务端应用
堆回收器,jdk1.7提供的新收集器,
7)简述分代垃圾回收器工作?
分代垃圾分两个分区:新生代(1/3)和老生代(2/3),
新生代一般采用复制算法,新生代分三个分区:Eden(8),from survivor (1),to survivor(1)
首先新建的对象一般放在eden和form survivor中,存活的对象放在to survivor中,
清空from survivor和eden区
from to互换
每次从from 移动到to,年龄+1,当超过15时,放入老生代(大对象直接放入老生代)
当老生代空间占用达到某个值后,就会触发垃圾回收
如果一批对象的总大小大域survivor>50%时,直接进入老年区,老年区满时,会进行full gc
(1) STW
stop the word,停止用户线程
7.java内存分配,回收策略,以及minor gc和major gc
1)内存分配
对象的内存分配一般是在堆上,自动内存分配
对象优先分配再eden区,
大对象直接分配在老年代(大对象指需要大量连续内存空间)
长期存活的对象放入老年代
2)minor gc和Major gc
当新生对象无法为其分配内存时,执行minor gc,发生在新生代
major gc清理老年代,major gc执行前往往会执行一次 minor gc
full gc老年代和新生代,
触发条件:
老年代空间不足,方法去空间不足,System.gc,minor gc后进入老年代的平均大小大于老年代可用大小
装载->验证->准备->解析->初始化->实例化->垃圾收集->对象终结->卸载类型
8.java类加载机制
虚拟机将描述的类信息从class文件加载到内存,并对数据进行检验,解析和初始化,形成被虚拟机直接使用java类型
1)什么是类加载器
通过类的全限定名,获取该类的二进制字节流的代码块,加载到内存,获取class类对象
2)有哪些类加载器
启动类加载器
用于加载java核心类库,无法被java程序直接引用
扩展类加载器
java扩展库,
系统类加载器
根据java应用的类路径来加载Java类,一般来说,Java应用的类都是由它来实现加载
用户自定义类加载器
通过继承java.lang.ClassLoader的方式来实现
3)类加载的执行过程
加载
根据路径查找相应的class文件导入
验证
检查加载class文件的正确性
准备
给类的静态变量分配内存空间
解析
将常量池中符号引用替换成直接引用 (符号引用只是一个表示,直接引用指向内存地址)
初始化
对静态变量和静态代码块执行初始化工作
4)什么是双亲委派模型?
类加载器 通过全限定名将class文件加载到jvm中,然后转化成class对象
定义
当一个类收到类加载请求时,不会自己先去加载这个类,而是将其委派给父类,有父类去加载,如果此时父类不能加载,反馈给子类,由子类去完成加载
9.jvm调优
1)调优工具
jdk bin目录下,
jconsole和jvisualvm
2)调优参数配置
-Xms2g:初始化推大小为 2g;
-Xmx2g:堆最大内存为 2g;
-Xmn:年轻代内存大小
-XX:NewRatio=4:设置年轻的和老年代的内存比例为 1:4;
-XX:SurvivorRatio=8:设置新生代 Eden 和 Survivor 比例为 8:2;
–XX:+UseParNewGC:指定使用 ParNew + Serial Old 垃圾回收器组合;
-XX:+UseParallelOldGC:指定使用 ParNew + ParNew Old 垃圾回收器组合;
-XX:+UseConcMarkSweepGC:指定使用 CMS + Serial Old 垃圾回收器组合;
-XX:+PrintGC:开启打印 gc 信息;
-XX:+PrintGCDetails:打印 gc 详细信息
3)调优方案
亿级电商系统VM参数设置优化
6.对象内存布局
对象头
第一部分:存储对象自身运行时的数据,如哈希码 gc分代年龄 锁状态标志,线程持有的锁等等
第二部分:类型指针,对象指向类元数据的指针
实例数据
数据
对齐填充
不是必然存在,只是为了对齐