1.简介

1.1 对java的了解

在JDK的安装目录下有一个JRE目录,里面有两个文件夹bin和lib,在这里可以认为bin里的就是JVM,lib中则是JVM工作所需要的类库,而jvm和 lib合起来就称为jre。
JDK(Java Development Kit) :是 Java 语言的软件开发工具包(SDK)
JRE(Java Runtime Environment):Java运行环境
JVM(Java Virtual Machine):简单来说,作用是用于编译java代码成二进制,变成电脑系统能够识别的文件

  • 是一个虚构出来的计算机,是通过在实际的计算机上来仿真模拟各种计算机功能来实现的。JVM是整个java实现跨平台的最核心的部分,能够运行以Java语言写的程序。
  • 跨平台:编译java代码生成中间字节码,然后通过Java的开发团队针对不同的平台开发出了对应版本的java虚拟机解析执行。就能够实现各种平台上运行。(不同系统,比如win跨linux),但c不行,c有特定的cpu识别,所以没办法都兼容

JDK–>JRE–>JVM

1.2 java文件编译的过程

java 项目部署如何配置虚拟路径_Java

  • 程序员编写的.java文件
  • 由javac编译成字节码文件.class:(为什么编译成class文件,因为JVM只认识.class文件)
  • 在由JVM编译成电脑认识的文件 (对于电脑系统来说 文件代表一切)

1.3 为什么要学

假如:内存出现问题了,出现了内存溢出 ,内存泄漏问题怎么办–>研究java内部运行机制

1.4 JVM体系结构图

java 项目部署如何配置虚拟路径_Java_02


java 项目部署如何配置虚拟路径_Java_03

1.4.1 本地方法栈(Native)

  • 线程私有。本地方法栈与虚拟机栈所发挥的作用是非常相似的,它们之间的区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务。

1.4.2 方法区(Method Area)

  • 与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息(构造方法、接口定义)、常量、静态变量、即时编译器编译后的代码(字节码)等数据。方法区是JVM规范中定义的一个概念,具体放在哪里,不同的实现可以放在不同的地方。

运行时常量池

  • 运行时常量池是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。

1.4.3 程序计数器

  • 线程私有。一块较小的内存空间,可以看作当前线程字节码的指示器。如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Native方法,这个计数器值则为空。是个非常小的内存空间。几乎可以忽略不记

1.4.4 Java虚拟机栈

  • Java方法执行的内存模型:每个方法执行都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
  • 局部变量表:存放的是有些局部变量值的内存空间。在方法中定义的局部变量
  • 操作数栈:就是一个先入后出的栈
  • 动态链接:动态的获取class文件里面的变量和引用
  • 方法出口:方法的返回方式

1.4.5 Java堆

  • 线程共享。对大多数应用来说,Java堆是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。

1.5 常量池

class常量池 中存的是字面量和符号引用,也就是说他们存的并不是对象的实例,而是对象的符号引用值。而经过解析(resolve)之后,也就是把符号引用替换为直接引用,解析的过程会去查询 字符串常量池 ,以保证 运行时常量池所引用的字符串与字符串常量池中所引用的是一致的。

java 项目部署如何配置虚拟路径_java 项目部署如何配置虚拟路径_04

1.5.1 Class常量池-方法区中

常量池(Constant Pool),也叫 class 常量池(Class Constant Pool)

  • 用于存放编译器生成的各种字面量( Literal )和 符号引用(Symbolic References)

1.5.2 字符串常量池-堆中(String Pool)

  • 也叫全局常量池,String Pool 中存的是引用值,而不是具体的实例对象,具体的实例对象是在堆中开辟的一块空间存放的。
  • 字符串常量池在每个VM中只有一份

1.5.3 运行时常量池 -方法区中(Runtime Constant Pool)

  • 当类加载到内存中后,jvm就会将 class常量池 中的内容存放到 运行时常量池 中,由此可知,运行时常量池也是每个类都有一个。

2. 类加载器ClassLoader

2.1 总类

启动类加载器(Bootstrap ClassLoader):

  • 这个类加载器负责将存放在<JAVA_HOME>\lib目录中的,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如rt.jar,名字不符合的类库即使放在lib目录中也不会被加载)类库加载到虚拟机内存中。加载系统的包,包含jdk核心库里的类

扩展类加载器(Extension ClassLoader):

  • 这个加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载<JAVA_HOME>\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器。加载扩展jar包中的类

应用程序类加载器(Application ClassLoader):

  • 这个类加载器由sun.misc.Launcher$AppClassLoader实现。由于这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也称它为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。加载你编写的类,编译后的类

自定义类加载器:

  • 用户自定义的类加载器。继承Java.long.ClassLoader类,用户可以定制类的加载方式

2.2 双亲委派模型

如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。

java 项目部署如何配置虚拟路径_类加载器_05


java 项目部署如何配置虚拟路径_jvm_06


java 项目部署如何配置虚拟路径_类加载器_07

有哪些场景破坏了双亲委派模型

线程上下文类加载器是一种类加载器传递机制,只要是同一个线程,一旦设置了线程上下文加载器,在线程后续执行过程中就能把这个类加载器取出来用。

  • JDBC 使用线程上下文类加载器加载 Driver 实现类:jdbc是第三方提供的jar包,他是应用程序类加载器加载,由于JVM类加载器是双亲委托机制,java.lang.sql中的所有接口都是由JDK提供,所以就没办法加载第三方的驱动类。所以有了线程上下文类加载器,启动类加载器委托应用程序类加载器去加载第三方提供的具体实现。
  • Tomcat 的多 Web 应用程序,多个 Web 应用程序很容易存在依赖同一个 jar 包,但是版本不一样的情况会造成不兼容的情况。Tomcat 也是一样使用线程上下文类加载器,为每个 Web 应用创建一个 WebAppClassLoader 类加载器,并在启动任何一个Web 应用的线程里设置线程上下文加载器

2.3 类加载过程

类加载的过程包括:加载、验证、准备、解析、初始化,其中验证、准备、解析统称为连接。

加载:通过一个类的全限定名来获取定义此类的二进制字节流,在内存中生成一个代表这个类的java.lang.Class对象。

验证:确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。

准备:为静态变量分配内存并设置静态变量初始值,这里所说的初始值“通常情况”下是数据类型的零值。

解析:将常量池内的符号引用替换为直接引用。

初始化:到了初始化阶段,才真正开始执行类中定义的 Java 初始化程序代码。主要是静态变量赋值动作和静态语句块(static{})中的语句。

class Test{
public static int a = 1;
} /
/我们程序中给定的是 public static int a = 1;
//但是在加载过程中的步骤如下:
1. 加载阶段
编译文件为 .class文件,然后通过类加载,加载到JVM
2. 连接阶段
第一步(验证):确保Class类文件没问题
第二步(准备):先初始化为 a=0。(因为你int类型的初始值为0)
第三步(解析):将引用转换为直接引用
3. 初始化阶段:
通过此解析阶段,把1赋值为变量a

3.本地方法栈(Native方法)

public class test {
    /**
     *  native :凡是带了native关键字的,说明java的作用范围达不到了,回去调用底层c语言的库!
     * 会进入本地方法栈
     * 调用本地方法本地接口JNI(Java Native Interface)
     *  JNI作用:扩展Java的使用,融合不同的编程语言为ava所用!最初: C. C++.
     * Java 诞生的时候C、C++横行,想要立足,必须要有调川]C、C++的程序
     * 它在内存区域中专门开辟了一块标记区域: Native Method stack,登记native方法
     * 在最终执行的时候,加载本地方法库中的方法通过INI
     *  Java程序驱动打印机。管理系统,掌握即可, 在企业级应用中较为少见!
     * 因为现在的异构领域间通信很发达,比如可以使用Socket通信,也可以使用Web Service
     **/
    private native void start0();
}

4.方法区(Method Area)

java 项目部署如何配置虚拟路径_jvm_08

5.栈(Stack)

5.1 基本理解

程序 = 算法 + 数据结构

程序 = 框架 + 业务逻辑(表层且只会CURD)

java 项目部署如何配置虚拟路径_java 项目部署如何配置虚拟路径_09


java 项目部署如何配置虚拟路径_jvm_10


StackOverflowError(栈溢出异常)

java 项目部署如何配置虚拟路径_jvm_11

5.2 运行原理

java 项目部署如何配置虚拟路径_类加载器_12

6.堆(Heap)

Heap 堆,一个JVM实例只存在一个堆内存,堆内存的大小是可以调节的,类加载器读取了类文件后,需要把类,方法,常变量放到堆内存中,保存所有引用类型的真实信息,以方便执行器执行,
堆内存分为三部分

  • 新生区 Young Generation Space Young/New
  • 养老区 Tenure generation space Old/Tenure
  • 永久区 Permanent Space Perm(JDK8后叫元空间)
  • 所有的类都是在伊甸区被new出来的,当伊甸区内存空间满了就会跳到幸存区(0和1互相交替),并且对伊甸区进行垃圾回收(MinorGC-轻GC)
  • 当幸存者区满了就会跳到养老区,如果养老区也满了就会进行垃圾回收(MajorGC-FullGC重GC)清理内存,如果还是无法保存新对象,则会OOM异常java.lang.OutOfMemoryError:java heap space(堆内存溢出)

6.1 新生区(堆空间)

  • 新生区是类诞生,成长,消亡的区域,一个类在这里产生,应用,最后被垃圾回收器收集,结束生命。
  • 新生区又分为两部分:伊甸区(Eden Space)和幸存者区(Survivor Space)
  • 经过研究,不同对象的生命周期不同,在Java中98%的对象都是临时对象

6.2 永久区/元空间

6.2.3 区别

  • jdk1.6:出现“PermGen Space”的内存溢出(在方法区)
  • jdk1.7:正常“heap space”堆内存溢出,对于动态生成类的情况比较容易出现永久代的内存溢出 “PermGen space”。(永久代-堆中)
  • jdk1.8:正常“heap space”堆内存溢出,如果指定MetaSpaceSize 和 MaxMetaSpaceSize的大小,会出现元空间溢出“Metaspace”(元空间-本地内存中)
  • -XX:MetaspaceSize=100m -XX:MaxMetaspaceSize=100m -XX:+PrintGCDetails

6.2.4 永久代与元空间的区别

  • 永久区存在堆中,而元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制,但可以通过参数来指定元空间的大小
  • Metaspace 区域位于堆外,所以它的最大内存大小取决于系统内存,而不是堆大小,我们可以指定 MaxMetaspaceSize 参数来限定它的最大内存。
  • 用于记录一个 Java 类在 JVM 中的信息
  • Klass 结构 (JVM内部)
  • method metadata,包括方法的字节码、局部变量表、异常表、参数信息等
  • 常量池、注解、方法计数器

6.2.5 为什么jdk8用元空间取代了永久代

  • 字符串存在永久代中,容易出现性能问题和内存溢出。
  • 类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。
  • 永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。
  • Oracle 可能会将HotSpot VM 与 JRockit VM合二为一(HotSpot VM是我们现在用jdk1.8用的)

6.2.6 元空间参数定义(内存调优)

6.2.6.1 正常调参

-Xms :设置初始分配大小,默认为物理内存的 “1/64
-Xmx :最大分配内存,默认为物理内存的 “1/4”
-XX:+PrintGCDetails :输出详细的GC处理日志

-Xms1024m -Xmx1024m -XX:+PrintGCDetails
public class Test {

    public static void main(String[] args) {
        //返回Java虚拟机试图使用的最大内存量
        long maxMemory = Runtime.getRuntime().maxMemory();
//返回Java虚拟机中的内存总量
        long totalMemory = Runtime.getRuntime().totalMemory();
        System.out.println("MAX_MEMORY="+maxMemory+"(字节)、"
                +(maxMemory/(double)1024/1024)+"MB");
        System.out.println("TOTAL_MEMORY="+totalMemory+"(字节)、"
                +(totalMemory/(double)1024/1024)+"MB");


    }


}

java 项目部署如何配置虚拟路径_类加载器_13


java 项目部署如何配置虚拟路径_类加载器_14

6.2.6.2 参数调小
import java.util.Random;

public class Test {

    public static void main(String[] args) {
        String str = "无线循环创建随机数";
        while (true){
            str += str + new Random().nextInt(88888888)
                    +new Random().nextInt(999999999);
        }


    }


}
-Xms8m -Xmx8m -XX:+PrintGCDetails

java 项目部署如何配置虚拟路径_java 项目部署如何配置虚拟路径_15

7.Dump内存快照-JProfiler

在运行java程序的时候,有时候想测试运行时占用内存情况,这时候就需要使用测试工具查看了。
下载地址: https://www.ej-technologies.com/download/jprofiler/version_92

注册码
L-Larry_Lau@163.com#36573-fdkscp15axjj6#25257
L-Larry_Lau@163.com#5481-ucjn4a16rvd98#6038

java 项目部署如何配置虚拟路径_jvm_16


java 项目部署如何配置虚拟路径_java_17

import java.util.ArrayList;
import java.util.Random;

/**
 * -Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
 * xms:初始化堆内存
 * xmx:最大堆内存
 * -xx:+  打印条件
 */
public class Test {
    byte[] byteArray = new byte[1*1024*1024]; //1M = 1024K
    public static void main(String[] args) {

            ArrayList<Test> list = new ArrayList<>();
            int count = 0;
            try {
                while (true){
                    list.add(new Test());
                    count = count + 1;
                }
            }catch (Error e){
                System.out.println("count:"+count);
                e.printStackTrace();
            }

    }


}

运行后生成一个java_pid35236.hprof文件

java 项目部署如何配置虚拟路径_jvm_18


java 项目部署如何配置虚拟路径_类加载器_19

8.GC

8.1 理解

在触发GC的时候,有Young GC 和 Full GC两种。

触发Young GC:

  • 当新生代中的 Eden 区(伊甸区)没有足够空间进行分配时会触发Young GC。

触发Full GC:

  • 调用System.gc()方法时,系统建议执行Full GC,但是不必然执行
  • 老年区空间不足
  • 通过Young GC后进入老年代的对象内存大于老年代的可用内存

8.2 GC算法

8.2.1 复制算法

java 项目部署如何配置虚拟路径_java 项目部署如何配置虚拟路径_20

  • 它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。这样使得每次都是对整个半区进行内存回收
  • 每次便用Eden和From(幸存区1)回收时。将Eden和From中还存活的对象金部复制到to(幸存区0)上。清理Eden和From, From和to互换。当一个对象经历了15次GC都活下来就会进入重GC
  • 好处:没有内存碎片,坏处:浪费内存空间
  • 坏处:占内存空间
  • 不管怎样,都会保证名为To是空的。 Minor GC会一直重复这样的过程。直到 To 区 被填满 , ”To “ 区被填满之后,会将所有的对象移动到老年代中

8.2.2 标记 - 清除算法

首先标记出所有存活的对象,在标记完成后统一回收所有未被标记的对象。
它的主要不足有两个:

  • 一个是效率问题,标记和清除两个过程的效率都不高;
  • 另一个是空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。

java 项目部署如何配置虚拟路径_java 项目部署如何配置虚拟路径_21

8.2.2 引用计数法(了解即可)

引用计数器是对象被引用了,计数器+1,引用结束-1,0则垃圾回收清除

8.2.2 标记 - 整理算法

标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。

8.2.2 分代收集算法

当前商业虚拟机的垃圾收集都采用“分代收集”(Generational Collection)算法,这种算法并没有什么新的思想,只是根据对象存活周期的不同将内存划分为几块。

一般是把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。

在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。

在老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用标记—清理或者标记—整理算法来进行回收。

8.2.3 总结

没有最优的方案,永远只是时间或者空间的权衡,而现在因为不缺空间的情况多,一般都是优先复制算法

9.补充

9.1 沙箱机制

Java安全模型的核心就是Java沙箱(sandbox),什么是沙箱?

  • 沙箱是一个限制程序运行的环境。沙箱机制就是将 Java 代码限定在虚拟机(JVM)特定的运行范围中,并且严格限制代码对本地系统资源访问,通过这样的措施来保证对代码的有效隔离,防止对本地系统造成破坏。
  • 沙箱主要限制系统资源访问,那系统资源包括什么?——CPU、内存、文件系统、网络。不同级别的沙箱对这些资源访问的限制也可以不一样。