0%

Java虚拟机

内存分配

  • 程序计数器
  • 虚拟机栈(java方法服务)与本地方法栈(native方法服务)
  • 堆区 存放实例对象 可达性算法
  • 方法区 (已被加载的类信息,常量,静态变量)运行时常量池(方法区一部分)
  • 直接内存(NIO)

对象创建:

new 对象
检测常量池符号引用是否存在
检测这个类是否加载,解析初始化

  • 堆分配内存(指针碰撞法,空闲列表法) CMS使用 空闲列表
  • 并发 CAS实现原子性,每个线程分配内存, TLAB实现线程同步安全
  • 内存初始化为0;(实例字段不赋初值也可以用)
  • 虚拟机层面完成了对象初始化,之后来执行代码初始化
    对象头(hashcode, GC分代年龄,锁标志,线程持有锁, 时间戳等)

OOM (out of memory)

堆区(new object)
栈区(stackOverflow, 可以多并发一直建Thread导致OOM)
方法区与常量池区(一直new String(i++).intern()
直接内存溢出:NIO

垃圾回收

程序计数器,虚拟机栈,本地方法栈随线程自动回收

可达性算法的GC root:
虚拟机栈引用对象
方法区中静态对象
方法区常量引用对象
JNI引用对象(native方法)

相互引用要看是否挂在root上。如果是独立的循环闭环,则也会被GC。

引用类型:

  • 强引用
  • 软引用
  • 弱引用
  • 虚引用

// 可达性分析不可达的对象 要先判断是否finalize() (不同于try finally)

垃圾回收算法

  1. 标记清除算法(低效率,内存碎片)
  2. 复制算法(内存缩小一半)
    Eden survivor0 ->survivor1 , 回收Eden survivor0 (E:s0:s1 8:1:1)
  3. 标记整理
  4. 分代回收

垃圾级收集器

  1. Serial收集器
  2. ParNew收集器 serial收集器的多线程版本
  3. Paraller Scavenge收集器 复制算法
  4. Serial old 标记整理算法
  5. Parallel Scavenge
  6. CMS 老年代收集器 标记清除算法 造成内存碎片
  7. G1 标记整理算法 降低停顿时间
    垃圾收集器:
    {
    ParNew 复制算法
    Parallel Scavenge自适应收集器
    CMS收集器并发收集器 老年代收集器 标记清除算法 内存碎片 内存整理
    G1收集器 标记整理 Region 并发标记 清除
    }

内存分配与内存回收策略

  1. Eden区中分配
  2. Eden区不够时 Minor GC 或者 Eden : survivor0 : survivor1 = 8:1:1
  3. 大对象直接进入老生代
  4. 长期存活的对象将进入老生代(年龄计数器)
    Full GC:
    a) 年老代(Tenured)被写满
    b) 持久代(Perm)被写满
    c) System.gc()被调用
    d) 上一次GC之后Heap的各域分配策略动态变化

-Xss调整内存大小

JVM调优

  • 新生代GC频繁发生,很明显是由于虚拟机分配给新生代的空间太小而导致的,Eden区加上一个Survivor区还不到35MB。因此很有必要使用Xmn参数调整新生代的大小。
  • 老生代频繁扩容引起时,调大老生代内存,每次Full Gc老年代都会扩容,但是有时是没有必要的,所以-XX设置成固定值

SafePoint每次垃圾收集用户线程必须到最近的safepoint挂起然后等待回收,之后线程恢复

虚拟机执行字节码,字节码中包含操作码

虚拟机类加载机制

类型的加载,链接(验证、准备、解析),初始化

类加载时机{
加载、验证、准备、解析、初始化、使用和卸载
}

类加载触发:

  1. 读取一个New关键字实例化的时候,读取设置静态字段,调用静态方法(final修饰的静态字段,已经在编译期放在了常量池除外)(static修饰的静态方法)
  2. 反射调用的时候,如果没有初始化,则初始化
  3. 父类没有初始化,则进行初始化
  4. 先进行主类main初始化

静态变量会导致类的初始化,常量则直接在常量池中,不会导致类的初始化

类加载详细过程(方法区):

  1. 通过类名获取二进制流
  2. 静态存储结构-》方法区
  3. 内存中生成对象,作为方法区的访问入口
    加载器 启动类加载器,扩展类加载器,应用程序类加载器

验证:保证运行安全(NoSuchMethodError)

准备:方法区分配内存给静态变量,(不包括实例化对象)

解析:将常量池中的符合替换用直接引用
类和接口解析,字段解析,方法解析

初始化阶段
执行类构造器

双亲委派模型

类加载器的双亲委派模型:除了顶层的启动类加载器,其余类都应有自己的父类加载器
过程:如果一个类加载器收到了类加载请求,首先委派给父类加载器完成。Object为顶层加载器。这样可以统一Object类避免造成混乱

破坏双亲委派模型:父类需要调用子类的时候
例如调用JDBC的JDK driver connector 会使用SPI破坏双亲委派模型,线程上下文加载器

虚拟机字节码执行引擎

运行时栈帧

  • 局部变量表
  • 操作数栈
  • 动态连接
  • 方法返回地址

局部变量表:存放方法参数,方法内部定义的局部变量

编译

  • 解析与填充符号表 ->语法树 tree结构
  • 注解处理器
  • 语义分析与字节码生成 执行方法

语法糖

  • 泛型和类型擦除
  • 自动装箱、拆箱和循环遍历

运行与测试
Javac -processer

编译优化技术

  • 公共子表达式消除
  • 数组边界检测消除
  • 方法内联
  • 逃逸分析(如果一个方法没有被其他外部方法调用,则可以分配到栈内存,只供当前线程使用,否则就分配到堆内存,线程共享)

线程安全

Synchronized 内部使用monitorenter monitorexit 和锁计数器来实现
RentrantLock重入锁
CAS

volitle防止指令重排

volatile关键字通过提供“内存屏障”的方式来防止指令被重排序,为了实现volatile的内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。

大多数的处理器都支持内存屏障的指令。

对于编译器来说,发现一个最优布置来最小化插入屏障的总数几乎不可能,为此,Java内存模型采取保守策略。下面是基于保守策略的JMM内存屏障插入策略:

锁优化

自旋锁和自适应锁
锁消除(依靠逃逸分析技术,判断如果数据不会被其他线程访问)
锁粗化 内部操作锁比较多,就直接给整体加一个锁
轻量级锁CAS操作
偏向锁