代码过程

a.jpg

编译

所谓的编译过程,从我们能看到的层面就是javac,将.java文件转化为.class文件,也就是将源代码转化为了字节码,因为java是通过jvm跨平台的,这个编译的作用就是将其转化为jvm可以识别的字节码。

加载

类加载器

BootStrapClassLoader

启动类加载器,主要加载jre\lib下的jar包,是Ext的父类加载器

ExtensionClassLoader

标准扩展类加载器,主要加载jre\lib\ext下的jar包,是AppClassLoader

ApplicationClassLoader

系统类加载器,主要加载calsspath下的jar包,也就是我们写的

双亲委派加载机制

如果一个类加载器收到了一个类加载的请求,它首先不会去加载类,而是去把这个请求委派给父加载器去加载,直到顶层启动类加载器,如果父类加载不了(不在父类加载的搜索范围内),才会自己去加载。
image.png
好处:避免类的重复加载,也保证了 Java 的核心 API 不被篡改
tomcat:
打破了这个双亲委派机制 因为一个容器里要同时放多个webapp 如果遵循双亲委派机制的话就会导致类加载的问题 所以tomca为了避免首先是采用了WebAppClassLoader 类加载器 重写了loadclass方法 避免了不同webapp加载类的问题 还重写其他的类加载器 避免其他的加载问题

加载过程

Java程序运行的场所是内存,当在命令行下执行时:
**java HelloWorld**
命令的时候,JVM会将HelloWorld.class字节码加载到内存中,并形成一个Class的对象HelloWorld.class。
其中的过程就是类加载过程:
1、寻找jre目录,寻找jvm.dll,并初始化JVM
2、产生一个Bootstrap Loader(启动类加载器);
3、Bootstrap Loader自动加载Extended Loader(标准扩展类加载器),并将其父Loader设为Bootstrap Loader。
4、Bootstrap Loader自动加载AppClass Loader(系统类加载器),并将其父Loader设为Extended Loader。
5、最后由AppClass Loader加载HelloWorld类,如果有自定义加载器的话,依次往下。
6、遵循双亲委派加载机制,即加载的时候自底向上依次检查是否已经加载了指定类,然后进行加载
7、装载完类就已经把class对象生成载堆里了 相关信息存储在方法区 然后对验证 类是否符合规范 给其分配内存 以及赋初始值

编译后的就是class文件 class文件是通过类加载器加载到内存里面的 jdk类加载器 首先是appClassLoader 这个就是负责加载程序中的类文件 遵循的是双亲委派机制 双亲其实就是父类加载 就是说加载类的时候 为了避免内存中重复加载 就会让父类查询是否已经加载了 依次往上加载 extClassLoader(JDK内部扩展类) BootStrapClassLoader(本地方法类)
最后检查类是否符合规范 给其分配内存 以及赋初始值

解释执行

JVM将字节码转化为对应平台能够识别执行的代码指令,然后执行,其实就是把 我们的代码解释给操作系统 ,转化成它能读懂的指令,进而到cpu运算执行。

TOMCAT打破双亲委派加载机制

tomcat是打破了这个双亲委派机制 因为一个容器里要同时放多个webapp 如果遵循双亲委派机制的话就会导致类加载的问题 所以tomca为了避免首先是采用了WebAppClassLoader 类加载器 重写了loadclass方法 避免了不同webapp加载类的问题 还重写其他的类加载器 避免其他的加载问题 装载完类就已经把class对象生成载堆在里了 相关信息存储载方法区 然后对验证

JVM

image.png

  1. public class JVMShowcase {
  2. //静态常量
  3. public final static String ClASS_CONST = "I'm a Const";
  4. //私有实例变量
  5. private int instanceVar=15;
  6. public static void main(String[] args) {
  7. //调用静态方法
  8. runStaticMethod();
  9. //调用非静态方法
  10. JVMShowcase showcase=new JVMShowcase();
  11. showcase.runNonStaticMethod(100);
  12. }
  13. //常规静态方法
  14. public static String runStaticMethod(){
  15. return ClASS_CONST;
  16. }
  17. //非静态方法
  18. public int runNonStaticMethod(int parameter){
  19. int methodVar=this.instanceVar * parameter;
  20. return methodVar;
  21. }
  22. }

Program Counter Register(程序计数器 私)

每个线程都会有一个程序计数器,是线程私有的,程序计数器就是用来记录当前线程执行的字节码的位置,分支、循环、异常处理、线程回复等基础功能都依赖这个计数器来完成。

NativeMethods Stack(本地方法栈 私)

线程私有
Java在刚开始发展的时候,C/C++正是如日中天,Java为了发展兼容C就在内存中专门开辟了一块区域处理标记为native的代码,就有了这个栈,只要有native标记的方法,就会存储在这里。

Meta Space(方法区 共)

方法区是所有线程共享的,存储静态变量、运行时常量、类信息(构造方法,接口定义),字符串常量池在1.7之后已经迁移到了堆中。

JDK8之前称为永久代
-XX:PermSize=N //方法区(永久代)初始大小
-XX:MaxPermSize=N //方法区(永久代)最大大小,超出这个值将会抛出OutOfMemoryError

JDK8之后称为元空间(Meta Space)
元空间直接使用的是本机内存。参数设置:
-XX:MetaspaceSize=N //设置Metaspace的初始(和最小大小)
-XX:MaxMetaspaceSize=N //设置Metaspace的最大大小

运行时常量池存和字符串常量池 的变化

JDK1.7之前 :
运行时常量池(包含 字符串常量池 )存放在 方法区,此时 hotspot 虚拟机对方法区的实现为永久代。
JDK1.7 :
字符串常量池 被从方法区拿到了堆中;
运行时常量池 剩下的东西还在 方法区, 也就是hotspot中的永久代。
JDK1.8 :
hotspot移除了 永久代,用 元空间(Metaspace) 取而代之。这时候,
字符串常量池还在,
运行时常量池 还在方法区, 只不过方法区的实现从永久代变成元空间(Metaspace)。

VM Stack(虚拟机栈 私)

虚拟机栈,也叫栈内存,线程私有,是负责存放一些编译器可知的基本类型的值对象的引用方法等,保存方法调用的信息,是在线程创建时创建,它的生命期是跟随线程的生命期,线程结束栈内存也就释放,对于栈来说不存在垃圾回收问题。

栈的特点:

存取速度比堆要快,仅次于寄存器,栈数据可以共享

栈溢出:

  1. public class jvmTest1 {
  2. public static void main(String[] args) {
  3. a();
  4. }
  5. public static void a(){
  6. b();
  7. //递归死循环 引起栈溢出
  8. }
  9. public static void b(){
  10. a();
  11. }
  12. }
  13. Exception in thread "main" java.lang.StackOverflowError

栈帧:

每一个方法调用就会产生一个栈帧,每个一方法的调用过程就对应一个栈帧在Java栈中从入栈到出栈的过程,基于先进后出的原理,所以栈顶都是当前的方法。image.png
JVM - 图6

image.png

首先是application类的加载,将常量池和类信息(还有静态方法)生成在方法区,栈帧入栈,基本类型的值、对象的引用地址、方法入栈,然后new 一个pet类,堆中生成对应的对象,方法区生存Pet对应的信息,栈中保存其在堆中的引用地址以及一些基本数据类型的值,栈生成对象会从常量池中拿到数据,字符串常量池在1.7之后就在堆里。

Heap (堆 共)

线程共享,主要是保存运行时的生成的对象,且字符串常量池也在这里,和栈不同的是,因为无法直接确定对象是否还在使用,所以不能像栈一样,方法运行结束指针就清空,需要JVM进行垃圾回收
image.png

新生代

年轻代一般存活时间较短,基于Copying算法进行回收,当有新的对象分配内存时,采用空闲指针的方法来检查空间是否够用,如果触发GC,扫描出存活的对象,并从Eden复制到Survivor区,如果新生代满了,会把对象转移到旧生代。
Eden
Survivor

老年代

旧生代的对象存活时间一般比较长,比较稳定,一般采用Mark算法来进行回收,即标记出存活的对象,然后回收未被标记的对象,回收后的空间要么进行合并,要么标记出来便于下次进行内存分配
大对象直接进老年代

永久区(元空间)

也就是原来的方法区

默认情况下GC回收器

如何查看jvm的垃圾回收器
java -XX:+PrintCommandLineFlags -version
Java 8默认使用的是Parallel NewParallel Old垃圾回收器,
新生代基于Copying算法进行回收,当有新的对象分配内存时,采用空闲指针的方法来检查空间是否够用,如果触发GC,扫描出存活的对象,并从Eden复制到Survivor区,如果新生代满了,会把对象转移到旧生代,这种的话实现简单,运行高效且不容易产生碎片,但是对内存空间的使用付出了高昂的代价,内存缩减一半。
老年代采用Mark算法来进行回收,即标记出存活的对象,然后回收未被标记的对象,回收后的空间要么进行合并,要么标记出来便于下次进行内存分配