1. 内存模型
线程私有:
1)程序计数器:指示程序执行位置、便于流程控制;线程切换时记录程序执行位置
2)虚拟机栈:方法执行内存模型(一次方法执行对应一个栈帧),包括局部变量表、操作数栈
3)本地方法栈:调用native方法,创建的局部变量
线程公有:
1)堆:存放对象实例 可分为
对象头:锁、GC状态、类型指针;实例数据;对象填充(对其8个倍数)
堆内存采用分代划分:主要划分为新生代和老年代,新生代又划分为eden、survivor区,整体比例为8:1:1
2)方法区:类信息、常量池
ps:class文件常量池:常量的字面量放到class文件中
运行时常量池:class常量池加载到内存的方法区
字符串常量池:内存堆中,全局,采用hash表保存字符串引用,值在堆中;intern 方法查找池中常量
3)直接内存:便于NIO类使用的内存
2. 类加载
加载:1)通过类名获取字节流 2)将文件中静态存储结构转变为方法区的运行时数据结构 3)生成class对象,作为方法区类数据的访问入口
链接:
1)验证:文件格式、元数据、字节码、符号引用校验
2)准备:为类变量分配内存,设置零值
3)解析:将常量池中的符号引用转换为直接引用
初始化:
执行类构造器方法(cinit),此方法自动收集类变量赋值、静态代码块语句,代码顺序执行
类加载器:
1)内置三个内加载器:启动类加载器(bootstrapclassloader)、最顶层类加载器,由c++实现,负责加载lib目录的jar;扩展类加载器(extensionclassloader),加载ext目录下的jar;应用程序类加载器(applicationclassloader),加载当前classpath的所有jar
2)双亲委派机制:在类加载过程中先判断是否被加载,若无则将请求委托给父类加载器,若父类加载器无法处理才由自己处理 目的:保证核心类库安全,使其不会被自定义类加载器加载
破坏双亲委派机制
1)SPI机制:以java.sql.Driver为例,Driver是jdk提供的一个接口,各大数据库厂商提供具体实现类,但是依据双亲委派机制,由引导类加载的Driver无法拿到由应用程序加载器加载的实现类,但是通过线程上下文类加载器可以实现
2)web应用类加载器:每个应用对应一个类加载器,先尝试自己加载,随后给父类加载器加载(保证用户类优先级高于容器提供的类)
JVM对象
对象创建:
1. 类加载检查:检查类是否已经被加载过
2. 分配内存:
1)堆内存规整:指针碰撞,空闲内存放一边,移动分界指针即可
2)堆内存不规整:空闲列表,维护一个内存可用列表
3. 初始化零值(变量赋为零值)
4. 设置对象头
5. 初始化:执行
ps:父类静态属性>子类静态属性>父类非静态属性(按代码顺序执行)>子类非静态属性
对象访问:
java程序访问时通过栈上reference数据结构操作对象,访问对象主要有两种方式:
1)句柄:在堆内存中划分一块句柄池,reference指向句柄池中地址,句柄负责指向对象内存地址和类对象地址
2)直接指针:reference指向对象地址,但还需通过对象去访问类对象
句柄的好处时reference存储的是稳定的句柄地址,在对象移动的时候只需要改变句柄对象指针即可;使用直接指针的好处是速度快,接生了一次指针定位的开销(hotspot使用直接指针)
GC:
判断对象是否回收:
1. 引用计数:
每个对象都有一个引用计数器,若被引用则加一,若为零则可被释放
优点:简单高效; 缺点:不能解决循环依赖
ps:强引用:不能被GC 软引用:内存不足的时候被回收 弱引用:GC时一定被回收;虚引用:和无引用一样,只是GC时受到系统通知
2. 可达性分析:
以GCRoots为起点搜索,搜索不到的则可被释放
GCRoots:1)栈帧中局部变量 2)方法区中的静态变量和常量 3)本地方法栈JNI引用对象
优点:更加精确,解决了循环依赖问题
具体来说其实经历了两次标记,首先可达性分析标记可回收对象,如果要执行finalize,则将其放入队列中,随后进行第二次标记,判断在执行finalize,是否被其他引用
缺点:实现复杂;可达性分析耗时,甚至STW
HotSpot具体实现:
1)GCRoots一般存在方法区和栈帧中,如果每次GC都去全局查找GCRoots则太费时间,因此采用空间换时间的思想,用一个oopMap结构来记录内存那个位置是GCRoots,这样GC时就不用查找
2)许多条命令的执行都会导致OopMap结构数据发生变化,如果为每次执行这些命令都生成OopMap的话,太耗费空间,因此只在指定时间点记录信息,这些时间点称为安全点
3)目前主要有两种方式让所有线程跑到最近的安全点,然后停顿下来
抢先式中断:首先将线程全部中断,然后再检查哪些没有到达安全点 ,没有到达安全点的就恢复线程,让他跑到安全点(几乎不用)
主动式中断:不直接对线程操作,仅仅简单的设置一个标志(共一个标志),各个线程执行时在安全点主动轮询这个标志,发现中断标志为真是就自己挂起
使用safepoint之后还可以进行改进,即使用“安全区域”,所谓安全区域指的是一段代码,在这段代码中,引用关系不变,因此可以生成OopMap并进行GC。当线程执行到SafeRegion后如果要离开时则要等待GC完成
准确GC?
垃圾回收算法:
2.1 标记-清除算法
首先标记所有需要回收的对象,标记回收后统一清理
缺点:1)效率问题:标记与清除过程效率不高 2)空间问题:标记清除产生大量不连续碎片
2.2 复制算法
将可用内存划分为大小相等的两块,每次只使用其中一块,当这块内存用完之后,就将还存活的对象复制到另一块,然后将已使用过一块直接清理
改进算法:将内存分为两部分,一部分是较大的Eden(占80%)和两个survivor区(10%),每次使用eden和一个survivor区,每次回收时将eden和survivor的活对象复制到另一个survivor区中,也就是只有10%的空间会被浪费
内存分配担保:并不是每次活着的对象都少于10%,因此如果另一个survivor空间没有足够空间存放存活对象将对象存放到老年代(即用老年代来担保)
优点:解决了内存碎片,可高速分配连续空间
缺点:浪费部分空间,使用效率低
2.3 标记-整理算法
首先先标记,然后将所有存活的对象都向一端移动,随后直接清理端边界以外的内存即可
优点:可有效利用堆空间,并且无内存碎片
缺点:复制开销大
垃圾收集器:
1)Serial、Serial Old
串行收集器,在进行收集的时候必须其他线程直到GC完成,
适用于单核或者比较小的堆中
2)Parallel Scavenge、Parallel Old
jdk1.8默认收集器组合
ps:新生代收集器全使用复制算法,老年代传并行收集器使用标记整理,CMS使用标记清除
G1适用于新生代、老年代



