目录
1.JVM
JVM : Java 虚拟机。它只认识 xxx.class 类型的文件,将 class 文件中的字节码指令进行识别并调用操作系统向上的 API 完成动作。
JVM 是 Java 能够跨平台的核心,原因在于JVM 只能认识字节码,针对不同的系统有不同的 jvm 实现,但是同一段代码在编译后的字节码是一样的。调用系统功能的代码是唯一的,和系统无关,编译生成的字节码也是唯一的。但是同一段字节码,在不同的 jvm 实现上会映射到不同系统的 API 调用,就可以实现代码的不加修改即可跨平台运行的功能。
2.JVM的位置
电脑的最底层是硬件,包括(intel,Spac…等)
中间是操作系统,包括(Windows,Linux,Mac等)
最上面是软件层,JRE和众多软件在这一层并列, 而JVM被包含于JRE里面 ,我们运行的java程序在JVM之上运行。
最上面的是各种软件,JRE就在这一层,包含着JVM和JAVA类库 |
中间的操作系统,Windows,Linux,Mac等 |
底层硬件部分 |
注:JDK包含了JRE和一些方便的小工具,而JRE包含JVM和JAVA类库,三者是层层包含的关系:JDK>JRE>JVM
3.类加载器
当我们写了一个java文件以.java结束后,会生成一个Class file ,运行后启动类加载器,就与运行时数据区进行交互,运行时数据区的异常是不可捕获的。
整体结构如下:
包含了方法区,栈与堆之类的。其中堆是垃圾回收的主要区域,栈(包含java栈,本地方法栈)和程序计数器不产生垃圾,用完就是弹出数据,因此调优几乎是在堆和方法区进行的。
3.1 类加载器的分类
作用:加载Class文件 如:new class_name();
我们写代码时 new 出来的是具体的实例,就是将一个类实例化,类加载器这时发挥作用。
在new一个对象时,对象的名称保存在栈里面,具体的信息保存在堆里面。栈是引用地址指向堆里面保存的真实数据。
类加载器的分类:
- 虚拟程序加载器;
- 启动类(根)加载器;
- 扩展类加载器;
- 应用程序加载器
当我们创建了一个实例后,类加载器会寻找这个类。
类加载器寻找类的步骤:
启动类(根)加载器 ------> 扩展类加载器(安全) ------> 当前应用程序加载器
3.2 双亲委派机制
双亲委派机制就是类加载器寻找类的过程:
- 类加载器收到类加载的请求;
- 将这个请求委托给父类加载器去完成,一直向上委托,直到启动类加载器;
- 启动加载器检查是否能够加载当前这个类,能加载就结束,使用当前的加载器,否则,抛出异常,通知子加载器进行加载;
- 重复步骤3 。
3.3 沙箱
所谓沙箱机制,便是基于双亲委派机制给java类加载带来的一个安全机制。 我们尝试去碰瓷原生api失败,是因为引导类加载器在加载的过程中会先加载jdk自带的文件。 我们尝试直接在java lang包下添加内容,结果直接报错,这也是沙箱安全机制的一种体现方式。
4.native
native表示本地,如本地方法栈和接口。
在java诞生初期想要立足就必须能够调用其他的程序,此时用C和C++的人很多,因此java在内存区域专门开辟了一块标记区域 native method stack,来登记native方法。
凡是带了native关键字的,说明Java的作用范围达不到了,会去调用C语言的库,会进入本地方法栈,调用本地方法接口 JNI。
JNI的作用是扩展Java的使用,加载本地方法库中的方法。
而现在调用其他接口的方法很多,例如: socket ,webservice,http…
5.方法区:
Method Area 方法区
方法区是被所有线程共享的的,有所有的字段、方法、字节码、以及一些特殊方法,如构造函数,接口代码等也在此处定义。简单来说,所有定义的方法的信息都保存的此处。
此区域属于公共区间。
静态变量、常量、类信息(构造方法、接口定义)、运行时的常量池存在方法区中,但是实例变量存在堆内存中,和方法区无关。
6.PC寄存器
每个线程都有一个程序计数器,是线程私有的,就是一个指针,指向方法区中的方法字节码(用来储存指向一条指令的地址,也就是将要执行的指令代码),在执行引擎读取下一条指令是计数,存在于非常小的内存空间,几乎可以忽略不计。
7.栈:
栈是一种数据结构,相对于队列,栈是先进后出,后进先出,类似于叠盘子,放在下面的盘子要先放但是必须要等上面的盘子拿下来之后才可以拿。而队列就是先进先出,类似于隧道。
程序中先执行main方法,但是main方法也是最后结束的。也可以理解递归时很容易出现的栈溢出的原因,相当于不停地放入但不能出就会溢出。
程序=数据结构+算法
栈:栈内存主管程序的运行,生命周期和线程同步,线程结束,栈内存也就释放了,因此对于栈来说,不存在垃圾回收问题。
栈运行的原理是栈帧
8.三种JVM
Sun公司 HotSpot (重点)
BEA公司 JRockit
IBM
9.堆 Heap
一个JVM只有一个堆内存,堆内存的大小可以调节的;
类加载器读取了类文件后,一般把什么东西放在堆中? 类,方法,常量,变量,保存我们所用引用类型的真实对象,就是实例。
堆内存中还要分为三个区域
- 新生区(伊甸园区)
- 养老区
- 永久存储区
轻GC表示轻回收,
重GC表示重回收(full GC),
GC垃圾回收主要在伊甸园区和养老区
新生区分为三个:类诞生和成长的地方,甚至死亡;
- 伊甸园区:所有的对象都是在这里 new 出来的。如果空间满了会触发 轻GC
- 幸存区0:上面轻GC之后幸存的进入这里
- 幸存区1:
养老区 Old : 新生区都满了然后还存在的在这里,满了会启动重GC。
永久区 Perm : 这个区域常驻内存,用来存放JDK自身携带的Class对象,Interface元数据。存储的是Java运行时的一些环境或类信息,这个区域不存在垃圾回收。关闭虚拟机的时候就会释放这个区域的内存。
- jdk 1.6之前:永久代,常量池是在方法区中;
- jdk1.7 : 有永久代,但是慢慢地退化了,提出‘去永久代’的概念,常量池在堆中
- jdk 1.8以后:永久存储区改了一个名字 叫元空间;
假设内存 (新生区和养老区)满了,报错OOM(out of memery …),堆内存不够!表示程序已经很严重了
OOM产生情况:
- 一个启动类,加载了大量的第三方jar包;
- Tomcat部署了太多的应用面,大量动态生成的反射类;
- 类不断地被加载。
三种情况下,程序运行到内存满了,就会出现OOM。
用图表示:
方法区里面的那个是常量池,紫色部分是堆的一部分,但是叫做“非堆”。元空间在逻辑上存在,物理上不存在。
oom怎么解决?
1.尝试扩大堆内存看结果
2.分析内存,看一下哪个地方出现了问题(专业工具)
能够看见第几行出错的工具有内存快照工具:MAT,Jprofiler
MAT,Jprofiler作用:
1.分析Dump内存文件,快速定位内存泄露;
(Dump文件是进程的内存镜像,可以把程序的执行状态通过调试器保存到dump文件中。主要是用来在系统中出现异常或者崩溃的时候来生成dump文件,然后用调试器进行调试,这样就可以把生产环境中的dmp文件拷贝到自己的开发机上,调试就可以找到程序出错的位置。)
2.获得堆中的数据
3.获得大的对象…
(不太了解……)
10.GC介绍
GC:垃圾回收
JVM在进行垃圾回收的时候,并不是对这两个区域统一回收。大部分时候,回收的都是新生代
新生区【伊甸园区 和 幸存区 (分为from 和 to)】
老年区
幸存区的from和to是动态交换的。
GC几乎不回收永久区(元空间)
GC的两种类:轻GC(普通的GC),重GC(全局GC)
11.GC的算法
11.1复制算法
相当于每一次轻GC都会把伊甸园区和form区回收一遍放在to区
好处:没有内存的碎片
坏处:浪费了内存空间:多了一半空间永远是空(to区)。
复制算法最佳使用场景:对象存活度较低的时候;新生区
11.2 引用计数法
给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器都为0的对象就是不再被使用的,垃圾收集器将回收该对象使用的内存。
用的较少
11.3 标记清除法
优点:不需要额外的空间!
缺点:两次扫描,严重浪费时间,会产生内存碎片。
11.4 标记压缩
11.5 标记清除压缩
先标记几次,再压缩
GC算法总结
内存效率:复制算法>标记清除算法>标记压缩算法 (时间复杂度)
内存整齐度:复制算法=标记压缩法>标记清除算法
内存利用率:标记压缩算法=标记清除算法>复制算法
没有JVM最优算法,只有最合适的算法。------->GC:分代收集算法
狂神说学习笔记而已,小白一个,有错误可以留言,谢谢!