关于对象

8个bit占一个字节,

对象有四个部分(说的是64位机器)

        1、对象头,占用8字节(锁状态、hashcode、对象年龄 最大15)

        2、类型指针,占用4字节(对象自己指向方法区看自己属于哪个class)

        3、实例数据,不一定

        4、对齐,JAVA对象要正好占到被8个字节整除,对齐用。

所以,创建一个对象的大小至少要16Byte字节(8+4+0+4)。

补充细节:

如果这个对象里增加了基本类型、int、long等就加上这个基本类型在补齐,

如果加上了引用类型那就加上4个字节的指针大小。

byte:1字节

short:2字节

int:4字节

long:8字节

float:4字节

double:8字节

char:2字节

boolean:1字节

reference引用:4字节

关于定位对象(通过引用找到对象)

两种:直接(默认直接定位)和句柄

直接定位就是引用保存对象的起始地址(少了一次定位,但是gc移动对象,栈中对象地址要更新)

句柄就是引用保存句柄池,句柄池在指向对象(多了一次定位,但是gc只需要更新句柄池)

关于新建对象分配

新建对象分配可能分配在堆里,可能分配在栈上

堆的Eden区:通常的对象先分配到eden区,年轻代gc存活之后在复制到其中一块survivor区,下一次gc,再把这块幸存区的和eden存活的对象复制到另一块幸存区。

堆的老年代:大对象直接晋升到老年代(一般是字符串),因为大对象如果存活太久在两个survivor复制成本高,短命的大对象也很快会占满eden,触发年轻代回收。(补充长期存活的对象1.8并发收集器默认15晋升到老年代)。

栈上:需要逃逸分析(对象的作用域逃出方法之外,比如做为返回值,被方法外引用),栈上分配的对象会随着方法的结束被销毁,栈上分配的对象成员被拆散分配在栈里。

关于TLAB

是Thread Local Allocation Buffe(线程本地分配缓存)(其实就是在堆的年轻代的eden中)

多个线程分配对象的时候为了保证线程安全,每个人自己预先在eden中占用一段。

关于对象创建的过程

分成三部分(不是三个指令,指令不止三个,且2、3可重排)

1、申请内存,设置默认值

2、调用构造方法设置初始值

  ↓  ↑

3、 返回对象引用

关于垃圾回收算法

标记复制:把内存分为两块每次使用其中一块,再把标记仍然存活的对象复制到另一块,(一次性删除整块内存的效率高)

标记清除:标记存活的对象,把剩下的空间对象清除

标记整理:标记存活的对象,把剩余空间清除,把存活对象向一侧移动整理

关于垃圾回收器

标记复制->Serial 、ParNew 、ParallelScavenge 、(G1的两个Region间复制)

标记清除->CMS

标记整理->SerialOld 、ParallelOld 、(G1的全局)

Serial:单线程年轻代

ParNew:多线程年轻代

ParallelScavenge:(1.8默认)多线程年轻代,关注吞吐量(单位时间内完成的任务数)。

CMS:以降低垃圾回收的停顿时间为目的,多线程老年代

SerialOld:单线程老年代

ParallelOld:(1.8默认)多线程老年代,ParallelScavenge老年代版本

G1:(1.9默认)面向整堆,关注回收价值,利用CPU多核优势缩小停顿时间

补充细节:

这七个垃圾回收器:G1面像整堆回收,有的Region充当年轻代有的Region充当老年代,大对象(超过一个Region50%)用一个或者连续多个Region(Humongous)来存,其余六个是分代收集

年轻代适用于标记复制且内存分配不会1比1,是1比9,因为大多数对象都是朝生夕灭,存活的对象都是少数,年轻代回收只需要复制少量对象。

内存分配方式有两种:指针碰撞和空闲列表,指针碰撞适用内存是绝对规整的这取决于垃圾收集器是否有压缩整理功能(CMS采用标记清除不规整,但是可以调整参数,让他每次回收都压缩一次变成规整的)

关于CMS和G1的四个阶段

没有能完全不StopWorld的收集器,但是能做到让StopWorld尽量的短,

CMS四个阶段:

(1)初始标记:Stop the World,只是标记一下GC Root能直接关联到的对象,速度很快。 

(2)并发标记:主要标记过程,标记过程和用户线程并发执行。 

(3)重新标记:Stop the World,修正并发标记期间因用户程序继续运作导致标记产生变动的那一部分对象的标记记录(停顿时间比初始标记长,但比并发标记短得多)。 

(4)并发清除:和用户线程并发执行,基于标记结果清理对象。(并发清除会产生浮动垃圾) 

CMS的清除需要提前20%的预留空间留给浮动垃圾(4并发清除用户线程运作产生的垃圾)。因为用户线程在并发执行,这个期间可能会占满空间造成“并发更新失败”用单线程的SerialOld做后备,会更慢。

G1四个阶段:

(1)初始标记:Stop the World,只是标记一下GC Root能直接关联到的对象

(2)并发标记:和用户线程并行执行的

(3)最终标记:完成三色标记周期,标记那些在并发标记阶段发生变化的对象(可并行不STW)

(4)筛选回收:对各个Regin的回收价值和成本进行排序,回收一部分region

每一个region的大小是1 - 32M不等,必须是2的整数次幂。每个region可以来表示Eden、幸存者0区、幸存者1区、老年代等,

注意:CMS与G1在第三阶段的不同就是,G1可以不STW,可以用其他核心继续运行用户线程


关于GCRoot都包括什么

栈里局部变量表引用的对象

方法区里静态变量、 常量,引用的对象

关于类加载

1、 加载:把class字节码加载进JVM,可以通过 .class文件、网络、jar包、内存生成等。

2、 验证:验证字节码安全性正确性,看到底是不是真正的字节码文件(.class后缀伪装),会不会对虚拟机造成危害。

3、 准备:为类的static变量赋初始值,静态final会直接赋最终值。

   ↓  ↑

4、 解析:把符号引用替换为直接引用,因为在class文件加载进虚拟机前是不知道真实地址,可能指向的目标还没背加载进JVM,只能通过指向常量池的数据结构的符号表示

5、 初始化:执行类构造器,为准备阶段赋初值的静态变量赋实际值。这里是类构造器合并static{}块产生的,并不是对象的构造方法。

注意:准备和解析可以互换,因为多态可以运行时绑定。

关于类初始化顺序

静态成员、静态块的肯定是先初始化了,且只在类加载阶段初始化一次。

普通成员、普通块、构造方法每创建一个对象初始化一次,成员和代码块在前,构造方法在后

初始化一个类的时候要先去初始化他的父类。

1、父类 静态成员、静态块

2、子类 静态成员、静态块

:这时候父子类加载完毕

3、父类 普通成员、普通块

4、父类 构造方法

5、子类 普通成员、普通块

6、子类 构造方法

:每次实例化对象重复3、4、5、6

关于类加载的双亲委派模型

        双亲委派(父亲委派)规定:子类加载器在加载一个类的时候先交给他的类加载,当父类加载器加载不了之后在交给子类加载。当发现已经加载过了直接返回已加载的引用。

每个加载器都有自己的加载目录。

        好处:核心类随着自己所在的加载目录有了更高的安全级别,不会被替换,且JVM里不会重复加载多份相同的类。

比如我们写了一个String类,应用类加载器先交给父加载器加载,父加载器在自己的目录加载了String。结果是加载了核心类的String代码,而不是我们自己在类路径写的String。

再比如应用类加载器加载MyClass类,先交给父加载器加载,父加载器在自己的目录没有找到该类,反馈加载不了,向下交给子加载器加载。结果我们自己在类路径写的类被应用类加载器加载。

加载器级别:

1、 启动类加载器:lib下

2、 扩展类加载器:lib/ext下

3、 应用类加载器:类路径下

3、 自定义加载器:自定义路径下

注意:父子类加载器的关系不是继承,而是组合。启动类加载器用c++写的。程序员能干预的加载器是自定义加载器。

关于JMM内存模型和内存结构