1 JVM结构
线程共享: 堆 方法区
线程私有: 程序计数器,虚拟机栈,本地方法栈
执行引擎: JIT,interpret,GC
栈中:局部变量表、操作数栈、动态连接、返回地址等。
1 怎么获取 Java 程序使用的内存?堆使用的百分比?
可以通过 java.lang.Runtime 类中与内存相关方法来获取剩余的内存,总内存及最大堆内存。
通过这些方法你也可以获取到堆使用的百分比及堆内存的剩余空间。
- Runtime.freeMemory() 方法返回剩余空间的字节数
- Runtime.totalMemory()方法总内存的字节数
- Runtime.maxMemory() 返回最大内存的字节数
2 内存溢出
除了程序计数器之外,其他地方均有可能。
OutofMemory OOM问题
堆内存,虚拟机栈都有可能,
使用-Xmx20m测试。
情况一:误用固定大小线程池 newFixedThreadPool
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(2);
System.out.println("项目开始运行:");
while (true) {
executorService.submit(new Runnable() {
@Override
public void run() {
try {
System.out.println("发送短信 :" + Thread.currentThread().getName());
TimeUnit.SECONDS.sleep(3);
System.out.println("发送短信结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
情况二:误用带缓冲池的线程池
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
System.out.println("项目开始运行:");
while (true) {
executorService.submit(new Runnable() {
@Override
public void run() {
try {
System.out.println("发送短信 :" + Thread.currentThread().getName());
TimeUnit.SECONDS.sleep(3);
System.out.println("发送短信结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
情况三:一次查询大量数据
使用分页+其他方法解决。
情况四:类加载过多
动态生成类导致内存溢出
- XX:MaxMetaSpaceSize 元空间最大大小。一般不设置的话无最大值。
stackoutflow
栈溢出,存在于虚拟机栈中,方法循环调用可能会出现此问题。
3 JVM参数
-Xmx 10240m -xms 10240m -xmn 5120 --xx:suriverRation=3 -xx:newRation=3
-xx:maxnewsize -xx:newsize -xmn
-Xss 栈的内存
4 垃圾回收
System.gc() 或Runtime.getRuntime().gc()
这两个方法用来提示 JVM 要进行垃圾回收。但是,立即开始还是延迟进行垃圾回收是取决于 JVM 的。
在 JVM 中,有一个垃圾回收线程,它是低优先级的,在正常情况下是不会执行的,只有在虚拟机空闲或者当前堆内存不足时,才会触发执行,扫面那些没有被任何引用的对象,并将它们添加到要回收的集合中,进行回收
1 垃圾回收算法
- 标记清除法: :空间碎片多。
- 标记整理法: :朝着一段靠拢,不会出现内存碎片,但是效率会低
- 标记复制法: :分为两部分区域。空间换时间
2 判断是否可回收
可达性分析,通过GC ROOT (一定不能回收的对象)的引用链查找,看对象有没有被引用到
三色标记法,黑色:已标记 灰色:正在标记 白色:还没有标记
三色标记漏标问题
记录标记过程中发生的变化,两个方法:
1 incremental update
增量更新,只要赋值发生,被赋值的对象就会被记录下来。(重新标记)
2 satb ( snap at the begin )
新加对象会被记录,被删除引用对象也会记录下来。
可达性算法中,哪些对象可作为GC Roots对象。
1、虚拟机栈中引用的对象
2、方法区静态成员引用的对象
3、方法区常量引用对象
4、本地方法栈JNI引用的对象
3 GC和分代回收算法
- GC的目标是回收无用对象,减少内存碎片,加快回收速度
- 回收区域是堆,方法区
- 具体称之为垃圾回收器
- GC大都采用分代回收算法,分为新生代,老年代
- 根据GC规模分为minor gc,major gc ,mixed gc,full gc
- MinorGC在年轻代空间不足的时候发生,MajorGC指的是老年代的GC,出现MajorGC一般经常伴有
MinorGC
FullGC有三种情况。
1、当老年代无法再分配内存的时候
2、元空间不足的时候
3、显示调用System.gc的时候。另外,像CMS一类的垃圾回收器,在MinorGC出现promotion failure
的时候也会发生FullGC
老年代存储的那些东西?
- 新生代中经历了N次垃圾回收仍然存活的对象就会被放到老年代中。
- 大对象一般直接放入老年代。
- 当Survivor空间不足。需要老年代担保一些空间,也会将对象放入老年代。
4 垃圾回收器
STW: STOP THE WORD 程序会暂停执行垃圾回收。
Parallel GC :
- 着重于吞吐量
- eden内存不足时候发生minor gc ,标记复制 stw
- old不足时full gc ,标记整理 stw
CourrentMarkSweep GC (CMS):
- 优点:并发收集,低停顿
- 更注重用户响应速度
- old并发标记,重新标记时需要stw,并发清除
- 流程: 初始标记(stw)-》并发标记-》重新标记(stw)->并发清除
- 缺点:默认启动回收线程数为(cpu+3)*4,当cpu小于4会占用系统过多资源,采用标记清除算法,会产生碎片
G1 GC:
- 核心:将内存划分成多个不同区域,每个区域都可以充当e,s,o,humongous(专门存储大对象的)
- jdk9开始,兼顾吞吐量和响应时间
- 流程:
1. 新生代回收,标记复制算法 会stw
2. 并发标记:对old并发标记,然后重新标记(需要stw)
3. 混合收集:(mixed GC) 参与复制的e,s,o,humongous,可通过设置暂停时间目标选择部分价值区域高的区域回收,标记复制 需要stw
4. fallback full gc:收集对象速度小于对象创建速度时使用。 - MaxGCPauseMillis,可以通过它设定G1的目标停顿时间,它会尽量的去达成这个目标。
- G1HeapRegionSize可以设置小堆区的大小,一般是2的次幂。
- InitiatingHeapOccupancyPercent,启动并发GC时的堆内存占用百分比。G1用它来触发并发GC周期,基于整个堆的使用率,而不只是某一代内存的使用比例,默认是45%。
Serial:
- Serial收集器是Hotspot运行在Client模式下的默认新生代收集器, 它在进行垃圾收集时,会暂停所有的工作进程,用一个线程去完成GC工作
- 特点:简单高效,适合jvm管理内存不大的情况(十兆到百兆)。
5 GC日志分析
- real 实际花费的时间,指的是从开始到结束所花费的时间。比如进程在等待I/O完成,这个阻塞时间
也会被计算在内。 - user 指的是进程在用户态(User Mode)所花费的时间,只统计本进程所使用的时间,是指多核。
- sys 指的是进程在核心态(Kernel Mode)花费的CPU时间量,指的是内核中的系统调用所花费的时
间,只统计本进程所使用的时间
2016-07-05T10:43:18.093+0800: 25.395: [GC [PSYoungGen: 274931K->10738K(274944K)]
371093K->147186K(450048K), 0.0668480 secs] [Times: user=0.17 sys=0.08, real=0.07
secs]
2016-07-05T10:43:18.160+0800: 25.462: [Full GC [PSYoungGen: 10738K->0K(274944K)]
[ParOldGen: 136447K->140379K(302592K)] 147186K->140379K(577536K) [PSPermGen:
85411K->85376K(171008K)], 0.6763541 secs] [Times: user=1.75 sys=0.02, real=0.68
secs]
6 生产上怎么配置垃圾回收
- 对堆内存大小设上限,防止内存溢出
通常,堆空间我会设置成操作系统的2/3(这是想给其他进程和操作系统预留一些时间),超过8GB的堆优先选用G1。 - 对JVM进行初步优化。比如根据老年代的对象提升速度,来调整年轻代和老年代之间的比例
- 专项优化,主要判断的依据就是系统容量、访问延迟、吞吐量等。我们的服务是高并发
的,所以对STW的时间非常敏感。
我会通过记录详细的GC日志,来找到这个瓶颈点,借用gceasy(重点)这样的日志分析工具,很容易位到问题。之所以选择采用工具,是因为gc日志看起来实在是太麻烦了,gceasy号称是AI学习分析问
题,可视化做的较好。
7 safepoint是什么?
当发生GC时,用户线程必须全部停下来,才可以进行垃圾回收,这个状态我们可以认为JVM是安全的
(safe),整个堆的状态是稳定的。
如果在GC前,有线程迟迟进入不了safepoint,那么整个JVM都在等待这个阻塞的线程,造成了整体GC
的时间变长。
什么是分布式垃圾回收(DGC)?它是如何工作的?
DGC 叫做分布式垃圾回收。RMI 使用 DGC 来做自动垃圾回收。因为 RMI 包含了跨虚拟机的远程对象的引用,垃圾回收是很困难的。DGC 使用引用计数算法来给远程对象提供自动内存管理。
5 类加载
使用HSDB查看内存中的状况
1 过程
加载
- 把类的字节码载入方法区,并创建类class对象。
- 如果此类的父类没有加载,先加载父类
- 加载是懒惰执行的
链接
- 验证 验证class的合法性,安全性
- 准备 为static修饰变量分配空间和默认值,为final static的变量设置值
- 解析 将常量池的符号引用解析为直接引用
初始化
- 执行静态代码块和非final静态变量赋值
- 是懒惰执行
final static 基本类型 不会加载初始化,引用类型会触发初始化
2 双亲委派
- boot 加载器: JAVA_HOME/jre/lib
- ext扩展加载器:JAVA_HOME/jre/lib/ext
- application加载器: 应用加载器
- 自定义加载器
双亲委派指的是优先让父加载器加载,父加载加载不到再自己加载。
方法区内存回收:
方法区类的信息由类加载器引用,除非类加载器不被使用,否则会一直存在。
3对象创建过程
4 打破双亲委派
1、Tomcat可以加载自己目录下的class文件,并不会传递给父类的加载器。
2、Java的SPI,发起者是BootstrapClassLoader,BootstrapClassLoader已经是最上层的了。它直接获
取了AppClassLoader进行驱动加载,和双亲委派是相反的。。
6 对象引用
强软虚弱
虚引用:
会产生内存泄漏问题。
public class WeakMap {
public static void main(String[] args) {
WeakMap m=new WeakMap();
m.put(1,new String("a"),"1");
m.put(2,new String("b"),"2");
m.put(3,new String("c"),"3");
m.put(4,new String("d"),"4");
System.out.println(m);
System.gc();
System.out.println(m);
}
static class Entry extends WeakReference<String>{
public String value;
public Entry(String key,String value) {
super(key);
this.value=value;
}
}
Entry[] e=new Entry[5];
public void put(int index,String key,String value){
e[index]=new Entry(key,value);
}
public String get(String key){
for (Entry entry : e) {
if(entry!=null){
String k=entry.get();
if(k!=null&&k.equals(key)){
return entry.value;
}
}
}
return null;
}
@Override
public String toString() {
String result=new String();
for (Entry entry : e) {
if(entry!=null){
result+="{key:";
result+=entry.get();
result+=",value=";
result+=entry.value;
result+="}";
}
}
return result;
}
}
7 内存分析
1 堆外内存
进程占用的内存,可以使用top命令,看RES段占用的值。如果这个值大大超出我们设定的最大堆内存,
则证明堆外内存占用了很大的区域。
使用gdb可以将物理内存dump下来,通常能看到里面的内容。更加复杂的分析可以使用perf工具,或者
谷歌开源的gperftools。那些申请内存最多的native函数,很容易就可以找到。
2调优命令有哪些?
jps jstat jmap jhat jstack jinfo
1、jps,JVM Process Status Tool,显示指定系统内所有的HotSpot虚拟机进程。
2、jstat,JVM statistics Monitoring是用于监视虚拟机运行时状态信息的命令,它可以显示出虚拟机进
程中的类装载、内存、垃圾收集、JIT编译等运行数据。
3、jmap,JVM Memory Map命令用于生成heap dump文件
4、jhat,JVM Heap Analysis Tool命令是与jmap搭配使用,用来分析jmap生成的dump,jhat内置了一
个微型的HTTP/HTML服务器,生成dump的分析结果后,可以在浏览器中查看
5、jstack,用于生成java虚拟机当前时刻的线程快照。
6、jinfo,JVM Configuration info 这个命令作用是实时查看和调整虚拟机运行参数
3 常用的调优工具有哪些
常用调优工具分为两类
,jdk自带监控工具:jconsole和jvisualvm,
第三方有:MAT(Memory AnalyzerTool)、GChisto。
1jconsole,Java Monitoring and Management Console是从java5开始,在JDK中自带的java监控和
管理控制台,用于对JVM中内存,线程和类等的监控
2、jvisualvm,jdk自带全能工具,可以分析内存快照、线程快照;监控内存变化、GC变化等。
3、MAT,Memory Analyzer Tool,一个基于Eclipse的内存分析工具,是一个快速、功能丰富的Java
heap分析工具,它可以帮助我们查找内存泄漏和减少内存消耗
4、GChisto,一款专业分析gc日志的工具