1. JVM模型
字节码文件先通过类装载子系统装载到运行时数据区(内存区域),然后由执行引擎执行字节码文件。
堆:new出来的对象一般都在堆上
栈(线程栈,虚拟机栈):栈是每一个线程独享的,每一个线程都会有一个自己的栈。主要存放线程执行过程中的局部变量,一个方法对应一个栈帧内存区域,栈帧内存区域还包括局部变量表,操作数栈,动态链接,方法出口。
本地方法栈:
方法区:类的字节码文件被类装载子系统装载到方法区,方法区一般存放常量+静态变量+类元信息(类的结构信息,比如类有啥常量,啥方法)
2. 垃圾回收
如何定位垃圾
-
引用计数法:对对象计数,当引用数量为0时,则是垃圾。缺点:解决不了循环引用(Python用的)
-
根可达性算法:从gc Root根上开始找,找不到的都是垃圾。
-
有哪些instance是gc root:线程栈的变量、静态变量、常量池、JNI指针
-
垃圾回收触发条件![](https://img-blog.csdnimg.cn/bad208c4d2c54d8da7a1b17956e442f5.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAQW1hemluZ-S4tuaelw==,size_20,color_FFFFFF,t_70,g_se,x_16)
新建对象会优先放到eden区,当eden区满了,则触发Minor GC,通过拷贝算法把活下来的对象从eden复制到s1区,eden区空间回收。下次Minor GC时,eden和s1都要扫描,然后把活下来的对象放到s2,然后把eden和s1清空。当对象很大,eden区放不下时,放到老年代。
纠正:新生代和老年代占用内存默认1:2
Minor GC:eden园满时进行minorGC,然后将Eden区存活的对象放到from区,分代年龄+1,下次eden区满时,再次进行minorGC,将Eden和from区的存活对象放到to区(survive区分为from 和to),分代年龄+1.当分代年龄到达15时,则讲对象
Full GC:老年代满时,进行fullGC,尝试收集整个堆的垃圾对象,无法回收则溢出,否则进行垃圾收集,fullGC过程中会停掉应用线程,也就是Stop The World(minorGC也会STW,但是很快,影响不大),java虚拟机调优的目的就是减少FullGC
常见的垃圾回收算法
-
标记清除算法(Mark-Sweep):
标记成垃圾以后,直接删除;
优点:简单
缺点:碎片化严重
-
拷贝算法(Copying):
只能用一半空间,当这一半内存用完后,垃圾收集器进入,将这部分空间的可用部分转移到另一半并整理。
优点:简单
缺点:浪费内存
-
标记压缩算法(Mark-Compact):
回收的时候顺便做整理空间。
优点:没有碎片化
缺点:效率很低
垃圾回收器
分为两种,一种是gc时必须要stw的,另一种是gc线程和业务线程并发的。
3. JVM调优
定位问题
生产环境下,可以半夜执行,或者dump一份(不能直接dump,可以隔离出一台机器),或者测试环境压测执行。
流程:定位进程,定位线程,看堆栈信息,重复看对象信息,看哪些对象一直不能被回收。
命令:
top :实时显示系统中各个进程的资源占用情况。
top -Hp:
jps:显示当前系统的java进程情况及进程id
jinfo:可以用来查看 Java 进程运行的JVM参数
jstack:打印出给定的java进程ID或core file或远程调试服务的Java堆栈信息
jstat:可以查看堆内存各部分的使用量,以及加载类的数量。
jmap:打印出某个java进程(使用pid)内存内的,所有‘对象’的情况(如:产生那些对象,及其数量)
Arthas:
可以替代以上命令。
Dashboard:和top差不多
Headdump:和jmap差不多
thread:把所有线程打印出来,也可以查死锁
jvm:和jinfo差不多
例子:
在垃圾回收过程中,会进行对象动态年龄判断,如果一批对象的总大小大于survivor区域的50%,则直接把这批对象放到老年代,
如图情况,eden区满了之后,活的对象大于survivor区容量的50%,则直接放到老年代,很短时间就会触发FullGC,所以可以加大survivor区域。
JMM内存模型
方法里的变量,在JVM哪个区,对象在哪个区
4. 面试题补充
-
请解释一下对象的创建过程(半初始化):
eg : 类T,T t = new T(),构造方法中有个成员变量int m = 8
Class T{
int m = 8;
}
-
申请一块内存 ,new出一个对象,成员变量此时值为0。
-
调用构造方法,此时m赋值为8
-
在变量t和新new出的对象之间建立关联
-
-
加问DCL(Double Check Lock)单例和voltail问题(指令重排):
volitail:线程间可见+禁止重排序
重排序:cpu的乱序执行,在读等待时,运行没有依赖关系的其他指令。
-
需要,因为DCL单例会判断instance是否为空,当
-
-
对象在内存中的存储布局(对象与数组的存储不同):
普通对象:
-
markword:对象头8个字节
-
Class pointer:类型指针,标识该对象类型
-
instance data:实例数据,m的位置
-
padding:对齐、空间换时间、读写效率高
数组:
-
多一个数组长度
-
-
对象头具体包括什么?(锁、gc、hashcode)
markword:3个内容。锁信息、gc信息(age)、hashcode(最原始的hashcode)
-
对象怎么定位?(直接间接)
-
句柄方式/直接指针
区别:句柄方式方便gc,gc复制时,t不需要变。
-
-
对象怎么分配?(栈上-线程本地-eden-old)
-
不存在逃逸的小对象(对象只在该方法内存活)可以存放在栈上,此时不存在gc,效率非常高。
-
如果栈不能分配,判断是不是很大,如果很大,放到老年代。
-
如果不大,则分配在线程本地缓冲区,如果线程本地缓冲区分配不下,则分配在eden区(为了防止内存冲突,在eden区给每个线程一块内存用于存储自己线程对象,叫做线程本地缓冲区 )。
-
经历gc、将对象清除
-
-
一个Object占用多少字节?
-
markword 8个字节、class pointer (默认压缩:4字节)、padding(4字节)
-
所以16字节
-
-
Class对象是在堆中还是方法区?
-
在方法区堆。o先指向了方法区的c++实现的对象,这个对象的内部指向了堆中的O.class,为了方便反射。
-