目录
一、JVM内存模型
JVM运行时数据区
Java对象的创建过程
Java对象的内存布局
Java对象怎么定位
Java对象怎么分配
二、JVM垃圾回收
如何定位垃圾?
如何清理垃圾?
JVM堆内存分带模型
JVM常见的垃圾回收器
三色标记算法
三、面试问题
CPU突然100%问题排查
内存充裕,为什么会发生FullGC
一个Object占多少个字节
四、JVM调优
JVM参数分类
arthas
如何解决OOM问题
方法区:
1:线程共享
2:它是一个逻辑分区:JDK8之前方法区又称永久代,被分配在JVM内存中,JDK8开始方法区被分配到JVM内存区域外的内存区域(MetaSpace),原因是无需指定这块区域的大小
3:它主要存放:类元信息、运行时常量池、静态变量、JIT代码缓存、域信息、方法信息
4:类元信息:类的成员变量信息、类的方法信息、类的常量池信息等等(具体见oop-klass模型)
5:运行时常量池:
6:方法区位于MetaSpace,MetaSpace的初始大小为21M(-XX:MetaspaceSize),最大大小为系统内存的1/64(-XX:MaxMetaspaceSize)
堆:
1:线程共享;存放实例对象数据、字符串常量池和.class对象(JDK1.8之后)
2:堆内存初始为系统内存的1/64(可通通过-XX:Xms指定)最大为系统内存的1/4(可通通过-XX:Xmx指定)
虚拟机栈:
1:线程独有;每个线程使用虚拟机栈完成线程的代码执行过程
2:每个线程的栈大小,一般情况下为1024K,可以通过-XX:Xss设置
程序计数器:线程独有;存放栈中执行的下一条指令的指令地址
本地方法栈:调用Native方法时使用的栈空间
Mark Word存储了对象的hashCode、GC信息、锁信息三部分
当锁状态为11时,对象处于GC过程中
class Pointer存储了指向方法区内对象类元信息的指针
1:直接引用(HotSpot虚拟机用的是这种方式)
2:句柄方式引用
使用直接指针定位方式优点是快,使用句柄池定位方式有点是提升GC效率
1:进入栈区:对对象进行逃逸分析和大对象分析,如果对象不逃逸且不是大对象,则对象被分配进入栈区
逃逸分析:对象不会在方法外被引用
大对象:Eden区分配不下;超过-XX:PretenureSizeThreshold参数(只对Serial和ParNew两种eden区垃圾回收器有效)
2:进入TLAB区:如果TLAB区域足够装下对象则直接进入,如果装不下则根据refill_waste(JVM运行时动态维护的一个变量)会有两种情况:1:请求对象大于refill_waste时将当前TLAB区域剩余的区域用dump object填满然后新开辟一块TLAB区域存放该对象;2:请求对象小于refill_waste时直接将请求对象放入Eden区
TLAB:Thread Local Allocation Buffer;多个对象被分配到堆上时会出现指针碰撞引起冲突,此时便出现了TLAB(也可以用CAS来解决指针碰撞问题),每个线程被创建出来之后JVM会在Eden区开放一块儿内存(约占Eden区的1%)作为该线程独有的内存区域
3:进入Eden区:1里已经进行过大对象分析,所以TLAB进不去对象就直接进入Eden区了
根可达算法:从根元素(栈里引用的对象、本地方法栈引用的对象、常量池内对象、静态变量引用的对象、.class对象)开始寻找,凡是通过根元素可以搜索到的对象,都不是垃圾
1:传统垃圾回收器使用的模型(例如G1垃圾回收器就不适用新生代+老年代的模型)
2:新生代+老年代+永久代(JDK1.7)/元数据区(JDK1.8)
永久代和元数据区都是装.class对象
永久代必须指定大小限制,元数据区可以不指定大小限制
字符串常量存放在永久代(JDK1.7)/字符串常量存放在堆里(JDK1.8)
3:新生代=eden区+2个survivor区域(8:1:1)
1:YoungGC后,回收eden的大部分对象,eden区活着的对象 -> survivor0区
2:再次YoungGC后,eden区域的对象+survivor0区的对象 -> survivor1区
3:再次YoungGC后,eden区域的对象+survivor1区的对象 -> survivor0区
4:每次YoungGC回收都会给对象增加年龄,年龄到了直接进入老年代
5:每次往survivor区拷贝对象时,如果survivor区内存不够了,则对象直接进入老年代
6:这种GC的模式比较适合使用拷贝算法
4:老年代
老年代满了,则进行FullGC(新生代和老年代进行一次整体GC,采用压缩算法)
5:占用内存
1.Serial:应用于新生代,串行回收器
2.Parallel Scavenge:应用于新生代,并行回收器
3.ParNew:应用于新生代,并行回收器 ,因为Parallel Scavenge无法和CMS配合使用,因而产生
4.Serial Old:就是将Serial算法应用于老年代
5.Parallel Old:就是将Parallel Scavenge算法应用于老年代
6.CMS:应用于老年代,并发,与应用程序并行运行,降低了STW时间,复杂
7.G1(10ms):未深究,视频有1小时40分钟专门讲G1
8.ZGC(1ms):使用三色标记法
9.Shenadoah
10.Eplison
JDK1.8默认的垃圾回收器是2+5
回收对象时对对象进行三色标记,黑色、灰色、白色
存在浮动垃圾问题,CMS和G1的解决方案不同
原因:
1:cpu的内核上下文切换频率过高。上下文切换需要经历保存运行线程的执行状态、让处于等待中的线程恢复执行这2个过程,这2个过程需要CPU执行内核指令,所以频繁的切换会占据大量的CPU资源;在java中文件IO、网络IO、锁等待都会促使CPU执行上下文切换
2:CPU资源过度消耗。过多的线程和执行时间较长的业务代码,都会导致CPU资源的消耗
排查步骤:
第一步:top 找出哪个进程占用cpu
第二步:top -Hp 进程号 找出这个进程的哪个线程占用cpu
第三步:printf "%x\n" tid将线程id转换为16进制,jstack进程号 |grep 线程号 -A 30,查看堆栈信息进行分析
分析:
情况1:占用CPU的线程总是同一个
jstack查看程序堆栈,分析是哪块儿业务逻辑在消耗CPU资源
情况2:占用CPU的线程总是不断变化
此时需要挑选出几个线程进行逐个分析
有大对象;连续空间不够;方法区或MetaSpace满了;手动调用System.gc();
内存小于32G:MarkWord占8个,klass pointer占4个,对象实例数据0个,padding占4个,一共16
内存大于32G:MarkWord占8个,klass pointer占8个,对象实例数据0个,padding占0个,一共16
标准:-开头,所有hotspot版本都支持
非标准:-X开头,不是所有hotspot版本都支持 查看 java -X
不稳定:-XX开头,有可能下个版本会移除 查看 java -XX:+PrintCommandLineFlags
java -XX:+PrintFlagsInitial --查看出厂默认值
java -XX:+PrintFlagsFinal -version --查看修改更新
java -XX:+PrintFlagsFinal -version |grep HeapSize --查找指定参数配置(:=为被更新过的值)
java -XX:+PrintCommandLineFlags -version --打印命令行参数(可以看默认垃圾回收器)
dashboard:展示所有线程的cpu和mem占用,展示GC情况
thread:打印线程详细信息
trace:跟踪类或者函数的所有调用并记录时间
jad:反编译class或者method代码
redefine:在线上修改类后,javac生成class文件,redefine重新加载class文件,redefine不能给类添加新的field或method
tt:追踪某个method的详细信息,入参 返回值等等
方法1:
设置JVM参数,-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/local/tomcat,让程序发生OOM时自动生成文件
方法2:当很久才会发生一次OOM,不可能等到OOM才分析时
使用jmap -histo [进程号]命令直接分析内存中哪些实例有可能会导致OOM,不推荐该方法,因为jmap本身非常占用内存
方法3:当使用jmap -histo [进程号]需要看的东西太多时
使用jmap -dump:live,format=b,file=[文件名].hprof [进程号]生成hprof文件,然后用MAT或VisualVM分析文件