1.JVM的位置和jvm的体系结构
2.类加载器(Class Loader)
类加载器的作用:
- 加载Class文件–>Class Loader加载并初始化 做一些实例化的操作
类加载器首先是:
AppClassLoader 应用类加载器,主要负责加载应用程序的主函数类
ExtClassLoader 扩展类加载器,主要负责加载jre/lib/ext目录下的一些扩展的jar
BootstrapClassLoader 根加载器,主要负责加载核心的类库(java.lang.*等),构造ExtClassLoader和APPClassLoader
public class Car {
public static void main(String[] args) {
// classloader加载实例化几个对象
Car car1 = new Car();
//同一个class文件
Class<? extends Car> aClass1 = car1.getClass();
//同一个加载器
ClassLoader classLoader1 = aClass1.getClassLoader();
System.out.println(classLoader1);//这个是AppClassLoader 容器加载器
ClassLoader parent = classLoader1.getParent();//这个是ExtClassLoader 扩展类加载器
System.out.println(parent);
System.out.println(parent.getParent());//根加载器,java取不到
}
}
输出的结果是
3.双亲委派机制(转载自:添加链接描述)
当一个类的class文件要被加载时,会通过由下至上的查找顺序,先去应用类加载器去查找是否加载过,没有加载过就去扩展类加载器去同样的查找,直到根加载器,然后再考虑是否能够由自己加载,注意的是在查找到根加载器之前都不会去选择加载class文件,如果根加载器无法加载文件,会下沉到扩展类直到应用类加载器,由上至下的加载顺序,都加载不了就会抛出异常.
双亲委派机制的好处和作用是:1.避免重复加载,2.防止恶意篡改java的核心包代码
4.沙箱安全机制:就是java的安全模式(保证java程序的安全性)
jvm将java代码限定在特定的环境中运行,就会需要通过一些检测器来确保代码的安全性
1.字节码校验器
2.类装载器
3.存取控制器
4.安全管理器
5安全软件包
5.Native关键字
凡是调用了Native方法的,表示java层面是接触不到的,为融合不同的语言和扩展java程序的使用(简单理解是调用C++)
在java包里有native方法的时候,就会在类加载的时候在运行时数据区域里开辟了一块本地方法栈,用于登记native本地方法,可参考最上面的图片
public static void main(String[] args) {
new Thread(()->{
}).start();
}
点入start方法会看见:
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();//这里在java运行区域开辟了一块本地方法栈,用于登记native本地方法
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
//这个start0(),调用的是本地方法接口,JNI
private native void start0();
6.PC寄存器—>程序计数器
每个线程有一份
作用:PC寄存器用来存储指向下一条指令的地址,也即将要执行的指令代码。由执行引擎读取下一条指令。
7.方法区,方法区能存放的是什么?和堆 栈里放的有什么不一样?方法区具体在哪儿?
首先确定方法区的概念:参考添加链接描述
通过下面的图和实例看出
1.方法区里放的是static变量,final常量(方法区里包含了常量池),和Class里的数据以及构造函数
2.new 出来的对象都放在堆中,还有非static修饰的成员变量
3.每个线程都有自己的一个栈区,栈区里面存放对象的引用 和 基础数据(8种基本数据类型)
public class PersonDemo
{
public static void main(String[] args)
{ //局部变量p和形参args都在main方法的栈帧中
//new Person()对象在堆中分配空间
Person p = new Person();
//sum在栈中,new int[10]在堆中分配空间
int[] sum = new int[10];
}
}
class Person
{ // 实例变量name和age在堆(Heap)中分配空间
private String name;
private int age;
//类变量(引用类型)name1和"cn"都在方法区(Method Area)
private static String name1 = "cn";
//类变量(引用类型)name2在方法区(Method Area)
//new String("cn")对象在堆(Heap)中分配空间
private static String name2 = new String("cn");
//num在堆中,new int[10]也在堆中
private int[] num = new int[10];
Person(String name,int age)
{
//this及形参name、age在构造方法被调用时
//会在构造方法的栈帧中开辟空间
this.name = name;
this.age = age;
}
//setName()方法在方法区中
public void setName(String name)
{
this.name = name;
}
//speak()方法在方法区中
public void speak()
{
System.out.println(this.name+"..."+this.age);
}
//showCountry()方法在方法区中
public static void showCountry()
{
System.out.println("country="+country);
}
}
————————————————
原文链接:https://blog.csdn.net/u013241673/article/details/78574770
8.栈:数据结构(和队列类似)
队列; 先进先出 --> 两头没堵住的管道(FIFO: Fisrt input Fisrt Output)
栈:一个有底的桶,先进后出,后进先出
- 程序 = 数据结构 + 算法!!!
- 栈:有栈底和栈顶,管理程序的运行,生命周期和线程周期同步(创建->就绪->运行->阻塞->结束),线程结束,栈内存就会释放,不存在垃圾回收问题
- 栈里存放的是一个个栈帧,栈帧里包含: 8大基本数据类型 + 对象 + 实例的方法
栈帧里有的元素
1.方法索引(index)
2.输入输出参数
3.本地变量
4.ClassFile(引用)
5.父帧
6.子帧
9.堆Heap
gc分为轻gc() 和 重gc(Full GC)
一个jvm只有一个堆内存,但是可以通过调节参数,调节大小.
类加载器读取了类文件后,一般会把什么放在堆中? ===>保存所有引用类型的真实对象
- 类
- 方法
- 常量
- 变量
堆的细分:
新生区(伊甸园区Eden Space–>幸存0区–>幸存1区) 垃圾回收主要是在新生区和养老区
老年区(养老区)
永久区(JDK1.8以后叫元空间) 一般存放的是JDK自身携带的class对象,interface元数据
永久区逻辑上存在,物理上是不存在的
新生区
类:诞生和成长的地方,甚至死亡
伊甸园区:所有new出来的对象都是在
public static void main(String[] args) {
//返回虚拟机试图使用的最大内存
long l = Runtime.getRuntime().maxMemory();
System.out.println(l/1024/1024);
//返回jvm的总内存
long totalMemory = Runtime.getRuntime().totalMemory();
System.out.println(totalMemory/1024/1024);
}
//OOM情况下
//1.调大jvm内存-Xms1024m -Xmx1024m -XX:+PrintGCDetails
//2.看代码问题
确认元空间的大小
通过设置jvm的大小后,新生区的大小为 305664k + 老年区的大小699392k = 1005056k = 981.5M ,表示元空间在物理上的大小是0,存在于理论上
使用JProfiler工具分析OOM原因
如何在一个项目中排除OOM故障,研究为什么出错
1.内存快照分析工具,MAT,Jprofiler
2.Debug(不适用)
MAT和Jprofile作用:
1.分析Dump内存文件,快速定位内存泄露
2.获得堆中的数据
3.获得大的对象
Jprofiler通过dump文件命令下载文件进行分析:
-Xms1m 这个是设置初始化的jvm内存 -Xmx8m 这个是设置最大的内存空间
-Xms5m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError 堆内存溢出就下载文件
-Xms1m -Xmx8m -XX:+PrintGC 每次触发GC的时候打印相关日志
-XX:+PrintGCDetails 更详细的GC日志
-XX:MaxTenuingThreshold=进行设置 (默认为15次,在新生代存活的对象进行老年区)
GC算法
GC主要是在堆里的新生区进行回收
堆分为:新生区(方法区逻辑上在新生区) 老年区(当一个对象经历了15次GC还没死亡) 元空间(元空间物理上不存在,逻辑上存在)
新生区分为,在新生区的两个幸存区中,谁空谁是to:
伊甸区(Eden)
幸存区(To)
幸存区(From)
GC分为两种:轻GC(普通的GC) 重GC(全局GC)
GC的算法:
- 标记清除法
- 标记压缩
- 复制算法:复制算法是发生在新生代,保证没有内存碎片,每次将From区的对象,复制到To区,此时为空的幸存区会变为To区,有对象的幸存区为From,但是会浪费一点内存
- 引用计数器