java虚拟机

1、JVM初识
jvm本身和其他程序一样,本身就是一段程序,运行在操作系统上。只不过是自己写的代码运行在虚拟机上而已。
在这里插入图片描述
2、JVM的体系结构
在这里插入图片描述
3、类加载机制
由.java 编译为 java.class文件,经过ClassLoader类加载器加载,完成初始化,生成Class对象,然后实例化对象。

public class Test {
    public static void main(String[] args) {
    //类是模板,对象是具体的
        Test test = new Test();
        Test test2 = new Test();
        Test test3 = new Test();
        System.out.println(test.hashCode());
        System.out.println(test2.hashCode());
        System.out.println(test3.hashCode());
        //当前类加载器 AppClassloader
        ClassLoader classLoader = test.getClass().getClassLoader();
        //扩展类加载器 ExtClassloader
        System.out.println(classLoader.getParent());
        //根类加载器 BootStractClassloader  1、要么不存在,2、要么java获取不到,调用c的类库
        System.out.println(classLoader.getParent().getParent());

    }
Connected to the target VM, address: '127.0.0.1:5779', transport: 'socket'
2094777811
984213526
400136488
sun.misc.Launcher$ExtClassLoader@3834d63f
null

在这里插入图片描述
4、双亲委派机制
双亲委派机制是一种安全机制,防止开发人员恶意的操作底层类库而造成不可预估的损失。
刚才说到类加载器一共有三个:
AppClassLoader 应用程序类加载器
ExcClassLoader 扩展类加载器
BootStracpClassLoader 根类加载器
每当我们new一个类,由.java编译为.class,接下来将.class文件交给应用程序类加载器去加载,应用程序类加载器会将向上询问自己的父类加载器 (委托父类),父类加载器然后去询问根加载器(委托父类),如果有就直接加载,优先使用根类加载器的类,优先级从大到小 :根类加载器 》扩展类 》应用程序类。如果没有直接抛出异常 Class NotFoundException

沙箱安全机制

5 、Native

凡是带了Native关键字的,说明java的作用范围达不到了,此时他去调用底层C的类库,会进入本地方法栈。
调用本地方法 本地接口JNI
JNI作用:扩展java的使用,融合不同的编程语言为java所用,毕竟早期是C 和C++的天下。
所用要想立足,必须要有调用C 、C++的程序。
java在内存区域中专门开辟了一块标记区域:Native Method Stack,登记native方法,
在执行引擎执行的时候加载Native libraies.(本地库)

在最终执行的时候,加载本地方法库中的方法通过JNI。如:调用打印机、管理系统。
    private native void start0();

6、方法区 Method Area

	方法区被所有线程共享,所有 字段和方法字节码,以及一些特殊方法,如构造函数、接口代码也在此定义,
	简单的说,所有定义的方法的信息都保存在该区域,***此区域属于共享区间***✔。
	静态变量、常量、类信息(构造方法,接口定义)、运行时的常量池存在方法区中,但是实例存在堆中,
	和方法区无关。

7 PC寄存器

程序计数器:Program Counter Register
	每个线程都有一个程序计数器,是线程私有的,就是一个指针,指向方法区中的方法字节码
(用来存储指向一条指令的地址,也即将要执行的指令代码),在执行引擎读取下一条指令,
是一个非常小的内存空间,几乎可以忽略不计。

8、 栈

栈 :先进后出、后进先出。想象成子弹的弹夹。最开始压入弹夹的子弹,最后被打出去
		这也是为什么main方法先执行,最后结束的原因。
  pc寄存器
  队列:先进先出,后进后出。想象成水管,往往都是顺序消费。
  栈:栈内存,主管程序的运行,生命周期和线程同步,线程结束,栈内存也就释放,
  对于栈来说,不存在垃圾回收问题。因为一旦线程结束,栈就结束了。
  栈:8大基本类型+对象的引用+实例的方法
  栈运行原理:栈帧
  一旦栈中满了 就会报StackOverflowError,此时程序已经停掉,无法运行。典型的像线程之间互相调用,
  且都不释放锁资源,或者递归调用。

9、堆
Heap,一个JVM只有一个堆内存,堆内存的大小是可以调节的。
类加载器读取了文件后,一般会把类、方法、常量,变量-, 保存在应用类型的真实对象;
堆内存主要还要细分三个区域:
新生区(伊甸园区)Young/New
养老区 old
永久区

新生区

	类:诞生和成长的地方,甚至死亡
	伊甸园,所有对象都是在伊甸园区new 出来的。
	幸存者区(0,1)位置上互换。

在这里插入图片描述
GC垃圾回收,主要是在伊甸园区和养老区
假如内存满了,OOM,堆内存不够了,java.lang.OoutOfMemoryError:java heap space
在JDK8以后,永久存储区改了名字(元空间)

永久区

	这个区域常驻内存的,用来存放JDK自身携带的Class对象,Interface元数据,存储的是java运行时的一些环境或者类信息,这个区域不存在垃圾回收,关闭JVm虚拟机就会释放这个区域的内存。
	JDK1.6之前: 永久代,常量池在方法区
	JDK1.7:        永久代,当时慢慢的退化了,去永久带,常量池在堆中。
	JDK1.8:		 无永久代,常量池在元空间


在这里插入图片描述

public class HeapTest {


    public static void main(String[] args) {
        //虚拟机试图使用的最大内存
        //字节 1024*1024
        long max = Runtime.getRuntime().maxMemory();
        //jvm的初始化总内存
        long total = Runtime.getRuntime().totalMemory();
        System.out.println("max=" + max + "字节\t" + (max / (double) 1024 / 1024) + "MB");
        System.out.println("total=" + max + "字节\t" + (total / (double) 1024 / 1024) + "MB");
    }
}

控制台输出
max=1771044864字节	1689.0MB
total=1771044864字节	115.0MB

假如报出来OOM:

 1、调节虚拟机堆的内存以及总内存
 2、分析内存,看一下哪个地方出现了问题
-Xms1024m -Xmx1024m -XX:+PrintGCDetails

打印信息到控制台,如下所示:

Heap
 PSYoungGen      total 305664K, used 31457K [0x00000000eab00000, 0x0000000100000000, 0x0000000100000000)
  eden space 262144K, 12% used [0x00000000eab00000,0x00000000ec9b86e8,0x00000000fab00000)
  from space 43520K, 0% used [0x00000000fd580000,0x00000000fd580000,0x0000000100000000)
  to   space 43520K, 0% used [0x00000000fab00000,0x00000000fab00000,0x00000000fd580000)
 ParOldGen       total 699392K, used 0K [0x00000000c0000000, 0x00000000eab00000, 0x00000000eab00000)
  object space 699392K, 0% used [0x00000000c0000000,0x00000000c0000000,0x00000000eab00000)
 Metaspace       used 3159K, capacity 4556K, committed 4864K, reserved 1056768K
  class space    used 329K, capacity 392K, committed 512K, reserved 1048576K

下面模拟堆内存溢出


public class TestHeap {
    public static void main(String[] args) {
        List<User> list = new ArrayList<>();
        int count=0;
        try {
            while (true) {
                list.add(new User());
                count=count+1;
            }
        } catch (Error error) {
            error.printStackTrace();
            System.out.println("count:"+count);
        }

    }
}

在编辑器中添加必要的参数命令 ,大致意思说,只要堆内存溢出,就会dump文件,建议初始化的堆内存不要设置过大,否则会很卡。

-Xms2m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError

执行完毕,查看当前项目下生成的Jporfiler文件,直接双击打开。
在这里插入图片描述

java.lang.OutOfMemoryError: GC overhead limit exceeded
Dumping heap to java_pid21192.hprof ...
java.lang.OutOfMemoryError: GC overhead limit exceeded
Heap dump file created [12397181 bytes in 0.033 secs]
	at com.riemann.springbootdemo.util.common.TestHeap.main(TestHeap.java:14)
Disconnected from the target VM, address: '127.0.0.1:9047', transport: 'socket'
count:317484

可以看到哪个类的实例数,以及最大对象的占比,此时显示arraylist占比88%,是有问题的
在这里插入图片描述
继续看线程转储,此时只有一个main方法,点击查看细节,发现main 方法中第14行出现了问题。
在这里插入图片描述
在这里插入图片描述
10、GC
jvm在进行GC时,并不是对这三个区域统一回收,大部分时候,回收都是新生代
新生代
幸存区
老年区
GC两种类:轻GC(普通的GC),重GC(全局GC)
GC算法:标记清除法、标记压缩法、复制算法、应用计数器
引用计数法:
对于每个使用的对象应用,进行计数,如果检测到对象应用计数为0时,就清除这个对象引用。
在这里插入图片描述

标记清除法
优点:不需要额外的空间。
缺点:两次扫描,严重浪费,会产生内存碎片。在这里插入图片描述
标记压缩
标记压缩是对标记清除的优化。
在这里插入图片描述
总结

	内存效率:复制算法》标记清除算法>标记压缩算法(时间复杂度)
	内存整齐度:复制算法=标记压缩算法》标记清除算法
	内存利用率:标记压缩算法=标记清除算法》复制算法
	没有最完美的算法,只有最合适的算法》GC分代收集算法

年轻代
·存活率低
·复制算法
老年代
·区域大:存活率
·标记清除(内存碎片不是太多)+标记压缩混合实现。
序消费。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值