JAVA内存模型
运行时数据区域,JVM在执行JAVA程序是将其所管理的内存划分为不同的数据区域
程序计数器,线程私有
记录当前线程执行字节码的行号,多线程切换后能够正确的恢复到程序执行的位置
虚拟机栈 ,线程私有,生命周期与线程相同
虚拟机栈描述的是JAVA方法执行的内存模型
方法执行时会创建一个栈帧的数据结构,栈帧存储了局部变量表(方法的参数,方法中的局部变量),操作栈,动态链接,方法出口等信息
方法的执行过程就是栈帧进栈和出栈的过程
本地方法栈,与虚拟机栈功能类似,为本地方法服务
Java堆区(Heap) 线程共享的
虚拟机管理的最大一块区域
用来存放对象实例
也是垃圾收集器主要工作的区域 按照内存回收的角度又分为
新生代(Eden ,survivor) 经验性 死亡率高达97%需老年代提供担保 用少量存活对象的复制代价完成清理
老年代 存活率高 用 标记-清除 或 标记-整理算法 为新生代提供担保空间,一旦存活对象survivor无法容纳直接进入老年代
方法区 线程共有的
主要用来存放类信息 ,静态变量 ,常量
内存回收目标有两个;类型的卸载 常量池的回收
运行时常量池 :用来存放 字面量 ,符号引用 。
对象是否存活?
引用计数法:为对象添加一个计数器,对象每被引用一次计数器加一,当一个引用失效计数器减一,当计数器为0时说明对象不可用
缺点:无法解决循环引用的问题 A.S=B ,B.S=A python用次算法
可达性分析算法(跟搜索算法)
通过一系列的‘’GC Roots‘’ 的对象向下搜索 搜索走过的路径称为引用链 若对象到GC Roots没有任何引用链时 对象就是不可用的
可作为GC Roots 的对象
方法区的静态变量,常量 引用的对象
虚拟机栈 局部变量表中引用的对象
本地方法栈 中本地方法引用的对象
标记-清除算法
首先标记出所有要回收的对象,标记完成后统一清理所用被标记的对象
缺点:产生大量的空间碎片,分配大对象是无法找到足够的连续内存
标记-整理算法
让所有存活的对象移动到内存的一端,清理掉边界外的内存。
复制算法
将新生代分为一块Eden区 和两块Survivor区域(8:1:1),每次使用Eden区和一块Survivor区,回收时将存活的对象复制到另一块Survivor区
最后清理掉Eden区,和Survivor区。
利用了新生代的朝生夕死的特性,付出少量的存活对象的复制代价完成清理 ,需要老年代担保
分代收集算法:根据对象存活周期不同分为 新生代,老年代
垃圾收集器
serial收集器
单线程收集垃圾,用户线程 STOP THE WORD
优点:无线程交互的开销 ,专心做垃圾收集工作
ParNew收集器 多线程版serial
Parallel Scavenge收集器(吞吐量)
吞吐量=用户线程时间 / CPU总消耗时间 适合后台运算不需要太多交互的任务
CMS 垃圾收集器(Concurrent Mask Sweep)最短停顿时间 重视服务的响应速度
1 初始标记 快速扫描GC Roots直接引用的对象 需要STOP THE WORD
2 并发标记 与用户线程并行
3 重新标记 STOP THE WORD 标记那些并发标记因用户线程改变的对象引用
4 并发清理
缺点:标记-清除算法 产生空间碎片
并发清理时产生的垃圾无法进行收集,产生浮动垃圾
并发标记与用户线程并行效率不高
G1 垃圾收集器(Garbage first)
致力于非常精确的控制停顿
G1将堆内存划分为多个大小固定的独立区域(Region),并跟踪每块区域的垃圾堆积程度,计算垃圾回收价值(回收需要的时间,回收获得的空间)在后台维护一个优先表,根据每次允许的收集时间来优先回收价值最大的区域。
对象优先分配在Eden区
大对象直接进入老年代,避免在Eden区和Srvivor区发生大量内存拷贝
长期存活的对象进入老年代,对象每熬过一次minor GC年龄+1 增加到15晋升老年代
动态年龄划分:在Survivor空间中所有相同年龄的所有对象大小大于Survivor的一半,大于等于与该年龄的对象进入老年代
类加载机制
加载
通过类的权限名获取次类的二进制字节流,将字节流代表的静态储存结构转换为方法区的运行时数据结构
在java堆生成java.lang.Class对象作为方法区这些数据的访问入口
连接 分为三个阶段(验证,准备,解析)
验证是连接的第一个阶段
1文件格式验证 字节流是否满足Class文件要求 ,
2元数据验证(父类是否继承final修饰的类,不是抽象类是否实现了父类或接口的全部方法)
3字节码验证
4符号引用验证
准备
准备阶段是正式为类变量分配内存(方法区)并设置初始值(零值)的阶段,
解析
符号引用(用符号描述所引用的目标)变成直接引用(直接指向对象的指针或句柄)
初始化
执行java代码
双亲委派模型
两个类是否相等,不仅要求是同一个Class文件还必须是一个类加载器加载的
如果一个类加载器收到了类加载的请求,它首先不会尝试自己去加载这个类,而是把这个请求委托给父类去完成,因此所有类加载的请求都应该传送到顶层的启动类加载器,只有当父类反馈自己无法完成这个加载请求(他的搜索范围没有找到这个类)时子类加载器才会自己去尝试加载这个类。