面试题(三)之JVM
内存分配和回收策略
1) 对象首先会进入Eden区
2) 大对象直接进入老年代
如:ByteBuffer byteBuffer = ByteBuffer.allocate(1 * 1024 * 1024);
大对象是指,需要大量连续内存空间的Java对象,典型的大对象就是很长的字符串或者大数组。
-XX:PretenureSizeThreshold可以令大于这个设置值的对象直接在老年代分配。这样可以避免在Eden区及两个Survivor区之间发生大量的内存复制
3) 长期存活对象进入老年代
对象在Survivor区中每“熬过”一次Minor GC,年龄就增加1岁,当它的年龄增加到一定程度(默认为15岁),就将会被晋升到老年代中
XX:+MaxTenuringThreshold
4) 对象年龄的动态判断
如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进 入老年代,无须等到MaxTenuringThreshold中要求的年龄(比如Survivor空间有100M,存活对象大于50M,直接将存活对象移动到老年代)
5) 空间分配担保
HandlePromotionFailure,检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次Minor GC;如果小于,或者设置不允许冒险,那这时也要改为进行一次Full GC。
JVM调优?调那些?
1)降低Full GC的频次,降低Full GC的时间 ,一天有1-2或者2-3次(应该有尽量少的Full GC)(卡顿时间不能太长,不能再高峰时间产生Full GC)可以写个定时任务在晚上进行Full GC
2)确保大多数对象“朝生夕死”
3)提高大对象进入老年代的门槛
4)避免使用大对象
死循环
死锁
非堆内存:(jvm无法控制的)
堆外内存
文件句柄
Socket句柄
资源(数据库连接,文件读写)
文件:限制文件大小,大文件拆分
网络IO:
大对象:(JVM噩梦) 老年代 到一定的百分比68% 会进行Full GC (CMS算法回收),FUll GC =垃圾回收+空间压缩(耗时大),尽量减少大对象的生存时间
jps 得到线程ID jstack class 线程ID 可以生产dump文件 用工具分析
jconsole 工具 图形化查看
垃圾回收原因 命令?
触发GC的条件?
堆内存划分为 Eden、Survivor 和 Tenured/Old 空间,如下图所示: 新生代使用 复制算法,老年代使用 标记-清理,标记-整理 算法
Minor GC(从年轻代空间(包括 Eden 和 Survivor 区域)回收内存被称为 Minor GC)
Major GC(老年代GC称为Major GC)
Full GC (Full GC是对整个堆)
JVM的YGC&FGC
YGC :对新生代堆进行GC。频率比较高,因为大部分对象的存活寿命较短,在新生代里被回收。性能耗费较小。
FGC :全堆范围的GC。默认堆空间使用到达80%(可调整)的时候会触发FGC。以我们生产环境为例,一般比较少会触发FGC,有时10天或一周左右会有一次。
什么时候会触发YGC,什么时候触发FGC?
YGC的时机:
edn空间不足
FGC的时机:
1.old空间不足;
2.perm空间不足;
3.显示调用System.gc() ,包括RMI等的定时触发;
4.YGC时的悲观策略;
5.dump live的内存信息时(jmap –dump:live)。
对YGC的 触发时机,相当的显而易见,就是eden空间不足, 这时候就肯定会触发ygc
对于FGC的触发时机, old空间不足, 和perm的空间不足, 调用system.gc()这几个都比较显而易见,就是在这种情况下, 一般都会触发GC。
STW
Stop-The-World机制简称STW,是在执行垃圾收集算法时,Java应用程序的其他所有线程都被挂起(除了垃圾收集帮助器之外)。Java中一种全局暂停现象,全局停顿,所有Java代码停止,native代码可以执行,但不能与JVM交互;这些现象多半是由于gc引起。
Full GC 会导致STW,减少Full GC 次数,减少Full GC时间
估量对象占用多大空间,计算每个字段占用空间,然后求和 约30个对象 约1KB
调优举例
java -Dapp.id=loadsdsdsncore-service -Dspring.profiles.active=PROD -Dskywalking.collector.backend_service=collector.skywalking:11800 -Dskywalking.agent.service_name= fdfdfoancore -Dskywalking.agent.namespace=bkjk-sk6 -javaagent:./skywalking-agent6/skywalking-agent.jar -Deureka.instance.metadata-map.starkGroup=prod -Xms3277m -Xmx3277m -XX:ParallelGCThreads=3 -XX:ConcGCThreads=3 -Djava.util.concurrent.ForkJoinPool.common.parallelism=3 -XX:CICompilerCount=2 -XX:+UseParallelGC -XX:GCTimeRatio=4 -XX:AdaptiveSizePolicyWeight=90 -XX:MinHeapFreeRatio=20 -XX:MaxHeapFreeRatio=40 -XX:+ExitOnOutOfMemoryError -Dserver.port=8080 -Dmanagement.port=8081 -Dlog.home=/opt/app/logs -Dfile.encoding=UTF8 -cp . -jar /opt/app/cdsdsdoadsdsncore.jar
在Java语言里,可作为GC Roots对象的包括如下几种:
a.虚拟机栈(栈桢中的本地变量表)中的引用的对象
b.方法区中的类静态属性引用的对象
c.方法区中的常量引用的对象
d.本地方法栈中JNI的引用的对象
常量池,存放两大类常量:字面量,符号引用
字面量:int m=3(字面量就是等号右边的内容,这里是3)
符号引用:1类和接口的全限定名 com.xx.controller.xxController
2字段名称和描述符 (private/public/protected)
3方法的名称和描述符(public/public/protected)
HEXEditor
javap -v 文件名
元数据:类的数据,比如有什么样的字段,什么的方法
类名应该有多长?256
对象分类
这种算法并不是一种新的算法,而是根据对象的存活周期的不同而将内存分为几块,分别为新生代、老年代和永久代。
新生代:朝生夕灭的对象(例如:方法的局部变量等)。
老年代:存活得比较久,但还是要死的对象(例如:缓存对象、单例对象等)。
永久代:对象生成后几乎不灭的对象(例如:加载过的类信息)。
内存区域
回想一下之前jvm对内存的划分,我们可能就已经猜到了,新生代和老年代都在java堆,永久代在方法区。
java堆对象的回收
现在,我们来看看分代收集算法是如何针对堆内存进行回收的。
新生代:采用复制算法,新生代对象一般存活率较低,因此可以不使用50%的内存作为空闲,一般的,使用两块10%的内存
作为空闲和活动区间,而另外80%的内存,则是用来给新建对象分配内存的。一旦发生GC,将10%的活动区间与另外80%中存
活的对象转移到10%的空闲区间,接下来,将之前90%的内存全部释放,以此类推,下面还是用一张图来说明:
解释下,堆大小=新生代+老年代,新生代与老年代的比例为1:2,新生代细分为一块较大的Eden空间和两块较小的Survivor空间,分别被命名为from和to。
老年代:老年代中使用“标记-清除”或者“标记-整理”算法进行垃圾回收,回收次数相对较少,每次回收时间比较长。
方法区对象回收
-XX:+PrintGCDetails
JVM内存结构图:
并发
Redis
Nginx
线程1结束执行线程2