做公司网站的总结,扫描到网站目录然后怎么做,跨境电商面试自我介绍范文,平面广告设计作品集申明#xff1a;文章内容是本人学习极客时间课程所写#xff0c;文字和图片基本来源于课程资料#xff0c;在某些地方会插入一点自己的理解#xff0c;未用于商业用途#xff0c;侵删。 原资料地址#xff1a;课程资料
什么是JVM
原文连接#xff1a; 原文连接 JVM是J…申明文章内容是本人学习极客时间课程所写文字和图片基本来源于课程资料在某些地方会插入一点自己的理解未用于商业用途侵删。 原资料地址课程资料
什么是JVM
原文连接 原文连接 JVM是Java Virtual MachineJava虚拟机的缩写是通过在实际的计算机上仿真模拟各种计算机功能来实现的。由一套字节码指令集、一组寄存器、一个栈、一个垃圾回收堆和一个存储方法域等组成。JVM屏蔽了与操作系统平台相关的信息使得Java程序只需要生成在Java虚拟机上运行的目标代码字节码就可在多种平台上不加修改的运行这也是Java能够“一次编译到处运行的”原因。 JVM的主要板块
类加载子系统
类加载器的定义深入理解JVM原话 通过一个类的全限定名称来描述此类的二进制字节流将这个动作放到Java虚拟机外部去实现以便让应用程序自己据欸的那个如何区获取所需要的类实现这个动作的代码模快称为类加载器。 1 JVM 的类加载是通过ClassLoader及子类来完成的通常来说有下面几种类加载器
启动类加载器Bootstrap ClassLoader) 负责加载JAVA_ HOME\lib目录的或通过-Xbootclasspath参数指定路径中的且被虚拟机认可(rt.jar) 的类库扩展类加载器(Extension ClassLoader) 负责加载JAVA_ _HOME\lib\ext目录或通过java.ext.dirs系统变量指定路径中的类库应用程序类加载器(Application ClassLoader) 负责加载用户路径classpath上的类库自定义类加载器 加载应用之外的类文件
2 类加载执行顺序 检查顺序是自底向上:加载过程中会先检查类是否被已加载从Custom到BootStrap逐层检查,只要某个类加载器已加载就视为此类已加载保证此类所有ClassLoader只加载一 次. 3 加载时机(检查时自底向上加载时自顶向下)
1-遇到new、getStatic、 putStatic、 invokeStatic四条指令时。 2-使用java.lang.reflect包方法时对类进行反射调用。 3-初始化-个类时发现其父类还没初始化要先初始化其父类。 4-当虚拟机启动时用户需要指定–个主类Main需要先将主类加载。
4 一个类的一生 5 类加载所做的事情 在类加载的过程中做了如下几件事情 1 根据全限定名称加载二进制字节流。 2 将字节流转换为数据结构 3 创建字节码class的对象
6 类加载途径 ➢01-jar/war ➢02-jsp生成的class ➢03-数据库中的二进制字节流 ➢04-网络中的二进制字节流 ➢05-动态代理生成的二进制字节流 自定义类加载器案例heiloworld
public class CustomClassLoader extends ClassLoader {private final String classPath;public CustomClassLoader(String classPath) {this.classPath classPath;}public static void main(String[] args) {CustomClassLoader customClassLoader new CustomClassLoader(E:\\lesson-one\\lesson-one\\src\\lib);try {Class? c customClassLoader.loadClass(com.learn.lessonone.dto.Test);if (c ! null) {Object o c.newInstance();Method say c.getMethod(say, null);say.invoke(o, null);System.out.println(c.getClassLoader().toString());}} catch (Exception e) {e.printStackTrace();}}Overrideprotected Class? loadClass(String name, boolean resolve) throws ClassNotFoundException {try {byte[] calsssDate getData(name);if (calsssDate ! null) {return defineClass(name, calsssDate, 0, calsssDate.length);}} catch (Exception e) {e.printStackTrace();}return super.loadClass(name, resolve);}Overrideprotected Object getClassLoadingLock(String className) {return super.getClassLoadingLock(className);}public byte[] getData(String className) {String path classPath File.separator className.replace(., File.separator) .class;try (InputStream in new FileInputStream(path);ByteArrayOutputStream out new ByteArrayOutputStream()) {byte[] buffer new byte[1024];int len 0;while ((len in.read(buffer)) ! -1) {out.write(buffer, 0, len);}} catch (Exception e) {e.printStackTrace();}return null;}
}类加载机制双亲委派
1-什么是双亲委派? 当一个类加载器收到类加载任务会先交给其父类加载器去完成因此最终加载任务都会传递到顶层的启动类加载器只有当父类加载器无法完成加载任务时才会尝试执行加载任务。
2-为什么需要双亲委派呢? 双亲委派其实是一种规范它一定程度上能够保证安全性。就比如我们尝试用的ObjectString类如果我们没有委托父类进行加载每个子类进行加载如果这个时候我们自己写了一个类的全限定名称和系统的一模一样这个时候它加载的就是我们写的类这样就会导致我们使用的不是Java给我门头提供的Object类从而程序完全乱套。 为什么双亲委派能够解决这个问题呢因为我们会一直委托父类去加载加载Object这种类最终都是由BootstrapClassLoader来加载它保证了加载的一定是Java提供给我们的Object类因为BootstrapClassLoader就是负责加载这类内置类的。
3-为什么还需要破坏双亲委派? 在实际应用中双亲委派解决了Java 基础类统一加载的问题但是却存在着缺陷。JDK中的基础类作为典型的API被用户调用但是也存在API调用用户代码的情况JNDISPI这种情况就需要打破双亲委派模式。 例如数据库驱动DriverManager。以Driver接口为例Driver接口定义在]DK中其实现由各个数据库的服务商来提供由系统类加载器加载。这个时候就需要启动类加载器来委托子类来加载Driver实现这就破坏了双亲委派。
4-如何破坏双亲委派? 方式一:重写ClassLoader的loadClass方法 方式二:SPl类委托自类加载器加载Class以数据库驱动DriverManager为例
自己的理解 自定义的类始最终都是由ApplicationClassLoader或自定义类加载器加载比如我写了一个CustomObject 继承了Object这个类 并定义了这个类字段结构。在执行加载的时候CustomObject一直向上委托最后发现BootStrapClassLoaer加载不了然后又自顶向下回溯ApplicationClassLoader来加载CustomObject这个类但是Object BootStrapClassLoaer是能加载的在回到这里之前Oject已将被加载过了。 类加载的源码 可以类的加载时委托自己的父亲进行加载
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 statssun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}if (resolve) {resolveClass(c);}return c;}}运行时数据区概述
按照线程使用情况和职责分成两大类 1线程独享程序执行区 虚拟机栈本地方法栈程序计数器 不需要垃圾回收
2线程共享数据存储区域) 堆和方法区 存储类的静态数据和对象数据 需要垃圾回收
1)堆 是虚拟机所管理的内存中最大的一块在虚拟器启动时创建被所有线程共享。此区域的唯一目的就是存放对象实例一般来说对象实例都是在这里分配内存包括现对象、数组与运行时常量。 2)堆内存划分依据 1.弱分代假说大多数对象存活时间短。 2.强分代假说熬过越多次的垃圾回收就越难以被回收。 3.跨代引用假说跨代引用的对象占少数。 划分情况如下图,1.6 新生代和老年代Eden空间From Survivor To Survivor。 ·3)划分变迁 Young 年轻区 主要保存年轻对象分为三部分Eden区、两个Survivor区。 Tenured 年老区 主要保存年长对象当对象在Young复制转移一定的次数后对象就会被转移到Tenured区。 Perm 永久区 主要保存class、method、filed对象这部份的空间一般不会溢出除非一次性加载了很多的类不过在涉及到热部署的应用服务器的时候有时候会遇到OOM : PermGen space 的错误。 Virtual区 最大内存和初始内存的差值就是Virtual区。 由2部分组成新生代Eden 2*Survivor 年老代OldGen JDK1.8中变化最大是的Perm永久区用Metaspace进行了替换 注意Metaspace所占用的内存空间不是在虚拟机内部而是在本地内存空间中。区别于JDK1.7 取消新生代、老年代的物理划分 将堆划分为若干个区域Region这些区域中包含了有逻辑上的新生代、老年代区域
下面我们来验证区分划分 1 我们需要先将不同版本的jdk下载到本地 2 接着借助jdk自带的工具来看JVM的相关信息 打开这个exe然后按安装插件 插件下载地址 然后1.6版本我们可以看到 同时我们使用命令
#可以查看堆内存结构需要Java\bin目录下或者配置环境变量
jmap -heap 104204)虚拟机栈 栈帧(Stack Frame)是用于支持虚拟机进行方法执行的数据结构。 栈帧存储了方法的局部变量表、操作数栈、动态连接和方法返回地址等信息。每一个方法从调用至执行 完成的过程都对应着一个栈帧在虚拟机栈里从入栈到出栈的过程。 栈内存为线程私有的空间每个线程都会创建私有的栈内存生命周期与线程相同每个Java方法在执 行的时候都会创建一个栈帧Stack Frame。栈内存大小决定了方法调用的深度栈内存过小则会导 致方法调用的深度较小如递归调用的次数较少。
虚拟机栈可能出现的两种异常 如果线程请求的栈深度大于虚拟机所允许的深度Xss默认1m会抛出StackOverflowError异常. 下面这个例子中我们大约递归到2完多次就会堆栈溢出。
public class DemoError {private static int cnt 0;private void call() {cnt ;try {call();} catch (Throwable e) {e.printStackTrace();System.out.println(cnt);}}public static void main(String[] args) {DemoError demoError new DemoError();demoError.call();}
}#空间进一步缩小,递归的深度会进一步缩小
java -Xss256k DemoError 如果在创建新的线程时没有足够的内存去创建对应的虚拟机栈会抛出OutOfMemoryError异常。 5本地方法栈 本地方法栈和虚拟机栈相似区别就是虚拟机栈为虚拟机执行Java服务字节码服务而本地方法栈为虚拟机使用到的Native方法比如C方法服务。
6方法区 方法区Method Area是可供各个线程共享的运行时内存区域方法区本质上是Java语言编译后代码存储区域它存储每一个类的结构信息例如运行时常量池、成员变量、方法数据、构造方法和普通方法的字节码指令等内容。很多语言都有类似区域。 方法区的具体实现有两种永久代PermGen、元空间Metaspace 方法区存储下面这几种数据类型 第一Class
类型信息比如Classcom.hero.User类方法信息比如Method方法名称、方法参数列表、方法返回值信息字段信息比如Field字段类型字段名称需要特殊设置才能保存的住类变量静态变量JDK1.7之后转移到堆中存储方法表方法调用的时候 在A类的main方法中去调用B类的method1方法是根据B类的方 法表去查找合适的方法进行调用的。 第二运行时常量池字符串常量池从class中的常量池加载而来JDK1.7之后转移到堆中存 储 字面量类型 引用类型–内存地址 第三JIT编译器编译之后的代码缓存
永久代和元空间的区别是什么 1JDK1.8之前使用的方法区实现是永久代JDK1.8及以后使用的方法区实现是元空间。 2)存储位置不同 永久代所使用的内存区域是JVM进程所使用的区域它的大小受整个JVM的大小所限制。 元空间所使用的内存区域是物理内存区域。那么元空间的使用大小只会受物理内存大小的限 制。 3)存储内容不同 永久代存储的信息基本上就是上面方法区存储内容中的数据。 元空间只存储类的元信息而静态变量和运行时常量池都挪到堆中。 3为什么要使用元空间来替换永久代 字符串存在永久代中容易出现性能问题和永久代内存溢出。
类及方法的信息等比较难确定其大小因此对于永久代的大小指定比较困难太小容易出现永久代溢出太大则容易导致老年代溢出。永久代会为 GC 带来不必要的复杂度并且回收效率偏低。Oracle 计划将HotSpot 与 JRockit 合二为一。 下面来看几个例子搞明白这个变迁
import java.util.ArrayList;
import java.util.List;
public class StringDemo {public static void main(String[] args) {String base ;ListString strings new ArrayListString();for (int i 0; i Integer.MAX_VALUE; i ) {String str base abcdefg;strings.add(str.intern());}}
}
java -XX:PermSize8m -XX:MaxPermSize8m StringDemo在1.6的运行结果 1.7运行结果 永久带还保留但是字符串常量已经存到了堆中去因为报的是堆内存溢出
1.8的运行结果 可以看到1.8永久带彻底被移除字符串常量存储到了堆内存中。
运行时常量
三种常量池的比较
class常量池一个class文件只有一个class常量池 字面量数值型int、float、long、double、双引号引起来的字符串值等 符号引用Class、Method、Field等
运行时常量池一个class对象有一个运行时常量池 字面量数值型int、float、long、double、双引号引起来的字符串值等 符号引用Class、Method、Field等
字符串常量池全局只有一个字符串常量池 双引号引起来的字符串值
我们定义字符串的时候如果直接用进行引用则是存储在字符串常量池中如果用new String()则是存储在堆中。字符串常量池底层是一个拉链哈希表用来存储和索引所以字符串常量池不会存储重复的字符串。 看下面这几个例子来理解 例子1
String a abc;
String b abc;
// true 我们知道 比较的是存储地址所以我们可以看到这两个字符串都是存储在字符串常量池中的并且引用的是同一个地址那么肯定是只存储了一份。
System.out.println(a b);例子2
String a abc;
String b new String(abc);
// false b 这个对象是通过new出来的他不会放到字符串常量池中
System.out.println(a b);例子3
String a abc;
String c def;
String d a c;
String f abcdef;
// false 所以只有 的字符串在在字符串常量池,涉及到任何其它操作都不会存储在字符串常量池中
System.out.println(f d);
// intern将字符串移动到字符串常量池中
// 因为我们调用函数intern d 的引用移动了字符串常量池,他们的地址也就一样了
System.out.println(f d.intern());例子4 map的底层实现和字符串常量池基本一样
MapString, Integer map new HashMap();
map.put(通话, 51);
map.put(重地, 55);
// 两个字符串的hashcode一模一样
System.out.println(通话.hashCode());
System.out.println(重地.hashCode());
// hashcode 相同的时候会进行拉链,也就是先比较hashcode如果相同在比较值,最后放到这个槽的链表上
System.out.println(map.size());8程序计数器 程序计数器Program Counter Register也叫PC寄存器是一块较小的内存空间它可以看作是当 前线程所执行的字节码指令的行号指示器。字节码解释器的工作就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。分支循环跳转异常处理线程回复等都需要依赖这个计数器来完 成。 为什么需要程序计数器 由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的在任何一个确定的时刻一个处理器针对多核处理器来说是一个内核都只会执行一条线程中的指令。因此为了线 程切换系统上下文切换后能恢复到正确的执行位置每条线程都需要有一个独立的程序计数器各条线程之间计数器互不影响独立存储我们称这类内存区域为“线程私有”的内存。 存储的什么数据 如果一个线程正在执行的是一个Java方法这个计数器记录的是正在执行的虚拟机字节码指令的地址如果正在执行的是一个Native方法这个计数器的值则为空。 异常此内存区域是唯一一个在Java的虚拟机规范中没有规定任何OutOfMemoryError异常情况的区 域。 9)直接内存 直接内存并不是虚拟机运行时数据区的一部分也不是Java 虚拟机规范中定义的内存区域。在JDK1.4 中新加入了NIO(New Input/Output)类引入了一种基于通道(Channel)与缓冲区Buffer的I/O 方式 它可以使用native 函数库直接分配堆外内存然后通过一个存储在Java堆中的DirectByteBuffer 对象 作为这块内存的引用进行操作。这样能在一些场景中显著提高性能因为避免了在Java堆和Native堆中来回复制数据。 本机直接内存的分配不会受到Java 堆大小的限制受到本机总内存大小限制。
直接内存堆外内存与堆内存比较 直接内存申请空间耗费更高的性能当频繁申请到一定量时尤为明显 直接内存IO读写的性能要优于普通的堆内存在多次读写操作的情况下差异明显 下面这个例子可以比较它们读写之前的差异
public class ByteBufferCompare {public static void main(String[] args) {//allocateCompare(); //分配比较operateCompare(); //读写比较}/*** 直接内存 和 堆内存的 分配空间比较* 结论 在数据量提升时直接内存相比非直接内的申请有很严重的性能问题*/public static void allocateCompare() {int time 1000 * 10000; //操作次数,1千万long st System.currentTimeMillis();for (int i 0; i time; i) {//ByteBuffer.allocate(int capacity) 分配一个新的字节缓冲区。//非直接内存分配申请ByteBuffer buffer ByteBuffer.allocate(2);}long et System.currentTimeMillis();System.out.println(在进行 time 次分配操作时堆内存 分配耗时: (et - st) ms);long st_heap System.currentTimeMillis();for (int i 0; i time; i) {//ByteBuffer.allocateDirect(int capacity) 分配新的直接字节缓冲区。//直接内存分配申请ByteBuffer buffer ByteBuffer.allocateDirect(2);}long et_direct System.currentTimeMillis();System.out.println(在进行 time 次分配操作时直接内存 分配耗时: (et_direct - st_heap) ms);}/*** 直接内存 和 堆内存的 读写性能比较* 结论直接内存在直接的IO 操作上在频繁的读写时 会有显著的性能提升*/public static void operateCompare() {int time 10 * 10000 * 10000; //操作次数,10亿ByteBuffer buffer ByteBuffer.allocate(2 * time);long st System.currentTimeMillis();for (int i 0; i time; i) {// putChar(char value) 用来写入 char 值的相对 put 方法buffer.putChar(a);}buffer.flip();for (int i 0; i time; i) {buffer.getChar();}long et System.currentTimeMillis();System.out.println(在进行 time 次读写操作时非直接内存读写耗时 (et - st) ms);ByteBuffer buffer_d ByteBuffer.allocateDirect(2 * time);long st_direct System.currentTimeMillis();for (int i 0; i time; i) {// putChar(char value) 用来写入 char 值的相对 put 方法buffer_d.putChar(a);}buffer_d.flip();for (int i 0; i time; i) {buffer_d.getChar();}long et_direct System.currentTimeMillis();System.out.println(在进行 time 次读写操作时直接内存读写耗时: (et_direct - st_direct) ms);}
}从数据流的角度来看:
非直接内存作用链本地IO –直接内存–非直接内存–直接内存–本地IO 直接内存作用链本地IO–直接内存–本地IO 直接内存的使用场景 有很大的数据需要存储它的生命周期很长 适合频繁的IO操作例如网络并发场景