在通用的计算机体系中,程序计数器用来记录当前正在执行的指令,在JVM中也是如此。程序计数器是线程私有,所以当一个新的线程创建时,程序计数器也会创建。由于Java是支持多线程,Java中的程序计数器用来记录当前线程中正在执行的指令。如果当前正在执行的方法是本地方法,那么此刻程序计数器的值为undefined。注意这个区域是唯一一个不抛出OutOfMemoryError的运行时数据区。
线程私有
每个线程运行时所需要的内存,成为虚拟机栈
每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存
每个线程是能有一个活动栈帧,对应着当前正在执行的那个方法
栈内存的大小可以有两种设置,固定值和根据线程需要动态增长。
在JVM栈这个数据区可能会发生抛出两种错误。
一个支持native方法调用的JVM实现,需要有这样一个数据区,就是本地方法栈,Java官方对于本地方法的定义为methods written in a language other than the Java programming language,就是使用非Java语言实现的方法,但是通常我们指的一般为C或者C++,因此这个栈也有着C栈这一称号。一个不支持本地方法执行的JVM没有必要实现这个数据区域。本地方法栈基本和JVM栈一样,其大小也是可以设置为固定值或者动态增加,因此也会对应抛出StackOverflowError和OutOfMemoryError错误。
在HotSopt虚拟机中直接就把本地方法栈和Java栈合二为一。
线程私有
JVM调用本地方法时,给本地方法的一个内存空间
本地方法: 被native修饰,没有方法体,底层是用c/c++编写
堆数据区是用来存放对象和数组(特殊的对象
)。堆内存由多个线程共享
。堆内存随着JVM启动而创建。众所周知,Java中有一个很好的特性就是自动垃圾回收。垃圾回收就操作这个数据区来回收对象进而释放内存。如果堆内存剩余的内存不足以满足于对象创建,JVM会抛出OutOfMemoryError错误。
它是线程共享的,堆中对象都需要考虑线程安全的问题
有垃圾回收机制
方法区在JVM中也是一个非常重要的区域,它与堆一样,是被线程共享
的区域。在方法区中,存储了每个类的信息(包括类的名称、方法信息、字段信息)、静态变量、常量以及编译器编译后的代码等。
在Class文件中除了类的字段、方法、接口等描述信息外,还有一项信息是常量池,用来存储编译期间生成的字面量和符号引用。
在方法区中有一个非常重要的部分就是运行时常量池,它是每一个类或接口的常量池的运行时表示形式,在类和接口被加载到JVM后,对应的运行时常量池就被创建出来。当然并非Class文件常量池中的内容才能进入运行时常量池,在运行期间也可将新的常量放入运行时常量池中,比如String的intern方法。
当一个类加载器收到类加载请求的时候,不会立即去加载,而是让父类加载器
去加载,如果父类也没有加载,继续往上抛出,直到被加载
优点:避免了类重复加载
**打断双亲委派:**重写findclass方法
首先GCROOT 会判断并标记对象是否需要被清理.
1. 标记清理:
把需要回收的对象直接回收,这样会导致内存碎片(回收的时候会产生内存间隙,比如: 回收掉两个1kb的对象,这样就会多出来两个不相邻的1kb空间,如果此时进来一个2kb的对象,就会放不进去)
2. 标记整理:
把需要回收的对象直接回收,然后所有对象位置前移,填补空间,这样会导致内存消耗太大
3. 复制:
把内存一分为二,在第一个内存中不需要回收的对象复制到第二个内存中,缺点:需要双倍内存 占空间
分为yang年轻代和old老年代
该区分为 e伊甸园区 s0 s1 三个区,比例为8:1:1
当垃圾回收超过六次
依然没有被回收的对象,或者大对象会被放到old区
当old区满了的时候,old区和yang区会同时进行gc(full gc 这时候会导致STW,STW:整个业务停摆去专门gc) ,full gc使用标记清理和标记整理方法
为了避免yang区和old同时塞满导致的full gc,old区有一个回收阈值,可以自调节,当达到阈值时就会边放入对象边回收。
垃圾回收的并发标记阶段,gc线程和应用线程是并发执行的,所以一个对象被标记之后,应用线程可能篡改对象的引用关系,从而造成对象的漏标、误标,其实误标没什么关系,顶多造成浮动垃圾,在下次gc还是可以回收的,但是漏标的后果是致命的,把本应该存活的对象给回收了,从而影响的程序的正确性。
为了解决在并发标记过程中,存活对象漏标的情况,GC HandBook把对象分成三种颜色(三色标记算法)
垃圾回收线程间断扫描对象并完成标记,期间会有业务线程执行
假设有三个对象,关系为: a的子类b b的子类d
黑色: 自身以及可达对象都已经被标记
灰色: 自身被标记,可达对象还未标记
白色: 还未被标记
当垃圾回收线程再次回来的时候,会跳过黑色标记a,去扫描灰色b和白色d
如果在业务线程执行过程中 灰色b指向白色d的引用消失了(引用=null),并且把白色d引用指向黑色a,这个时候就会出现回收bug:
这个时候的垃圾回收线程是不知道以用已经改变了的,他以为白色d是垃圾,就会把d回收,这个时候业务代码a.d就会空指针
所以,漏标的情况只会发生在白色对象中,且满足以下任意一个条件:
对于第一种情况,利用post-write barrier,记录所有新增的引用关系,然后根据这些引用关系为根重新扫描一遍
对于第二种情况,利用pre-write barrier,将所有即将被删除的引用关系的旧引用记录下来,最后以这些旧引用为根重新扫描一遍
CMS算法解决方案: 在每次操作对象引用的时候做一个写屏障(其实是切面)
,动态更新对象三色状态,避免回收引起的业务bug
CMS会有一个remark阶段,必须从头到尾扫一遍,如果old区阈值满了会引起stw(业务停摆,全面gc)
G1解决方案: 如果灰色和白色的引用消失了,会把这个引用指向放到专门的堆栈里,下次垃圾回收线程回来的时候先去看堆栈里有没有新加的引用指向,有的话再次扫描标记。
使用三色 + G1 + SATB 可在并发标记过程中新分配对象不会漏标,边放对象边清理垃圾
SATB全称snapshot-at-the-beginning,由Taiichi Yuasa为增量式标记清除垃圾收集器开发的一个算法,主要应用于垃圾收集的并发标记阶段
,解决了CMS垃圾收集器重新标记阶段长时间STW的潜在风险
。
SATB保证了在并发标记过程中新分配对象不会漏标
是region结构, 物理分区 逻辑分片
吞吐量大
响应速度200ms 对stw进行控制
灵活:分region回收,优先回收花费时间少,垃圾比例高的region
G1中如何解决漏标?
如果说Serial GC 是年轻代中的单线程垃圾收集器,那么ParNew收集器则是Serial收集器的多线程版本
ParNew 收集器除了采用并行回收
的方式执行内存回收外,两款垃圾收集器之间几乎没有任何区别。
ParNew收集器在年轻代中同样也是采用复制算法、“Stop-the-World”机制。
ParNew是很多JVM运行在Server模式下新生代的默认垃圾收集器。
由于ParNew收集器是基于并行回收,那么是否可以断定ParNew收集器的回收效率在任何场景下都会比Serial GC收集器更高效?
ParNew收集器运行在多CPU的环境下,由于可以充分利用多CPU、多核心等物理硬件资源优势,可以更快的完成垃圾收集,提升程序的吞吐量。
但是在单个CPU的环境下,ParNew收集器不比Serial 收集器更高效,虽然Serial 收集器是基于串行回收,但是由于CPU不需要频繁的切换,因此可以有效避免多线程交互过程中产生的一些额外开销
。
除了Serial外,目前只有ParNew GC能与CMS收集器配合工作。
在程序中,开发人员可以通过选项“-XX:+UserParNewGC”手动指定使用ParNew收集器执行内存回收任务,它表示年轻代使用并行收集器,不影响老年代。
-XX:ParallelGCThreads 限制线程数量,默认开启和CPU数据相同的线程数。