【java基础】 JVM

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

老年代存储的那些东西?

  1. 新生代中经历了N次垃圾回收仍然存活的对象就会被放到老年代中。
  2. 大对象一般直接放入老年代。
  3. 当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日志分析

  1. real 实际花费的时间,指的是从开始到结束所花费的时间。比如进程在等待I/O完成,这个阻塞时间
    也会被计算在内。
  2. user 指的是进程在用户态(User Mode)所花费的时间,只统计本进程所使用的时间,是指多核。
  3. 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 生产上怎么配置垃圾回收

  1. 对堆内存大小设上限,防止内存溢出
    通常,堆空间我会设置成操作系统的2/3(这是想给其他进程和操作系统预留一些时间),超过8GB的堆优先选用G1。
  2. 对JVM进行初步优化。比如根据老年代的对象提升速度,来调整年轻代和老年代之间的比例
  3. 专项优化,主要判断的依据就是系统容量、访问延迟、吞吐量等。我们的服务是高并发
    的,所以对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日志的工具

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值