JAVA-JVM

看图回顾

在这里插入图片描述

在这里插入图片描述


类加载器

在这里插入图片描述

  • 加载:查找并加载类的二进制数据
  • 链接
    • 验证:保证被加载的类的正确性;
    • 准备: 给类静态变量分配内存空间,赋值一个默认的初始值;
    • 解析:把类中的符号引用转换为直接引用
  • 初始化:给类的静态变量赋值正确的值;
public class Test{
    public static int a = 1;
}
// 1、加载   编译文件为 .class 文件,通过类加载,加载到JVM

// 2、连接   
	  验证(1)  保证Class类文件没有问题
      准备(2)int类型分配内存空间,a = 0;
      解析(3)  符号引用转换为直接引用

// 3、初始化
      经过这个阶段的解析,把1 赋值给 变量 a;
类的加载(static)
package com.threestooges.core.JVM.lesson1;

import java.util.UUID;

/**
 * @author ${Author}
 * @Date 2020/3/10
 * @Description:
 */
public class Lesson {
	public static void main(String[] args) {
		System.out.println(MyParent02.str);
		/**
		 * MyParent1 static
		 * MyChild1 static
		 * hello,str2
		 */
		System.out.println(MyParent03.str);
		/**
		 * hello world
		 */
		System.out.println(MyParent04.str);
		/**
		 * MyParent04 static
		 * 7a292044-36c6-4326-b3e1-31596f5e55ee
		 */
	}
}

class MyParent01{
	public static  String str = "hello world 1";

	static {
		System.out.println("MyParent01 static"); // 这句话会输出吗?
	}
	/**
	 * final 常量在编译阶段的时候 常量池;
	 * 这个代码中将常量放到了 Demo03 的常量池中。之后 Demo03与MyParent02 就没有关系了
	 */
}

class MyParent02 extends MyParent01{
	public static  String str = "hello world";

	static {
		System.out.println("MyParent02 static"); // 这句话会输出吗?
	}
}

class MyParent03 {
	public static final String str = "hello world";

	static {
		System.out.println("MyParent03 static"); // 这句话会输出吗?
	}
	/**
	 * final 常量在编译阶段的时候 常量池;
	 * 这个代码中将常量放到了 Demo03 的常量池中。之后 Demo03与MyParent02 就没有关系了
	 */
}

class MyParent04{
	/**
	 * 当一个常量的值并非编译期间可以确定的,那这个值就不会被方法调用类的常量池中!
	 * 程序运行期间的时候,回主动使用常用所在的类
	 */
	public static final String str = UUID.randomUUID().toString();

	static {
		System.out.println("MyParent04 static"); // 这句话会输出吗?
	}
}

ClassLoader 分类

1、java虚拟机自带的加载器

  • BootStrap: 根加载器 (加载系统的包,JDK 核心库中的类 rt.jar)
    •   rt.jar jdk 出厂自带的,最高级别的类加载器要加载的!	
      
  • Ext: 扩展类加载器 (加载一些扩展jar包中的类)
  • Sys/App: 系统(应用类)加载器 (我们自己编写的类)

2、用户自己定义的加载器

  • ClassLoader,只需要继承这个抽象类即可,自定义自己的类加载
	委派双亲机制:
	一个类加载器在接到加载类的请求的时候,首先他不会加载,他会委托给父类加载器,依次递归.
	如果父类加载器可以加载,就会完成加载工作并成功返回。如果父类完成不了,才自己加载。
// Demo01
public class Demo01 {

    public static void main(String[] args) {
        Object o = new Object(); // jdk 自带的
        Demo01 demo01 = new Demo01();  // 实例化一个自己定义的对象

        // null 在这里并不代表没有,只是Java触及不到!
        System.out.println(o.getClass().getClassLoader()); // null
        System.out.println(demo01.getClass().getClassLoader()); // AppClassLoader
        System.out.println(demo01.getClass().getClassLoader().getParent()); // ExtClassLoader
        System.out.println(demo01.getClass().getClassLoader().getParent().getParent()); // null

        // 思考:为什么我们刚才自己定义的 java.lang.String 没有生效?

        // jvm 中有机制可以保护自己的安全;
        // 双亲委派机制 : 一层一层的让父类去加载,如果顶层的加载器不能加载,然后再向下类推
        // Demo01
        // AppClassLoader       03
        // ExtClassLoader       02
        // BootStrap (最顶层)   01  java.lang.String  rt.jar

        // 双亲委派机制 可以保护java的核心类不会被自己定义的类所替代
    }
}

native方法

native : 只要是带了这个关键字的,说明 java的作用范围达不到,只能去调用底层 C 语言的库!
JNI : Java Native Interface (Java 本地方法接口)


程序计数器

每个线程都有一个程序计数器,是线程私有的。
程序计数器就是一块十分小的内存空间;几乎可以不计。

  • 作用: 看做当前字节码执行的行号指示器;
  • 分支、循环、跳转、异常处理!都需要依赖于程序计数器来完成!
    在这里插入图片描述

bipush 将 int、float、String、常量值推送值栈顶;

istore 将一个数值从操作数栈存储到局部变量表;

iadd

imul


方法区(Method Area)
Method Area 方法区 是 Java虚拟机规范中定义的运行是数据区域之一,和堆(heap)一样可以在线程之间共享!

JDK1.7之前

永久代:用于存储一些虚拟机加载类信息,常量,字符串、静态变量等等。。。。这些东西都会放到永久代中;

永久代大小空间是有限的:如果满了 OutOfMemoryError:PermGen

JDK1.8之后

彻底将永久代移除 HotSpot jvm ,Java Heap 中或者 Metaspcace(Native Heap)元空间;

元空间就是方法区在 HotSpot jvm 的实现;

方法区重要就是来存:类信息,常量,字符串、静态变量、符号引用、方法代码。。。。。。

元空间和永久代,都是对JVM规范中方法区的实现。

元空间和永久代最大的区别:元空间并不在Java虚拟机中,使用的是本地内存!


栈Stack(※)
栈和队列都是基本的数据结构;
队列:FIFO(First Input First  OutPut)

栈就是管理程序运行的
栈存储 基本类型和引用类型:
基本类型包括:byte,short,int,long,char,float,double,Boolean,returnAddress
引用类型包括:类类型,接口类型和数组。在这里插入图片描述

	1. 程序的运行就是压栈的过程;
	2. 栈的优势: 存取速度快,仅次于寄存器;
	3. 栈的数据不能共享;
	4. 没有回收来及的问题,只要线程结束就清空;
	5. 组成元素“栈帧”

栈帧
栈+堆+方法区关系图(Hotspot)
在这里插入图片描述


堆 heap(※)
java 7 前: 一个JVM实例是只有一个堆,堆内存大小是可以通过参数调整, 可以存类、方法、常量、保存类型引用的真实信息。

堆内存在“逻辑上”分为三部分:
在这里插入图片描述
物理上只有 新生、养老;元空间在本地内存中,不在JVM中!
GC 垃圾回收主要是在 新生区和养老区,又分为 普通的GC 和 Full GC,如果堆满了,就会爆出 OutOfMemory

新生区

新生区 就是一个类诞生、成长、消亡的地方!

新生区细分: Eden、s(from to),所有的类Eden被 new 出来的,慢慢的当 Eden 满了,程序还需要创建对象的时候,就会触发一次轻量级GC;清理完一次垃圾之后,会将活下来的对象,会放入幸存者区(),… 清理了 20次之后,出现了一些极其顽强的对象,有些对象突破了15次的垃圾回收!这时候就会将这个对象送入养老区!运行了几个月之后,养老区满了,就会触发一次 Full GC;假设项目1年后,整个空间彻彻底底的满了,突然有一天系统 OOM,排除OOM问题,或者重启;

Sun HotSpot 虚拟机中,内存管理(分代管理机制:不同的区域使用不同的算法!)

99% 的对象在 Eden 都是临时对象;

养老区

15次都幸存下来的对象进入养老区,养老区满了之后,触发 Full GC。

默认是15次,可以修改

永久区(Perm)

放一些 JDK 自身携带的 Class、Interface的元数据;

几乎不会被垃圾回收的;

JDK1.6之前: 有永久代、常量池在方法区;

JDK1.7:有永久代、但是开始尝试去永久代,常量池在堆中;

JDK1.8 之后:永久代没有了,取而代之的是元空间;常量池在元空间中;

OutOfMemoryError:PermGen 在项目启动的时候永久代不够用了?加载大量的第三方包!


堆内存调优(初识,基于HotSpot, jdk1.8)
maxMemory:	虚拟机试图使用的最大的内存量  一般是物理内存的 1/4
totalMemory:	虚拟机试图默认的内存总量 一般是物理内存的 1/64
/**
 * 默认情况:
 * maxMemory : 1808.0MB (虚拟机试图使用的最大的内存量  一般是物理内存的 1/4)
 * totalMemory : 123.0MB (虚拟机试图默认的内存总量 一般是物理内存的 1/64)
 */
// 我们可以自定堆内存的总量
// -XX:+PrintGCDetails; // 输出详细的垃圾回收信息
// -Xmx: 最大分配内存; 1/4
// -Xms: 初始分配的内存大小; 1/64

// -Xmx1024m -Xms1024m -XX:+PrintGCDetails
public class Demo01 {
    public static void main(String[] args) {
        // 获取堆内存的初始大小和最大大小
        long maxMemory = Runtime.getRuntime().maxMemory();
        long totalMemory = Runtime.getRuntime().totalMemory();

        System.out.println("maxMemory="+maxMemory+"(字节)、"+(maxMemory/1024/(double)1024)+"MB");
        System.out.println("totalMemory="+totalMemory+"(字节)、"+(totalMemory/1024/(double)1024)+"MB");

    }
}

在这里插入图片描述

/*
 * -Xmx8m -Xms8m -XX:+PrintGCDetails
 *
 * 分析GC日志:
 *
 *  [Times: user=0.00 sys=0.00, real=0.00 secs]
 * 1、GC 类型  GC:普通的GC,Full GC :重GC
 * 2、1536K 执行 GC之前的大小
 * 3、504K  执行 GC之后的大小
 * 4、(2048K) young 的total大小
 * 5、0.0012643 secs 清理的时间
 * 6、user 总计GC所占用CPU的时间   sys OS调用等待的时间   real 应用暂停的时间
 *
 * GC :串行执行 STW(Stop The World)  并行执行   G1
*/

public class Demo02 {
    public static void main(String[] args) {
        System.gc(); // 手动唤醒GC(),等待cpu的调用
        String str = "ilovecoding";
        while (true){
            str += str
                    + new Random().nextInt(999999999)
                    + new Random().nextInt(999999999);
        }
        // 出现问题:java.lang.OutOfMemoryError: Java heap space
    }
}

堆小结

在这里插入图片描述
在这里插入图片描述

  1. JVM内存划分为堆内存和非堆内存,堆内存分为年轻代(Young Generation)、老年代(Old Generation)
  2. 非堆内存就一个永久代(Permanent Generation)
  3. 年轻代又分为Eden和Survivor区。Survivor区由FromSpace和ToSpace组成。Eden区占大容量,Survivor两个区占小容量,默认比例是8:1:1。
  4. 堆内存用途:存放的是对象,垃圾收集器就是收集这些对象,然后根据GC算法回收。
    • 年轻代GC次数比较频繁,老年代GC次数比较少。
    • 关于垃圾回收:分代收集算法 不同的区域使用不同的算法
      Minor GC : 清理年轻代
      Major GC: 清理老年代;老年代存储长期存活的对象,占满时会触发Major GC=Full GC
      Full GC : 清理整个堆空间。
      所有GC都会停止应用所有线程 。
  5. 非堆内存用途:永久代,也称为方法区,存储程序运行时长期存活的对象,比如类的元数据、方法、常量、属性等。
  6. 在JDK1.8版本废弃了永久代,替代的是元空间(MetaSpace),元空间与永久代上类似,都是方法区的实现,他们最大区别是:元空间并不在JVM中,而是使用本地内存。
  7. 元空间有注意有两个参数:
    • MetaspaceSize :初始化元空间大小,控制发生GC阈值
    • MaxMetaspaceSize :限制元空间大小上限,防止异常占用过多物理内存

Dump内存快照
使用一些工具来查看:
	1、Jconsole
	2、idea debug	
	3、Eclipse(MAT插件)	
	4、IDEA(Jprofiler插件)

GC
GC四大算法

引用计数法 JVM 一般不采用这种方式

在这里插入图片描述

  • 特点: 每个对象都有一个引用计数器,每当对象被引用,则计数器+1,当引用失效,计数器-1.当计数器为0时,GC时就会被清理掉.
  • 缺点: 计数器维护麻烦; 循环引用无法处理.

复制算法

在这里插入图片描述
1、一般普通GC 之后,差不多Eden几乎都是空的了!
2、每次存活的对象,都会被从 from 区和 Eden区等复制到 to区,from 和 to 会发生一次交换;记住一个点就好,谁空谁是to,每当幸存一次,就会导致这个对象的年龄+1;如果这个年龄值大于15(默认值),就会进入养老区!
在这里插入图片描述

  • 优点:没有标记和清除的过程!效率高!没有内存碎片!

  • 缺点需要浪费双倍的空间
    Eden 区,对象存活率极低! 统计:99% 对象都会在使用一次之后,引用失效!
    推荐使用 复制算法

标记清除

在这里插入图片描述

  • 优点:不需要额外的空间!
  • 缺点:两次扫描,耗时较为严重,会产生内存碎片,不连续; 碎片过多时,当程序运行时需要分配比较大对象时,因为找不到连续的内存空间,导致会再次触发GC。

标记清除压缩

在这里插入图片描述

  • 特点:不需要额外的空间!解决内存碎片问题。
  • 缺点:耗时严重。假设这个空间中很少,不经常发生GC,那么可以考虑使用这个算法!
GC ROOT
1. 方法区中的常量
2. 类中静态属性的引用对象
3. 虚拟机栈中的引用对象
4. 本地方法栈中Native方法的引用对象
public class GCRoots{
		
     private byte[] array = new byte[100*1024*1024]; // 开辟内空间!
     private static GCRoots2 t2; // GC root;
     private static final GCRoots3 t3 = new GCRoots3(); // GC root;
    
    // 引用远远不止于此,强引用,软引用,弱引用,虚引用! 四个类的使用!
    public static void m1(){
        GCRoots g1 = new GCRoots(); //GCroot
        System.gc();
    }
    
    public static void main(String[] args){
        m1();
    }
}
垃圾收集器

串行 (STW: stop the world) :单线程工作

在这里插入图片描述

并行(多线程工作 但是也是 STW)

在这里插入图片描述

并发(回收垃圾的同时,可以正常执行线程,并行处理,但是如果是单核CPU,只能交替执行!)

在这里插入图片描述

G1 (※) (将堆内存分割成不同的区域,然后并发的对其进行垃圾回收)

G1(Garbage-First)收集器 ,面向服务器端的应用的收集器;
在这里插入图片描述
G1优点:
1、没有内存碎片!
2、可以精准空垃圾回收时间


OOM(Out Of Memory)

OutOfMemoryError:PermGen;永久代大小空间是有限的,满了报OOM
OutOfMemoryError :java heap space ;

	public static void main(String[] args) {
		HashMap<String,String> map = new HashMap<>();
		for (int i = 0; ; i++) {
			map.put(String.valueOf(i), String.valueOf(i));
			System.out.println(i);
		}
	}

java.lang.StackOverflowError

public class OOMDemo {
	public static void main(String[] args) {
		a();
	}
	private static void a() {
		a();
	}
}

java.lang.OutOfMemoryError: GC overhead limit exceeded

GC 回收时间过长也会导致 OOM;
可能CPU占用率一直是100%,GC 但是没有什么效果!

// -Xms10m -Xmx10m -XX:MaxDirectMemorySize=5m -XX:+PrintGCDetails
public class OomDemo {
    public static void main(String[] args) throws Throwable {
        int i = 0;
        List<String> list = new ArrayList<String>();
        try {
            while (true){
                list.add(String.valueOf(++i).intern());
            }
        } catch (Throwable e) {
            System.out.println("i=>"+i);
            e.printStackTrace();
            throw e;
        }
    }
}

java.lang.OutOfMemoryError: Direct buffer memory 基础缓冲区的错误!

java.lang.OutOfMemoryError: unable to create native Thread

高并发 , unable to create native Thread这个错误更多的时候和平台有关!
1、应用创建的线程太多!
2、服务器不允许你创建这么多线程!

public class TDemo {
    public static void main(String[] args) {
        for (int i = 1; ; i++) {
            System.out.println("i=>"+i);
            new Thread(()->{
                try {
                    Thread.sleep(Integer.MAX_VALUE);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            },""+i).start();
        }
    }
}
//Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread
//1、服务器线程不够了,超过了限制,也会爆出OOM异常!

java.lang.OutOfMemoryError: Metaspace 元空间报错

// -XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m
public class OomDemo {    
    static class OOMTest{}
    public static void main(String[] args) throws Throwable {
        int i = 0; // 模拟计数器
        try {
            while (true){
                i++;
                // 不断的加载对象! Spring的 cglib;
                Enhancer enhancer = new Enhancer();
                enhancer.setSuperclass(OOMTest.class);
                enhancer.setUseCache(false);
                enhancer.setCallback(new MethodInterceptor() {
                    @Override
                    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                        return method.invoke(o,args);
                    }
                });
                enhancer.create();
            }
        } catch (Throwable e) {
            System.out.println("i=>"+i);
            e.printStackTrace();
        }        
    }
}

常用jvm 参数

JVM参数有三种
1. 标配参数
2. X参数
3.XX参数

标配参数:在各种版本之间都很稳定,很少有变化
-version
-help
-showversion

X参数(了解)
-Xint # 解释执行
-Xcomp # 第一次使用就编译成本地的代码
-Xmixed # 混合模式(Java默认)

重点(XX参数之布尔型)

参数备注
-XX:+TraceClassLoading用于追踪类的加载信息并打印出来
-XX:+PrintGCDetails打印GC详细信息
-XX:+PrintFlagsInitial查看 java 环境初始默认值
-XX:+PrintFlagsFinal查看修改过的值
-Xmx(XX:MaxHeapSize)最大分配内存(默認占比1/6)
-Xms(-XX:InitialHeapSize)初始分配的内存大小; 1/64
-Xss线程栈大小设置,默认 512k~1024k
-Xmn设置年轻代的大小,一般不用动!
-XX:MetasapceSize10m初始化元空间大小,控制发生GC阈值(设置为10m)
-XX:MaxMetaspaceSize限制元空间大小上限,防止异常占用过多物理内存
-XX:+PrintCommandLineFlags -version打印出用户手动选项的 XX 选项
-XX:SurvivorRatio设置新生代中的 s0/s1 空间的比例
-XX:NewRatio设置年轻代与老年代的占比
-XX:MaxTenuringThreshold=15控制新生代需要经历多少次GC晋升到老年代中的最大阈值(默认是15)
GC相关:
-XX:+UseSerialGC串行收集器
-XX:+UseParallelGC并行收集器
-XX:+UseParallelGCThreads=8并行收集器线程数,同时有多少个线程进行垃圾回收,一般与CPU数量相等
-XX:+UseParallelOldGC指定老年代为并行收集
-XX:+UseConcMarkSweepGCCMS收集器(并发收集器)
-XX:+UseCMSCompactAtFullCollection开启内存空间压缩和整理,防止过多内存碎片
-XX:CMSFullGCsBeforeCompaction=0表示多少次Full GC后开始压缩和整理,0表示每次Full GC后立即执行压缩和整理
-XX:CMSInitiatingOccupancyFraction=80%表示老年代内存空间使用80%时开始执行CMS收集,防止过多的Full GC
-XX:+UseG1GCG1收集器
-XX:MaxTenuringThreshold=0在年轻代经过几次GC后还存活,就进入老年代,0表示直接进入老年代
-XX:MaxGCPauseMillis=100最大的GC停顿时间单位:毫秒,JVM尽可能的保证停顿小于这个时间!
jinfo:
jinfo -flag PrintGCDetails +pid查看打印GC详细信息是否开启
jinfo -flags查看所有信息
jinfo -flag MetaspaceSize +pid查看元空间大小
jinfo -flag MaxTenuringTheshold新生代存货次数
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值