Java虚拟机
JVM
java 虚拟机 jvm 是 java 实现跨平台的重要部分,jvm 是虚拟化的计算机,有完整的硬件功能,可以屏蔽底层不同的操作系统,只需要计算机厂商在不同系统中安装虚拟机,定义好如何将字节码文件解析成当前计算机系统识别的计算机码即可。从而实现JAVA的跨平台。JVM中重要的2个部分别是 JAVA内存模型和GC。大家要搞清楚JVM 是内存中的虚拟机,即JVM的存储就是内存
JVM 架构
- classLoader: 按照特特定格式,加载class文件到内存。
- Runtime Date Area: JVM 内存模型,JMM。
- Execution Engine: 对命令进行解析。class 文件是按照特定格式书写的,只有可以被Execution Engine 解析的class文件才是合法的。
- Native Interface: 本地接口,主要是用来加载第三方API 比如C++等,融合不同的语言被JAVA所用,如 Class.forName 方法 底层就是Native 接口
JAVA 反射机制
JAVA 反射是在程序运行过程中,对任意的类或者是对象,我们能够知道或者调用对象的任意方法和属性,这种动态获取信息以及动态调用方法的机制就是JAVA语音的反射。
public class Robot {
private String name;
public void sayHi(String hello){
System.out.println("hello:"+hello);
}
private String throwHello(String hi){
System.out.println("Hi:"+hi);
return hi;
}
}
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
Class roClass = Class.forName("com.java.imooc.reflect.Robot");
Robot robot = (Robot) roClass.newInstance();
System.out.println(roClass.getName());
// 不能获取私有方法,可以获取public 和 继承 实现的方法
Method[] methods = roClass.getMethods();
//String.class 是因为 throwHello 接收一个 String 对象,不能获取的是继承的方法和接口的方法,获取该类所有的方法。
Method throwHello = roClass.getDeclaredMethod("throwHello", String.class);
// 私有方法 需要设置为true
throwHello.setAccessible(true);
Object bobo = throwHello.invoke(robot, "bobo");
System.out.println(bobo);
System.out.println(methods);
}
JAVA 从编译到执行的过程
- 编译器将.java的文件编译成.class文件
- classLoader 将字节码文件转转换为JVM中的Class对象
- JVM利用Class对象实例化 T对象
ClassLoader
ClassLoader 在java中有着非常重要的作用,主要工作在class 装载的加载阶段,主要功能是将外部的class 二进制数据流,装载进系统,然后交给JAVA虚拟机进行连接,初始化等操作。
ClassLoader 的种类
- BootStrapClassLoader: C++ 编写,加载核心库 java.*
- ExtClassLoader: JAVA 编写 ,加载扩展库 javax.
- AppClassLoader: JAVA 编写,加载程序所在目录
- 自定义ClassLoader: JAVA编写,用户自定义。
ClassLoader 双亲委派机制
双亲委派机制 原则是先判断后加载,先判断Custom ClassLoader 是否加载过,如果没有判断父加载器AppClassLoader 是否加载过,如果没有继续判断Extension ClassLoader 是否加载过,
如果没有继续判断 BootStrap ClassLoader 是否加载过,如果都没有加载过,那么按照反向 进行加载,即由BootStrap ClassLoader 进行加载,如果BootStrap ClassLoader 库里没有,那么继续
向下 由Extension ClassLoader 进行加载,如果库里也没有,那么继续由子类 AppClassLoader 进行加载,如果在对应的jar里没有找到,那么最后由 Custom ClassLoader 进行加载,都没有 则抛出异常
判断是否加载 由下向上进行一次判断,加载过程是由上向下进行一次加载,这个判断加载的机制被称为双亲委派机制
- 采用这个机制的目的是防止同一份字节码被多次加载
代码阅读
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
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.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
类的加载方式
- 隐式加载: new
- 显示加载:loadClass,forName等
loadClass,forName 区别
- Class.forName 得到的class 是已经初始化完成的
- Classloader.loadClass 得到的class 是还没有链接的,说明只完成第一步。
JAVA内存模型
-
线程私有:程序计数器,虚拟机栈,本地方法栈
-
线程共享:MetaSpace,Java 堆
-
程序计数器
-
1. 当前线程所执行的字节码行号指示器。 2. 改变计数器的值来选取下一条需要执行的字节码指令 3. 和现成是一对一的关系,即线程私有 4. 对Java 方法计数,如果是Native方法则计数器值为Undefined 5. 不会发生内存泄露
-
Java 虚拟机栈(Stack)
-
1. Java 方法的的内存模型 2. 包含多个栈桢
-
元空间(MetaSpace)和 永久代(PermGen)
- jdk8 以后 元空间替代永久代,元空间使用本地内存,永久代使用的是jvm 内存。
- 替代好处是 字符串在永久代中容易出现性能问题和内存溢出问题。
- 类和方法的信息大小难以确认,给永久代的大小指定带来康困难。
- 永久代会给GC带来不必要的复杂性
-
Java 堆
- 对象实例的分配区域
- 是GC管理的主要区域
JVM 三大性能调优参数 -Xms -Xmx -Xss 的含义
- Xss :每个虚拟机栈(堆栈)的大小 一般情况下 256k 足够了 ,此配置回影响此进程中并发线程数量的大小
- Xms:堆的初始值,以及该线程刚创建出来的时候java堆的大小,一旦对象容量大于java堆容量,java堆会自动扩容。扩容到Xmx的大小
- Xmx:java堆能扩展到的最大值
一版情况下我们会将Xms和Xmx设置成一样的 因为在扩容的时候会发生内存抖动,影响程序运行的稳定性
Java内存中堆和栈的区别----内存分配策略
- 静态存储:在编译时确定每一个数据目标在运行时的存储空间需求
- 栈式存储:数据区需求在编译时未知,运行时模块入口前确定
- 堆式存储:编译时或运行时模块入口未知,无法确定,动态分配。
- 联系: 引用对象,数组时,栈里定义变量保存堆中目标的首地址
- 管理方式:栈自动施放,堆需要GC回收
- 空间带下:栈比堆小
- 碎片相关:栈的碎片比堆的碎片少
- 分配方式:栈支持静态和动态分配,而堆只支持动态分配
- 效率: 栈比堆高