jvm的自传
这是主要来自狂神视频的学习,然后觉得很有必要自己通过学习后能够变成自己的东西,让自己再自述一遍对jvm的认识!!
一.jvm在系统上的大体位置
jvm是Java虚拟机它的位置可以粗略的以,以下的方式表达出来!基于操作系统之上,这也就解释了为什么每一个系统都可以适用Java语言,使得Java具有了很好的可移植性。
二.jvm的体系结构
对于Java文件来说运行的大体过程是如何的呢?
三、类加载器
类加载器的作用:加载class文件
例子:
图例描述:car class文件通过类加载器(classloader)进行加载初始化得到car class类通过car class 类实例化得到car1、car2、car3对象而对于对象而言可以通过getcalss方法得到相应的class类以及通过相应的类用getclassloader方法得到对应的类加载器
加载器顺序:(app–>ext–>boot)
1.虚拟机自带的加载器
2.启动类加载器bootstrapClassLoader(rt.jar)
3.扩展类加载器extClassLoader
4.应用程序(系统类加载器)加载器appclassloader
note:加载器之间不是继承关系
四、双亲委派
描述:双亲委派机制是为了防止低级别的类替换高级别的类而设计的安全防护
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
// -----??-----
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// 首先,检查是否已经被类加载器加载过
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
// 存在父加载器,递归的交由父加载器
if (parent != null) {
c = parent.loadClass(name, false);
} else {
// 直到最上面的Bootstrap类加载器
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
c = findClass(name);
}
}
return c;
}
五、沙箱安全
描述:沙箱安全机制现在jdk1.6版本,按照域的概念,程序代码通过不同级别的域有不同的权限,让系统资源代理实现调用资源。
六、native
描述:ntaive用于调用非Java代码
常用jni例子:
首先编写一个Java的native方法
class Hello {
public native void hello();
static{
System.loadLibrary("hello");
}
public static void main(String[] args){
new Hello().hello();
}
}
编译javac Hello.java
然后javah -jni Hello.class生成对应的Hello.h
打开vs选择创建dll项目
创建hello.cpp并将对应缺少的jni.h、jni_md.h以及Hello.h头文件引入项目中
hello.cpp代码如下
#include <stdio.h>
#include "Hello.h"
using namespace std;
JNIEXPORT void JNICALL Java_Hello_hello
(JNIEnv*, jobject, jstring) {
printf("Hello World !\n");
return;
}
运行生成hello.dll
将此文件复制到Java文件目录下,运行java Hello即可有调用c的结果
这里需要注意hello.cpp中的函数要和Hello.h文件中的函数要一致方可运行成功
七、pc寄存器(program counter register)
描述:每个线程都有一个程序计数器,是线程私有的,就是一个指针,指向方法区中的方法字节码(用来存储指向像一条指令的地址,也即将要执行的指令代码),在执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不计。
执行过程:PC寄存器拿到指令地址,执行引擎根据指令地址读取操作指令,这时候执行引擎要做两个事(1.操作局部变量表,操作数栈。2.翻译成机器指令),之后就可以让CPU对机器指令进行运算。
例子:
class TestRegister{
public static void main(String[] args) {
String s = "name";
System.out.println(s);
}
}
/**
* 运行的时候
* 1.javac TestRegister.java
* 2.javap -v TestRegister.class
* 就可以看到了
*/
ps:1 2 3 4…就是寄存器拿的地址
比如 #2,#2后面的就是常量池中的操作,如下
#2就是String,然后执行#16,找到#16是赋值操作 =name 到此#2指令执行完毕。
八、方法区
描述:方法取里所有东西是线程共享的,所有字段和方法字节码,以及一些特殊方法,如构造函数,接口代码也在此定义。简单说,所有定义的方法的信息都保存在该区域,此区域属于共享区间。
静态变量、常量、类信息(构造方法,接口定义)、运行时的常量池也在方法区中,但是实例变量存在堆内存中,和方法区无关。
九、栈
作用:主管程序运行,生命周期和线程同步,线程结束,栈内存也就释放,对于栈来说,不存在垃圾回收问题一旦线程结束,栈就over
栈:8大基本类型+对象引用+实例方法
栈满了:StackOverflowError
十、三种java
- sun公司的
- BEA Oracle
JRockit
- IBM `j9vm
十一、堆
heap,一个jvm只有一个堆内存,堆内存的大小时可以调节的。
类加载器读取了类文件后,一般会把什么东西放到堆中? 类,方法,常量,变量,保存我们所有引用类新的对象。
堆内存中还要细分三个区域:
- 新生区(伊甸园)
- 养老区
- 永久区
GC垃圾回收,主要是在伊甸园区和养老区~
假设内存满了,oom,对内存不够!
再jdk1.8以后,永久存储区变成了元空间!
十二、新生区、养老区、永久区
新生区
- 类:诞生和成长的地方,甚至死亡;
- 伊甸园和幸存区,对象都是从伊甸园区new来的
- 幸存者区分0和1区
老年区
真理:经过研究,99%的对象都是临时对象!
永久区
这个区域常驻内存的。用来放jdk自身携带的clss对象。interface元数据,存储的时Java运行时的一些环境或类信息,这个区域不存在垃圾回收!关闭虚拟机就会释放这个区域的内存~
一个启动类,加载了大量的第三方jar包。tomcat部署了太多应用,大量动态生成的反射类,不断地被加载。知道内存满,就会出现oom。
元空间:逻辑上存在,物理上不存在
在一个项目中,出现oom故障,那么该如何排除?
- 能够看到代码第几行:内存快照分析工具,MAT,Jprofilter
- debug,分析
MAT,Jprofilter作用:
- 分析Dump内存文件,快速定位内存泄漏;
- 获得队中的数据
- 获得大的对象
十三、堆内存调优
这个目前用到的较为少可以参考别人的例子:
https://blog.csdn.net/hxy1625309592/article/details/114580109
十四、GC
常用算法:
- 标记清除压缩法
缺点:扫描多严重浪费时间,产生碎片(用压缩解决)
优点:不需要额外空间
- 复制算法
好处:没有内存碎片
坏处:浪费了内存空间:多了一般空间永远是to。假设对象存货100%存货(极端情况)
对象存货度较低适用