1.Jvm内存区域
![](https://i-blog.csdnimg.cn/blog_migrate/3ff9710189cd976148bcab86c84e709d.png)
JVM结构
2.JVM加载Class的原理机制
类加载器负责加载类,执行引擎(Execution Engine)负责解析类提交操作系统执行,本机接口
(Native Interface)负责融合不同的编程语言为Java所用
类加载的方式
1.隐式装载:程序在运行过程中通过new等方式生成对象时,隐式调用类装载器加载对应的类到jvm中
2.显式装载:通过class.forname()等方法,显式加载需要的类
类加载步骤
1.装载:查找和导入class文件;
2.连接:
1)验证:检查载入的class文件数据的正确性
2)准备:为类的静态变量分配存储空间
3)解析:将符号引用转换成直接引用
3.初始化:初始化静态变量,静态代码块
4.使用
5.卸载
3.对象分配规则
![](https://i-blog.csdnimg.cn/blog_migrate/b0a8ee8132430109beffc575f1fcb93c.png)
4.什么是双亲委派?可以打破双亲委派么?怎么打破?
1)当一个类加载器收到了类加载的请求,它不会直接去尝试自己加载,而是先把这个加载请求委派
给它的父类加载器;因此所有的加载请求最终都会被传递到顶层的启动类加载器中;自下而上的委派,
自上而下尝试加载,只有当上层加载器(父加载器)无法完成此加载请求时,下层加载器(子加载器)
才会尝试自己加载。
双亲委派保证类加载器,自下而上的委派,又自上而下的加载,保证每一个类在各个类加载器中都是
同一个类。
2)可以
3)继承ClassLoader,重写loadClass与findClass
5.Java对象创建过程
类初始化的时机
1.1)创建类的实例
1.2)访问设置类的静态变量(非final修饰),调用类的静态方法
1.3)创建子类实例
1.4)JVM启动时标明的启动类,即文件名和类名相同的那个类 只有这6中情况才会导致类的类的初始化。(main方法所在的类)
1.5)克隆
1.6)反序列化
1.7)ClassLoader.loadClass(className); 只加载和连接、不会进行初始化
1.8)Class.forName(String name, boolean initialize,ClassLoader loader);
使用loader进行加载和连接,根据参数initialize判定是否初始化。
当new一个对象时,先根据new的参数在常量池中定位一个类的符号引用,若果不存在符号引用则说明此类没有被加载过,则开始进行类加载、验证、准备、解析、初始化,在方法区中存储类信息,在堆中存储类的实例信息,初始化类的变量,调用类的<init>方法
6.Java的对象结构
对象头 + 实例数据 + 对齐填充
1)对象头(Header)4字节markword 4字节class对象指针
markword:哈希码、分代年龄、锁标志位、偏向线程ID、偏向时间戳等信息
class对象指针:指向当前对象的类的元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例
2)实例数据(Instance Data)对象实际数据(实际数据大小)
实例数据是对象真正存储的有效信息,是我们程序代码里面所定义的各种类型的字段内容,无论是从父类继承下来的,还是在子类中定义的都需要记录下来。
3)对齐填充(Padding) 8字节的填充(可选)
对齐填充并不是必然存在的,也没有特别的含义,它仅仅起到占位符的作用。由于HotSpot VM的自动内存管理系统要求对象起始地址必须是8字节的整数倍,换句话说就是对象的大小必须是8字节的整数倍。对象头正好是8字节的倍数(1倍或者2倍),因此当对象实例数据部分没有对齐的话,就需要通过对齐填充来补全。
7.强、软、弱、虚四种引用的
强引用(Strong Reference)
A a = new A(); 开发中的正常引用对象都为强引用对象,垃圾回收器无法回收具有强引用对象,
当虚拟机内存空间不足时抛出OOM,程序终止。
软引用(Soft Reference)
具有软引用对象在内存空间充足时,垃圾回收器回收时不会回收具有软引用对象,在内存空间不足
时回收具有软引用对象。
弱应用(Weak Reference)
弱化版的软引用,无论垃圾回收时内存空间是否充足都会回收掉具有弱引用的对象。
虚引用(Phantom Reference)
虚引用不会影响对象的生命周期,系统虚设,在任何时候垃圾回收都会回收掉具有虚引用的对象,
虚引用必须与应用队列(ReferenceQueue)一起使用,主要用来跟踪对象被垃圾回收器回收的活动。
8.堆与栈的区别
堆 存储对象实例、数组信息,无需连续空间,大小不固定,线程共享,需要垃圾回收
栈 存储方法运行时数据(栈帧=局部变量表、操作数栈、动态链接、方法出口 先进后出),编译时期
确认大小,连续固定的内存空间,线程私有,无需考虑垃圾回收
9.什么时候触发MinorGC
当Eden区满时,触发MinorGC
当-XX:+CollectGen0First=true时,FullGC前都执行一次MinorGC
当‐XX:+CMSScavengeBeforeRemark,在CMS GC前启动一次MinorGC
10.什么时候触发FullGC
调用 System.gc() 方法(不是必然执行)
老年代空间不足,新来的对象老年代放不下了
方法区空间不足
通过MinorGC后进入老年代的平均大小大于老年代可用内存
11.对象如何进入老年代
直接分配
1)对象的大小超过JVM PretenureSizeThreshold设置大小[默认值0,任何大小的对象都会先分配到Eden区]
2)对象的大小超过Eden的区大小
3)新生代分配失败[一个大数组或者大字符串]
从年轻代晋升
1)新生代分配担保,在执行MinorGC时要将Eden区存活的对象复制到Survivor区,当Survivor区内存不够所有存活对象分配时,就需要将Survivor无法容纳的对象分配到老年代去,这种机制就叫分配担保
2)对象年龄超过虚拟机MaxTenuringThreshold的设置值[默认值15]
3)当Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半时,年龄大于或等于该年龄的对象直接进入老年代。
空间分配担保
当发生MinorGC时,虚拟机会检测之前每次晋升到老年代的平均大小是否大于老年代的剩余空间大小,如果大于则改为直接进行一次Full GC。如果小于则查看HandlePromotionFailure设置是否允许担保失败;如果允许那只会进行MinorGC;如果不允许则也要改为进行一次Full GC
12.为何将年轻代拆分成Eden、Survivor区,为何要将Survivor区拆分成大小相等的两块
1)如果不将年轻代拆分成Eden、Survivor两个区,一次MinorGC后存活的对象将直接进入老年代,
老年代的空间将很快被用尽,触FullGC,FullGC的耗时远远大于MinorGC的耗时。因此需要
Survivor区域来进一步过滤赛选进入老年代的对象,尽量减少对象进入老年代,从而减少FullGC。
2)将Survivor区拆分成大小相等的两块是为了使用复制算法,即快速又避免了空间碎片的生成
(虽然在两块空间只能使用一块的情况直接造成了空间浪费,但这样对速度的提升是显而易见的,
同时有避免了空间碎片的生成(隐式的节约的空间))。
3)复制算法更适用于年轻代对象的存活率低,复制时只需复制少量对象的场景。
13.如何判断对象可以被回收
1)引用计数器法:为每个对象创建一个引用计数,有对象引用时计数器 +1,引用被释放时计数 -1,
当计数器为 0 时就可以被回收;它有一个缺点不能解决循环引用的问题。
2)可达性分析算法:从GCRoots开始向下追踪,追踪路径称为引用链。当一个对象到GCRoots没有任何引
用链相连时,则证明此对象是可以被回收的。
14.垃圾收集算法
1)标记清除(碎片问题)
2)标记整理(效率问题)
3)复制算法(空间浪费问题)
15.内存溢出与内存泄漏的区别
内存溢出 OOM 【OutOfMemoryError 内存不足】
堆溢出 java.lang.OutOfMemoryError: Java heap space
方法区溢出 java.lang.OutOfMemoryError: PermGen space
线程栈溢出 stackOverflow
尽早释放无用对象引用,使用Stringbuffer替换String,少使用静态变量,少开启大型文件,
每次尽可能少的从数据库中获取数据
内存泄露
长生命周期对象持有短生命周期对象引用
HashSet中对象值修改,影响hashcode
长时间开启耗费资源的连接
16.垃圾回收器
新生代
serial 单线程 复制算法 (全程stop the word) 响应速度优先
parnew 多线程 复制算法 (全程stop the word) 响应速度优先
parallel scavenge 多线程 复制算法 (全程stop the word) 吞吐量优先
老年代
serial old 单线程 标记整理 (全程stop the word) 响应速度优先
parallel old 多线程 标记整理 (全程stop the word) 吞吐量优先
cms 多线程 标记清除 (局部stop the word) 响应速度优先
新生代、老年代
g1 多线程 全局标记整理局部复制算法 (局部stop the word) 响应速度优先
17.JVM性能调优
1)合理的基础配置(各个空间大小,进入老年代的年龄等待)
2) 合理的回收器选择(根据CPU个数,吞吐量,响应速度区分选取)
3)开启TLAB 、关闭逃逸分析
4)JVM的监控分析工具(JAVA原生工具,第三方工具Arthas等)
参考 VM优化配置