方法区(永久代) 线程共享 用于存储被 JVM 加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
运行时常量池 是方法区的一部分。Class 文件中除了有类的版本、字段、方法、接口 字面量 和 符号引用
堆 线程共享 新生代(Eden区、From Survivor区和To Survivor区)和老年代。
新生代占堆的1/3 老年代占2/3 新生代中eden区与s区 比为8:1:1
1new 出来的对象先尝试在栈中分配(一旦弹出 整个声明周期结束 不需要通过垃圾回收) 2当栈中无法分配 潘丹对象的大小 如果很大 那么直接进入老年代 老年代只能通过FuillGC回收 3当对象不大 但是无法在栈上分配 ,那么就会分配到线程本地缓冲区(TLAB) 4当多个线程想在eden区分配时 会对空间挣用 降低效率 这时就会涉及到TLAB机制 每个线程在eden区取1%空间 为这个线程私有 分配对象的时候先往这个线程队友的地方分配 这样避免与其他线程争抢 效率变高 5此时对象在Eden区 当对象经过一次YoungGC ,该对象会进入s1区,s1区经过一次yongGC 会进入s2区 反复来回 若s1中的对象拷贝到s2 若超过s2的总容量50% 那么将s2中最大的对象直接分配担保到老年代 如果youngGC期间s1空间不够了 那么就空间担保直接进入老年代 对象经过多次GC(YongGC采用复制算法 MinorGC 的过程(复制->清空->互换) s1-s2当生命周期超过限制时大于15时 就会进入老年代) 知道年龄到了为止 为什么会大于15? 因为对象头信息包含 分代年龄栈4bit 1111 最大15 老年代满了 用FullGC(尽量不要有FullGC) 堆内存用来存放对象和数组 有java自动垃圾回收器来回收
程序计数器 唯一一个在虚拟机中没有规定任何 OutOfMemoryError 情况的区域 记录栈帧运行位置
java虚拟机栈
里面存放这一个一个栈帧 每个方法对应一个栈帧
bipush_8 的描述 是将8 byte类型扩展为int类型 将8压入栈中 int i=8 istore_1 出栈 讲这个值存储到局部变量表下标为1位置 iload_1从局部变量表中拿出来 压倒栈中 iinc 1 by 1 将局部变量表中的8 加1 变成9 但是栈中还是8 istore 出栈 将栈中的值赋值给局部变量表 由9变成8 return 8
bipush_8 将8 byte类型扩展为int类型 将8放入栈中 压栈 int i=8 istore_1 出栈 将8存入局部变量表中下标为1的位置 iinc 1 by 1 将局部变量表中的8 加1 变成9 iload_1 从局部变量表中拿出来 压倒栈中 栈中i=9 istore_1 出栈 将9 放到局部变量表中 下标为1的位置 return 9
局部变量表(local variables) 用来计算
操作栈(operand Stacks) 用来存储临时变量
动态链接(dynamic Linking) 运行时常量池里面的符号链接(编译字节码时 的符号引用 转换为具体引用)因为在resolution阶段 有方法调用了其他方法,resolution阶段无法解析, 那么dynamic Linking阶段会
查看符号链接是否有解析 如果没有解析 就进行动态解析 如果解析了 直接使用
返回地址(return address) 当a方法调用b方法是 b方法结束后会放回调用b方法位置继续执行
本地方法栈
本地方法(native method):凡是带了native关键字的,说明java的作用范围达不到,会通过native调用操作系统C语言的库
-----------------------------------------------------
类加载过程
1 loading
1.1 当我们new class 对象时 先编译成字节码 .class文件 然后由classLoader 加载到内存,不同的class 由不同的classLoader加载
1.2
1.2.1 customer 自定义classLoader 由用户自定义类加载路径
1.2.2AppClassLoader 加载classPath中指定的jar包
1.2.3 ExtClassLoader 加载JAVA_HOME/jre/lib/ext
1.2.4 BootStrap 加载JAVA_HOME/lib
1.3 这里会经过双亲委派机制:
先询问AppClassLoader 是否加载过此类 ?若没有加载 则向上询问父类加载器 ExtClassLoader 是否加载过此类? 若还是没有 则向上询问BootStrapClassLoader
是否加载过此类 ?若顶级父类也没有加载 那么就返回加载此类
2 linking
2.1 verification 校验加载的类是否符合规范 验证是否以cafebaby开头 的class文件
2.2 preparation 将类静态变量初始化默认值 static int i=8 此阶段为0
2.3 resolution 将符号引用转换为直接引用
3 initlizing 静态变量附初始值 此时static i=8 然后是静态代码块
4 申请内存对象
5 成员变量附默认值 int j=9 此时的j=0 是半初始化状态
6调用构造方法<invokespecial init>
6.1 成员变量的值为初始值 j=9 a_store 建立关联 t=new T();
6.2 在执行构造方法的时候 首先会调用父类的super() 先调用父类的构造器
--------------------------------------------
当对象加载到内存的时候 先判断是否为普通对象
如果是普通对象 是由
markword对象头 8字节
classpointer 类指针 4字节 原本是8字节 但是JVM默认开启了指针压缩 -XX:+UseCompressedClassPointers
实例数据 依据成员变量类型而定 JVM默认开启对象指针压缩 -XX:+UseCompressedOops 如果是基本数据类型不压缩 引用类型8字节压缩为4字节
padding 对齐 8的倍数 如果前面不足8的倍数 padding会自动对齐
为什么是8字节对齐?------》因为L1 缓存行大小为8字节 如果不是8字节对齐 会出现缓存行污染 影响加载效率
如果是数组对象
markword 8字节
classpointer 4字节
arrLength 4字节
arrStore 看具体数组类型
padding 8的倍数 如果前面不足8的倍数 padding自动补齐
markword 主要包含哪些信息?
1 锁信息
最低两位为 锁的标志位 为了区分无锁态和偏向锁 (因为他们最低两位头像同) 倒数第三位为 是否为偏向锁 0位无锁 1 为偏向锁
对象的hashCode 25bit+分代年龄 4bit+3bit(0 01 无锁) =32
00 轻量级锁
10 重量级锁
11 GC标记
101 偏向锁
2 hashCode信息
3 gc信息
------------------------------------------
常见的垃圾回收器
1 serial 年轻代串行回收 单线程 效率高 当serial 在年轻代运行 所有工作线程需要在safePoint停下 然后serial清理垃圾 停顿时间为STW
2 serial old 老年代 单线程 算法为mark-sweep 或者是mark-compact
3 PS 新生代 多线程
4 PO 老年代 多线程
----------》PS+PO 只要不明确指定CMS 或者G1垃圾回收 默认为PSPO
5 PN 是在PS 上增强 便于结合CMS
PN+CMS 有四个阶段
1 initial mark 初始化标记 GCRoots 直接关联的对象 在线程栈变量 静态变量 JNI指针 找到他们的跟对象 并把它们标记
2 concurrent mark 并发标记 从GCRoot 对 堆中对象 进行可达性分析 找出存活对象 可以和用户线程并发执行
3 remark 重新标记 在修正并发标记期间 用户执行程序而产生的并发新垃圾
4 concurrent sweep 并发清理 在清理的过程中还会出现垃圾 只能等待下个阶段重新清理
--------------------------------------------------------------
常见的垃圾回收算法
垃圾回收算法 引用计数法 --》判断一个对象是否存在引用 但是会造成循环引用 导致内存泄露 内存泄露的堆积会导致内存溢出----》但是引用计数法效率快 可达性分析算法---》我们会从一个根的位置触发 衍生出一条引用链 进行搜索 搜索当前引用链上所有对象 什么是GCRoot(一组活跃的指针 指向堆内存的地址) :静态变量 常量 栈帧中局部变量表中的元素 JNI(java native interface) 也可以作为GCroot 判定为GC不可达对象 那么 这个对象就真的背叛死刑了吗? 不是 如果该对象重写的finalize方法 并且在finalize()方法中建立我们与跟的联系 那么就会重新进入应用阶段 图片服务器一般会重写finalize方法 如何判断对象已死?---》GC不可达且没有重写finalize()方法
标记清除算法: 缺点很慢 会产生内存碎片 而且在标记清除之间会出现 对象与引用的重新建立或者删除而导致不该回收或者该回收的没有回收 造成这种问题的原因是因为 业务线程和GC线程来回切换 并行抢cpu资源 1 标记: 找到你所有的GCroot 找到所有GC可达对象并标记 2 清除 递归遍历全堆 把所有对象中没有被标记的对象全部清除 为了解决以上问题 可以将它做成串行的GC (Stop the world) 判断一个垃圾收集器的好坏 根据stop the world 时间长短
复制算法: 效率快 但代价太大 每次会浪费二分之一内存
标记清除整理算法 先标记 再清除 后整理