JVM内存分配和GC

目录

说明

内存分配

一、Java虚拟机栈

二、Java堆

三、方法区

四、运行时常量池

五、程序计数器

六、本地方法栈

七、直接内存

对象内存划分

一、本地线程分配缓冲(TLAB)

二、对象创建流程

三、JVM调参

1、堆内存

2、栈内存

3、方法区

4、本地直接内存

5、其它

对象内存布局

1、对象头

2、实例数据

3、对其填充

对象访问定位

垃圾收集器

一、对象已死

二、回收方法区

三、垃圾回收算法

四、HotSpot算法

五、垃圾收集器

1、新生代GC收集器

Serial

ParNew

Parallel Scavenge

2、老年代GC收集器

Serial Old

Parallel Old

CMS

G1

GC日志

何时GC

性能监控和故障处理工具

类文件结构

虚拟机类加载机制

概念

类加载过程:

加载

验证

准备

解析

虚拟机字节码执行引擎

栈帧

局部变量表

操作数栈

动态链接

程序编译与代码优化


说明

jit编辑器:JVM读入.class文件解释后,将其发给JIT编译器。JIT编译器将字节码编译成本机机器代码
jni:java native interface

内存分配

一、Java虚拟机栈

1、生命周期与线程相同
2、是Java方法执行的内存模型,每个方法执行时会创建局部变量表、操作数栈、动态链接、方法出口等信息
3、局部变量表存放:各种基本数据类型、对象引用、returnAddress类型,所需内存空间在编译期完成分配固定。
4、long和double占2个局部变量空间(Slot),其它基本数据类型占1个

StackOverflowError:线程请求栈深度大于虚拟机运行的深度。

oom:扩展时无法申请到足够的内存。

二、Java堆

1、所有线程共享,在虚拟机启动时创建
2、唯一目的是存放对象实例
3、是各个线程共享的内存区域,不需要连续内存
4、是垃圾收集器管理的主要区域(分代收集)

5、可以处于物理上不连续的内存空间中

TLAB:多个私有的分配缓冲区

-Xmx -Xms

三、方法区

1、线程共享,用于存储虚拟机加载的,类信息、常量、静态变量、编译后的代码
2、jdk1.7 HotSpot已经把原本放在永久代的字符串常量池移除
3、可以不实现GC,常量池的回收和类型的卸载,回收率很低

PermGen space

四、运行时常量池

1、是方法区的一部分
2、存放:编译期生成的字面量、符号引用、翻译出的直接引用在类加载后存入(Class文件有类的版本、字段方法、接口和常量池等信息)
3、动态性,运行期间可能将新的常量放入常量池

五、程序计数器

1、生命周期与线程相同
2、当前线程执行字节码的行号指示器
3、线程私有内存
4、如果是正在执行的Native其值为空

六、本地方法栈

1、生命周期与线程相同
2、虚拟机使用到的Native方法服务
3、虚拟机HotSpot不区分虚拟机栈和本地方法栈

七、直接内存

1、并不是虚拟机运行时数据区的一部分
2、DirectByteBuffer对象可以作为这块内存的引用进行操作

allocateMemory,DirectMemory导致内存溢出,特征是Heap Dump文件不会看见明显异常


对象内存划分

一、本地线程分配缓冲(TLAB)

每个线程在Java堆中预先分配一小块内存。通过-XX:+/-UseTLAB设定(Thread Local Allocation Buffer)

二、对象创建流程

(普通对象,不包括Class对象和数组)
1、new对象后,检查这个指令的参数是否能在常量池中定位到一个类的符号引用,看这个符号引用代表的类是否被加载解析和初始化过,没有则要进行类加载。
2、内存划分:指针碰撞和空闲列表,由不同的GC收集器决定。(内存是否规整)
3、内存划分时线程安全问题:1、CAS配上失败重试方式。2、本地线程分配缓冲(TLAB)
4、内存分配完后,虚拟机会将分配到的内存空间初始化为0;(实例对象在java中不赋值就能使用,访问的是0值)
5、对象头存放:虚拟机对对象进行必要设置,如:对象是哪个类的实例、如何找到元数据信息、对象的哈希码、GC分代年龄等
6、执行init方法把对象按照程序员的意愿进行初始化

三、JVM调参

http://unixboy.iteye.com/blog/174173/

1、堆内存

-Xmx3550m:设置JVM最大可用内存为3550M。
-Xms3550m:设置JVM初始内存为3550m。
-Xmn2g:设置年轻代大小为2G。


2、栈内存

-Xss128k:设置每个线程的栈大小。

3、方法区

-XX:MaxPermSize 最大方法区容量
-XX:PermSize

4、本地直接内存

-XX:MaxDirectMemorySize指定,不指定默认与-Xmx一样


5、其它

-XX:+/-UseTLAB:是否使用TLAB。
-XX:+PrintGCDetails :gc时打印JVM日志

-XX:PretenureSizeThreshold=3145728 大于这个参数的对象直接进入老年代,对Serial和ParNew两个收集器有效
-XX:MaxTenuringThreshold=15 对象变为老年代的阈值,默认为15
-XX:-HandlePromotionFailure 是否冒险进入老年代。JDK6 update 24之前

对象内存布局

对象在内存中存储布局:对象头、实例数据、对齐填充

1、对象头

一部分数据:存储对象自身的运行时数据(哈希码、GC分代年龄、锁状态标记、线程持有的锁、偏向线程ID、偏向时间戳等),MARK WORD
另一部分:类型指针,对象指向它的类元数据的指针,通过这个指针来确定对象是哪个类的实例。(注:java数组对象头会记录数组长度,而普通java对象虚拟机会通过元数据信息判断对象大小)

2、实例数据

概念:正真存储的有效信息,程序中定义各种类型字段内容
存储顺序会有分配策略,long/double、int、shorts\chart、byte\boolean、opps,然后父在子前

3、对其填充

作用:起到占位符的作用
对象起始地址(对象大小)必须是8字节的整数倍,不够时,对其填充。对象头正好是8字节的一倍或两倍

对象访问定位

reference:指向对象的引用,句柄或直接指针的方式访问对象。
句柄:reference中存放句柄地址。java堆中有句柄池(存放到实例数据的指针和到类型数据的指针)。
直接指针:reference存放对象地址。java对象中有到对象类型数据的指针。

垃圾收集器


一、对象已死

1、引用计数算法
2、可达性分析算法,和GC Roots有关联的对象都不会被回收
3、引用:

强引用(GC不回收),如:Object obj = new Object();

软引用(OOM前,将把对象放进回收范围中,内存不够时下次GC回收),SoftReference实现软引用

弱引用(下次GC回收),WeakReference实现弱引用

虚引用(不对对象生命周期有任何影响,也无法通过虚引用获得对象实例,作用是GC时有一条日志信息)PhantomReference实现

4、是否死亡:(两次标记)
没有与GC Roots相连
是否有必要执行,finalize()方法,该方法只会被执行一次。

二、GC Roots

可作为gc roots的对象包括

1、虚拟机栈引用的对象

2、方法区中类静态属性引用的对象或常量引用的对象

3、本地方法栈中JNI(一般指Native方法)引用的对象

三、回收方法区

1、永久代回收:废弃常量和无用的类

2、字面量、符号引用与堆中对象的回收类似

3、判断废弃常量:如常量池中有“abc”字符串,但是系统中没有任何一个String对象叫做“abc”;

4、判断无用类:
类的所有实例被回收
加载该类的ClassLoader被回收
该类对应的java.lang.Class对象不被引用(不能通过反射访问该类方法)

5、-Xnoclassgc进行控制是否对类进行回收
补充:堆中尤其是新生代,GC一般可回收75%-95%的空间

三、垃圾回收算法介绍

1、标记清除算法:标记和清除两个过程的回收效率不高、回收后会存在空间碎片

2、复制算法(解决标记清除算法的效率问题)
新生代使用该算法进行gc回收,因为新生代的对象回收率高,gc时移动到另一个Survivor的对象少,所以使用这个算法效率高。
HotSpot默认Eden和Survivor大小比例是8:1,gc时把Eden和Survivor中存活的对象移动到另一个Survivor中,然后把可回收的对象进行回收。

需要老年代的内存进行担保:gc时另一个Survivor无法存放存活的对象,则把这些存活的对象直接放到老年代中。

3、标记整理算法(基于老年代特点):存活的对象向一端移动,清理掉端边界以外的内存。

4、分代收集算法(商业VM都采用该方法)
内存划分为年轻代(使用复制算法)和老年代(使用标记整理算法/标记清除算法)

四、HotSpot算法(如何发起内存回收)

枚举根节点
1、可达性分析法时需要GC停顿(Stop The World)
2、考虑到效率问题:不需要一个不漏的检查完所有上下文和全局的引用位置使用OopMap的数据结构记录引用位置
3、HotSpot:OopMap的数据结构,类加载完成时计算什么偏移量上是什么类型的数据,JIT编译过程中在特定位置记录下栈和寄存器中哪些位置是引用。

HotSpot没有为每条指令生成OopMap,而是这特定的位置记录了信息,这个位置就是安全点。

如何解决不是每一个地方都可以停顿下来资执行GC的问题?

安全点:程序可以停顿下来开始GC的地方
1、抢先中断:先中断线程,如果发现不在安全点上的线程则恢复线程让它跑到安全点上。注意:没有虚拟机使用抢险中断
2、主动中断:先设置轮询标志,各个线程轮训这个标志,为true则中断,轮询标志地方和安全点重合,另外加上创建对象需要分配内存的地方(需要暂停线程时,把该内存页设置为不可读)

如果线程中断不执行,不能立马到达安全点呢?(如正在sleep或blocked)

安全区域:在一段代码片段中,引用关系不会发生变化

这个区域开始GC是安全的,GC发起时,不用管安全区域里的线程,当线程要离开时,GC完成了,收到安全离开信号则可以继续执行,否则必须等待直到收到可以安全离开的Safe Region信号。
 

五、垃圾收集器

概念:内存回收的具体实现

1、新生代GC收集器

Serial

Serial:新生代收集器,单线程,一般用于Clinet模式下的虚拟机,垃圾收集时会暂停所有用户线程,复制算法

ParNew

ParNew:新生代收集器,是Serial的多线程版本,默认启动线程数为CPU数,一般用于Server模式下的虚拟机,复制算法
使用-XX:+UseConcMarkSweepGC选项后的默认收集器,也可以用-XX:+UseParNewGC来强制指定它。-XX:ParallelGCThreads限制垃圾收集的线程数。

Parallel Scavenge

Parallel Scavenge:新生代收集器,复制算法,并行多线程收集器,更关注吞吐量(运行用户代码时间/(运行用户代码时间+垃圾收集时间)),有自适应调节策略,复制算法
-XX:MaxGCPauseMillis:尽可能保证GC时间不超过这个值,但是会牺牲吞吐量和新生代空间(值为大于0的毫秒数)
-XX:GCTimeRatio:设置吞吐量大小,默认为99,GC占总时间的(1/(值+1))。值大于0小于一百
-XX:UseAdaptiveSizePolicy:动态调整最适合停顿时间或最大吞吐量(自适应调节策略)

2、老年代GC收集器

Serial Old

Serial Old:Serial的老年代版本,单线程,CMS收集器的后备方案,标记整理算法

Parallel Old

Parallel Old:Parallel Scavenge老年代版本,多线程,标记整理,吞吐量优先的情况下考虑使用(Parallel Old加Parallel Scavenge),标记整理算法。

CMS

CMS:老年代,标记清除算法,用以获得最短回收停顿。jdk1.5发布
初始标记:程序停顿,时间短,标记一下GC Root能直接关联到的对象
并发标记:不停顿,GC Root追踪过程
重新标记:停顿,时间比初始标记长,比并发标记短,修正并发标记时的标记记录
并发清除:不停顿

缺点:
1.CPU资源敏感,CMS默认启动的回收线程数(CPU数+3)/4,cpu小于4时,对程序运行速度影响大。
2.无法处理浮动垃圾(并发清除时用户线程产生的垃圾),并发标记时程序还在执行会留有一部分内存给程序,该内存不够则Concurrent Mode Failure,然后导致启动Serial Old重新进行老年代垃圾收集,这时耗时会很长。
和其它收集器不一样,不等待老年代满就GC,-XX:CMSInitiatingOccupancyFraction,触发GC的百分比。
3.标记清除算法,会有空间碎片,没有连续内存而提前触发Full GC
-XX:+UseCMSCompactAtFullCollection,默认开启,要Full GC时启动内存合并整理,但此时程序不能并行执行,GC时间变长了
-XX:CMSFullGCsBeforeCompaction,执行多少次不压缩内存后,跟着来一次压缩内存,默认为0,即每次要Full GC时启动内存合并整理。

G1

G1:面向server,多个大小相等的Region,回收价值最大的Region垃圾堆积价值,故称为Garbage-First
并发与并行:程序可同时运行
分代收集:不同方式处理新对象、存活一段时间对象、旧对象
空间整合:整体看是标记整理算法,局部(两个Region)是复制算法
可预测停顿:可预测停顿时间模型,用户能明确指定M毫秒的时间片段内,GC时间不超过N毫秒

Remembered Set (避免全表扫描
原理:每个Region有一个与之对应的Remembered Set,JVM发现程序对Reference类型数据进行写操作时,会产生一个Write Barrier暂时中断写操作,检查Reference引用的对象是否处于不同的Region中,如果是通过CardTable把相关引用信息记录到被引用对象所属的Remembered Set中,GC Root枚举范围加入Remembered Set。

注意:其它收集器,新生代和老年代间对象引用,jvm也是使用Remembered Set来避免全堆扫描。

步骤:
初始标记:程序停顿,时间短,标记一下GC Root能直接关联到的对象
并发标记:不停顿,GC Root可达性分析,找出存活对象
最终标记:程序停顿,并执行,修正,变化记录在线程Remembered Set Logs里,然后合并到Remembered Set中
筛选回收:程序停顿,根据Region价值成本和期望GC停顿时间制定回收计划(程序可不停顿,但是停顿,GC效率很高)

 注意:

1、从G1开始,最先进的垃圾收集器的设计导向都不约而同地变为追求能够应付应用的内存分配速率 (Allocation Rate),而不追求一次把整个Java堆全部清理干净。这样,应用在分配,同时收集器在收集,只要收集的速度能跟得上对象分配的速度,那一切就能运作得很完美。

2、G1相比CMS缺点是:

  • remember set 对对象引用关系的算法计算更复杂,需要消耗更多的运算资源。
  • 要维护多个remember set,会占用更多的对内存(越为整个堆的20%)。

 

GC日志与触发时机

GC日志

serial-----DefNew
ParNew-----ParNew
Parallel Scavenge-----PSYoungGen

实例:

33.125:[Full GC [DefNew: 3324K->152K(3712K), 0.0025925 secs]  3324K->152K(11904K), 0.0031680 secs]

jvm启动以来进过的秒数:33.125s

Full gc 说明出现了 stop the world,而不是老年代的意思

ParNew收集器:已使用容量3324K,GC后该区域使用容量152K(该内存区域内总容量3712K),gc占用时间0.0025925s

java堆已使用容量3324K,GC后堆使用容量152K(堆总容量11904K),gc时间0.0031680s

[Times: user=0.01 sys=0.01 ,real=0.02]  user:用户时间 sys:内核态时间 real墙钟时间:包括非运输等待时间 

何时GC和对象进入老年代

1、何时Minor GC

大多数情况下,对象在Eden区中分配,当Eden没有足够空间时,虚拟机发生一次Minor GC,
要避免连续短生命周期的大对象情况出现,Serial和ParNew收集器可以调整多大对象直接进入老年代(-XX:PretenureSizeThreshold=3145728)。


2、对象年龄判定
移动一次年龄增加1,大于15则进入老年代(-XX:MaxTenuringThreshold设置)。
另外Survivor空间中相同年龄对象空间的和如果大于Survivor空间的一半,年龄大于或等于此值的对象进入老年代。

3、空间分配担保
Minor GC前,老年代连续内存空间小于新生代所有对象总空间,HandlePromotionFailure为true(开启空间分配担保),且老年代连续内存小于以往晋升老年代平均对象大小,进行一次Full GC.

4、jdk6 update24后:空间担保机制无效老年代连续空间大小大于新生代对象总大小或历次晋升老年代的平均值,就会进行Minor GC,否则进入Full GC

性能监控和故障处理工具

jps、jstat、jinfo、jmap、jhat、jstack、HSDIS
JConsole、VisualVM

jstat:主要用于监控虚拟机的各种运行状态信息,如类的装载、内存、垃圾回收、JIT编译器等.

jstat –gc 20445 1 20 (每1毫秒查询一次进程20445的垃圾回收情况,监控20次)

jinfo:实时查看虚拟机各项参数信息。

jps -v :查看虚拟机在启动时被显式指定的参数信息

jmap:生成堆快照 -dump:对应的dump信息。-heap:对应的堆信息。

jstack:jvm当前时刻线程快照。jstack -l 20445


类文件结构


概念:
全限定名:com/test/MyTest
简单名称:没有类型和参数修饰的方法或者字段名称
描述符:用来描述字段的数据类型,方法的的参数列表(包括数量、类型以及顺序)、和返回值
volatile:是否强制从主内存读写,不能修饰方法
transient:是否被序列化,不能修饰方法
编译器自动添加的方法:类构造器<clinit>方法、实例构造器<init>方法

1、Class文件包含jvm指令集和符号表以及一些辅助信息
2、字节码:所有平台统一使用的程序存储格式
3、Class文件是一组以8位字节为基础单位的二进制流,按照高位在前的方式分割成若干个8位字节进行存储。
4、存储数据:伪结构形式,它有两种数据类型:无符号数(u1、u2、u3、u4、u8)和表(多个无符号数或表构成,习惯以_info结尾)。
5、集合:前置的容器计数器加若干连续数据项

魔数:每个Class文件的头4个文件。用以确认这个文件是否是一个能被虚拟机接收的Class文件。
1、魔数的前4个字节存储的是Class文件的版本号,5、6字节是次版本号,7、8字节是主版本号。
2、版本号从45开始。如:java1.1-java1.7为45-51。

常量池:Class文件中的资源仓库,存放字面量(类似于常量如文本字符串、声明final的常量)和符号引用(类和接口的全限定名、字段名称和描述符、方法名称和描述符)
1、容量计数从1开始,常量池入口放置u2类型的数据代表容量计数值
2、jvm运行时从常量池获得对应的符号引用,在加载类文件时进行动态链接
3、每一项常量都是一个表,java1.7后有14种不同的表结构

访问标志(access_flags):类或者接口层次的访问信息
1、两个字节
2、共有16个标志位

类索引(this_class)、父类索引(super_class)、接口索引集合(interfacs)

字段表集合(field_info):类或接口声明的变量
1、结构:访问标志、名称索引、描述符索引、属性表集合

方法表集合
1、结构:访问标志、名称索引、描述符索引、属性表集合

属性表集合:在字段表集合后,用于存储一些额外的信息
1、类文件、方法表、字段表中,描述某些场景的专有信息

字节码指令:
1、一个字节长度,代表特定操作含义的数字(操作码)和跟随其后的零至多个代表操作所需参数(操作数)构成
2、jvm面向操作数栈,所以大多数指令只有一个操作码

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值