JVM虚拟机篇
为什么Java被称为“与平台无关的编程语言”
Java虚拟机执行的是java字节码文件,然后再通过虚拟机上编译成平台可读的机器码,所以更换平台只需要安装对应平台的虚拟机就可以实现跨平台了。
JVM虚拟机的内存模型及其存储的内容
堆(Heap):是JVM虚拟机中内存最大的一块,唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。堆是垃圾收集器管理的主要区域。被所有的线程共享
本地方法栈(Native Methed Stack):功能和特点类似虚拟机栈,是Java调用非Java代码的接口,作用就是与操作系统和外部环境交互。(比如虚拟机实现的本地方法接口是使用C连接模型,那么当C程序调用C函数时,所需的内存就在本地方法栈中分配。)
方法区(Methed Area):又叫静态区,方法区包含所有的Class和static变量,运行时常量池都分配在方法区中,被所有的线程共享。
程序计数器(Program Counter Register):记录线程执行下一条代码的位置。每个线程都有独立的程序计数器,互不影响。属于线程的私有内存
Java栈(Java Stack):与程序计数器一样,Java栈也是线程私有的内存且生命周期也相同。每个方法执行的时候都会创建一个
栈帧(Stack Frame),用来储存局部变量,操作栈,动态链接和方法出口等。每个方法从开始执行到执行完毕,对应Java栈的压栈和弹栈。进出特点:先进后出,后进先出。
对象分配规则
1、对象优先被分配在Eden区,如果Eden区内存被占满则会触发Minor GC。
2、占用内存超过Eden所剩 内存的60%的对象直接被放入老年代。此操作是为了避免Eden区 和Survivor区之间的大量内存拷贝(新生代采用复制算法收集内存)
3、长期存活对象进入老年代。虚拟机为每个对象都定义了一个年龄计数器。这些对象每经过一次Minor GC则年龄+1,长时间(15)存活的对象会被放入老年代。
4、动态判断对象年龄,如果Survivor区中相同年龄的所有对象大小的总和超过Survivor区空间的一半,年龄大于等于该年龄的对象被放入老年代。
5、每次进行Minor GC时,JVM判断进入老年代的对象大小是否超过老年代剩余内存,如果大于老年代内存则进行Full GC。但是如果设置
HandlePromotionFailure设置,如果true则只进行Minor GC ,如果为false则进行Full GC
Java对象创建过程
1、JVM遇到新建对象的指令时,首先检查这个指令的参数是不是能在常量池中定义到一个类的符号引用 ,然后再加载这个类
2、为对象分配内存,三种方法:
1、“指针碰撞”
2、“空闲列表”
3、“本地线程缓冲分配(TLAB)”
3、将除对象头外的对象内存空间初始化为0
4、对对象头进行必须的设置
类的生命周期
1、加载,查找并加载类的二进制数据,在Java堆中也创建一个Java.lang.Class类的对象
2、连接,连接包括三个任务(验证,准备,初始化),
验证:文件格式,元数据,字节码,符号引用验证。
准备:为类的静态变量分配内存,并将其初始化为默认值
解析:把类中的符号引用转换成直接引用
3、初始化:为类的静态变量赋予初始值
4、使用:new出对象的程序使用
5、卸载:执行垃圾回收
**注:**类加载器只有自定义的可以卸载其他类加载器是不可以回收的。
Java对象的结构
Java对象由三部分组成:对象头,实例数据,对其填充
对象头:hash码,GC分代年龄,锁标识状态,线程持有的锁,偏向线程ID(一般大小为32/64 bit);另外一部分是指针类型,指向对象的类元数据类型。如果是数组,则还有一部分记录数组长度
实例数据:从父类继承的和自己定义的内容
对其填充:JVM要求对象起始地址必须是8字节的整数倍(8字节对齐)
判断对象是否可以回收的方式
1、引用计数:每个对象的引用计数属性,每新增一个引用计数加1,反之减1,为0时可以回收。方法简单但是无法解决对象互相循环引用的问题
2、可达性分析(Reachability Analysis):从GC Roots开始向下搜索,搜索走过的路径被称为引用链。当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,不可达对象。判断为垃圾对象
JVM永久代中会发生垃圾回收吗?
垃圾回收不会发生在永久代,如果永久代满了或者是超过了临界值,会触发完全垃圾回收(Full GC)。如果你仔细查看垃圾收集器的输出信息,就会发现永久代也是被回收的。这就是为什么正确的永久代大小对避免Full GC是非常重要的原因。请参考下Java8:从永久代到元数据区 (注:Java8中已经移除了永久代,新加了一个叫做元数据区的native内存区)
GC垃圾回收算法
标记-清除算法
算法分为两个阶段:
- 标记需要回收的对象
- 回收被标记的对象
当经过标记-清除算法之后,内存的碎片化比较严重,会出现大量的非连续内存。
这是Java堆需要一段连续的内存的时候仍然需要清理以满足“连续空间”的要求。所以这种方法比较基础并且效率也比较低
复制算法
他把一块内存划分为两半,每次使用其中的一半,当一块用完了,就把还在存活的对象复制到另外一块内存中,然后把原一半内存全部清空。这样就可以对整个半区进行回收,但是比较浪费内存空间,内存的使用率不高。
标记-整理算法
和标记清除算法一样,首先清除被标记的对象,然后将存活的对象向内存的一端移动。整理出来大块的连续内存空间。这样内存的使用率高。这个算法也是当前主推的GC垃圾回收算法