当前位置: 首页 > news >正文

seo综合查询站长工具关键词全网营销案例

seo综合查询站长工具关键词,全网营销案例,小学网站模板下载,wordpress下载服务器文件夹3-JVM虚拟机 灵魂三问#xff1a; JVM是什么#xff1f; JVM广义上指的是一种规范。狭义上的是JDK中的JVM虚拟机。 为什么要学习JVM#xff1f; 面试过程中#xff0c;经常会被问到JVM。 研发过程中#xff0c;肯定会面临一些重难点问题与JVM有关系。例如#xff1a…3-JVM虚拟机 灵魂三问 JVM是什么 JVM广义上指的是一种规范。狭义上的是JDK中的JVM虚拟机。 为什么要学习JVM 面试过程中经常会被问到JVM。 研发过程中肯定会面临一些重难点问题与JVM有关系。例如线程死锁、内存溢出、项目性能优化等等。 基础不牢地动山摇。想深入掌握Java这门语言JVM始终是绕不过去的那座大山早晚得攀。 怎么学习JVM JVM虚拟机部分我们是这么安排的 JVM基本常识 类加载子系统 运行时数据区 一个对象的一生出生、死亡与内涵 GC垃圾收集器 JVM调优相关工具与可调参数 调优实战案例 1.JVM虚拟机概述 1.1 JVM 基本常识 什么是JVM 平时我们所说的JVM广义上指的是一种规范。狭义上的是JDK中的JVM虚拟机。JVM的实现是由各个厂商来做的。比如现在流传最广泛的是hotspot。其他实现BEA公司 JRocket、IBM j9、zing 号称世界最快JVM、taobao.vm。从广义上讲JavaKotlin、Clojure、JRuby、Groovy等运行于Java虚拟机上的编程语言及其相关的程序都属于Java技术体系中的一员。 Java技术体系主要包括如下四个方面。 Java程序设计语言 Java类库API 来自商业机构和开源社区的第三方Java类库 GoogleApache等等 Java虚拟机各种硬件平台上的Java虚拟机实现 可以简单类比一下Java虚拟机是宿主Java代码开发的程序则寄生在宿主上 JVM架构图 Java和JVM的关系 1.2 类加载子系统 1.2.1 类加载的时机 类加载主要有四个时机 遇到new 、 getstatic 、 putstatic和invokestatic这四条指令时如果对应的类没有初始化则要对对应的类先进行初始化。 public class Student{private static int age ;public static void method(){} } //Student.age //Student.method(); //new Student();使用java.lang.reflect 包方法时对类进行反射调用的时候。 Class c Class.forname(com.hero.Student);初始化一个类的时候发现其父类还没初始化要先初始化其父类 当虚拟机开始启动时用户需要指定一个主类main虚拟机会先执行这个主类的初始化。 1.2.2 类加载的过程 类加载主要做三件事 全限定名称 二进制字节流加载class文件 字节流的静态数据结构 方法区的运行时数据结构 创建字节码Class对象 一个类的一生 可以从哪些途径加载字节码 1.2.3 类加载器 JVM的类加载是通过ClassLoader及其子类来完成的。 检查顺序是自底向上加载过程中会先检查类是否被已加载从Custom ClassLoader到BootStrap ClassLoader逐层检查只要某个classloader已加载就视为已加载此类保证此类只所有 ClassLoader加载一次。 加载的顺序是自顶向下也就是由上层来逐层尝试加载此类。 启动类加载器(Bootstrap ClassLoader) 负责加载 JAVA_HOME\lib 目录中的或通过-Xbootclasspath参数指定路径中的且被虚拟机认可按文件名识别如rt.jar的类。由C实现不是ClassLoader的子类 扩展类加载器(Extension ClassLoader) 负责加载 JAVA_HOME\lib\ext 目录中的或通过java.ext.dirs系统变量指定路径中的类库。 应用程序类加载器(Application ClassLoader)负责加载用户路径classpath上的类库 自定义类加载器User ClassLoader 作用JVM自带的三个加载器只能加载指定路径下的类字节码如果某些情况下我们需要加载应用程序之外的类文件呢就需要用到自定义类加载器就像是在汽车行驶的时候为汽车更换轮子。比如本地D盘下的或者去加载网络上的某个类文件这种情况就可以使用自定义加载器了。举个栗子JRebel 自定义类加载器案例 目标自定义类加载器加载指定路径在D盘下的lib文件夹下的类。 步骤 新建一个需要被加载的类Test.jave 编译Test.jave到指定lib目录 自定义类加载器HeroClassLoader继承ClassLoader重写findClass()方法调用defineClass()方法 测试自定义类加载器 实现 1新建一个Test.java 类代码如下 package com.hero.jvm.classloader; public class Test {public void say(){System.out.println(Hello HeroClassLoader);} }2使用javac Test.java 命令将生成的Test.class 文件放到D:/lib/com/hero/jvm/classloader 文件夹下。 3自定义类加载器代码如下 package com.hero.jvm.classloader; import java.io.*; public class HeroClassLoader extends ClassLoader {private String classpath;public HeroClassLoader(String classpath) {this.classpath classpath;}Overrideprotected Class? findClass(String name) throws ClassNotFoundException{try {//输入流通过类的全限定名称加载文件到字节数组byte[] classDate getData(name);if (classDate ! null) {//defineClass方法将字节数组数据 转为 字节码对象return defineClass(name, classDate, 0, classDate.length);}} catch (IOException e) {e.printStackTrace();}return super.findClass(name);}//加载类的字节码数据private byte[] getData(String className) throws IOException {String path classpath File.separatorChar className.replace(.,File.separatorChar) .class;try (InputStream in new FileInputStream(path);ByteArrayOutputStream out new ByteArrayOutputStream()) {byte[] buffer new byte[2048];int len 0;while ((len in.read(buffer)) ! -1) {out.write(buffer, 0, len);}return out.toByteArray();} catch (FileNotFoundException e) {e.printStackTrace();}return null;} }4测试代码如下 package com.hero.jvm.classloader; import java.lang.reflect.Method; public class TestMyClassLoader {public static void main(String[] args) throws Exception{//自定义类加载器的加载路径HeroClassLoader hClassLoadernew HeroClassLoader(D:\\lib);//包名类名Class chClassLoader.loadClass(com.hero.jvm.classloader.Test);if(c!null){Object objc.newInstance();Method methodc.getMethod(say, null);method.invoke(obj, null);System.out.println(c.getClassLoader().toString());}} }输出结果如下 1.2.4 双亲委派模型与打破双亲委派 1什么是双亲委派 当一个类加载器收到类加载任务会先交给其父类加载器去完成。因此最终加载任务都会传递到顶层的启动类加载器只有当父类加载器无法完成加载任务时子类才会尝试执行加载任务。 Oracle 官网文档描述 The Java Class Loading Mechanism The Java platform uses a delegation model for loading classes. The basic idea is that every class loader has a “parent” class loader. When loading a class, a class loader first “delegates” the search for the class to its parent class loader before attempting to find the class itself. ------ Oracel Document https://docs.oracle.com/javase/tutorial/ext/basics/load.html 看到这里应该叫父亲委派对吧那么为什么要叫双亲委派呢因为最早的翻译者导致双亲委派的概念流行起来了。 2为什么需要双亲委派呢 考虑到安全因素双亲委派可以避免重复加载当父亲已经加载了该类的时候就没有必要子 ClassLoader再加载一次。 比如加载位于rt.jar包中的类java.lang.Object不管是哪个加载器加载这个类最终都是委托给顶层的启动类加载器进行加载这样就保证了使用不同的类加载器最终得到的都是同样一个Object对象。 3双亲委派机制源码 protected Class? loadClass(String name, boolean resolve) throws ClassNotFoundException{synchronized (getClassLoadingLock(name)) {// 首先, 检查class是否被加载如果没有加载则进行加载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;} }4为什么还需要破坏双亲委派 在实际应用中双亲委派解决了Java基础类统一加载的问题但是却存在着缺陷。JDK中的基础类作为典型的api被用户调用但是也存在api调用用户代码的情况典型的如SPI代码。这种情况就需要打破双亲委派模式。 举个栗子数据库驱动DriverManager。以Driver接口为例Driver接口定义在JDK中其实现由各个数据库的服务商来提供由系统类加载器加载。这个时候就需要启动类加载器来委托子类来加载Driver实现这就破坏了双亲委派。类似情况还有很多 5如何破坏双亲委派 第一种方式 在 jdk 1.2 之前那时候还没有双亲委派模型不过已经有了 ClassLoader这个抽象类所以已经有人继承这个抽象类重写 loadClass方法来实现用户自定义类加载器。 而在 1.2 的时候要引入双亲委派模型为了向前兼容 loadClass 这个方法还得保留着使之得以重 写新搞了个 findClass 方法让用户去重写并呼吁大家不要重写 loadClass只要重写 findClass。这就是第一次对双亲委派模型的破坏因为双亲委派的逻辑在 loadClass 上但是又允许重写 loadClass重写了之后就可以破坏委派逻辑了。 第二种方式 双亲委派机制是一种自上而下的加载需求越往上类越基础。 SPI代码打破了双亲委派 DriverManager源码 static {loadInitialDrivers();println(JDBC DriverManager initialized); }private static void loadInitialDrivers() {String drivers;try {drivers AccessController.doPrivileged(new PrivilegedActionString() {public String run() {return System.getProperty(jdbc.drivers);}});} catch (Exception ex) {drivers null;}AccessController.doPrivileged(new PrivilegedActionVoid() {public Void run() {ServiceLoaderDriver loadedDrivers ServiceLoader.load(Driver.class);IteratorDriver driversIterator loadedDrivers.iterator();try{while(driversIterator.hasNext()) {driversIterator.next();}} catch(Throwable t) {// Do nothing}return null;}});println(DriverManager.initialize: jdbc.drivers drivers);if (drivers null || drivers.equals()) {return;}String[] driversList drivers.split(:);println(number of Drivers: driversList.length);for (String aDriver : driversList) {try {println(DriverManager.Initialize: loading aDriver);//在这里需要加载各个厂商实现的数据库驱动com.mysql.jdbc.DriverClass.forName(aDriver, true,ClassLoader.getSystemClassLoader());} catch (Exception ex) {println(DriverManager.Initialize: load failed: ex);}} }如果出现SPI相关代码时我们应该如何解决基础类去加载用户代码类呢 这个时候JVM不得不妥协推出线程上下文类加载器的概念去解决该问题。这样也就打破了双亲委派 线程上下文类加载器 ThreadContextClassLoader 设置线程上下文类加载器源码 public Launcher() {// Create the extension class loaderClassLoader extcl;try {// 扩展类加载器extcl ExtClassLoader.getExtClassLoader();} catch (IOException e) {throw new InternalError(Could not create extension class loader, e);}// Now create the class loader to use to launch the applicationtry {// 应用类加载器/系统类加载器loader AppClassLoader.getAppClassLoader(extcl);} catch (IOException e) {throw new InternalError(Could not create application class loader, e);}// 线程上下文类加载器 // 同时为原始线程设置上下文类加载器 Thread.currentThread().setContextClassLoader(loader);// 最后如果需要安装安全管理器 String s System.getProperty(java.security.manager); if (s ! null) {SecurityManager sm null;if (.equals(s) || default.equals(s)) {sm new java.lang.SecurityManager();} else {try {sm (SecurityManager) loader.loadClass(s).newInstance();} catch (IllegalAccessException | InstantiationException | ClassNotFoundException | ClassCastException e) {// 处理异常此处省略具体操作}}if (sm ! null) {System.setSecurityManager(sm);} else {throw new InternalError(Could not create SecurityManager: s);} }获取线程上下文类加载器源码 public static S ServiceLoaderS load(ClassS service) {ClassLoader cl Thread.currentThread().getContextClassLoader();return ServiceLoader.load(service, cl); }第三种方式 为了满足热部署、不停机更新需求。OSGI 就是利用自定义的类加载器机制来完成模块化热部署而它实现的类加载机制就没有完全遵循自下而上的委托有很多平级之间的类加载器查找具体就不展开了有兴趣可以自行研究一下。 1.3 运行时数据区 整个JVM构成里面由三部分组成类加载系统、运行时数据区、执行引擎 按照线程使用情况和职责分成两大类 线程独享 程序执行区域) 不需要垃圾回收虚拟机栈、本地方法栈、程序计数器 线程共享 数据存储区域 垃圾回收存储类的静态数据和对象数据堆和方法区 1.3.1 堆 Java堆在JVM启动时创建内存区域去实现对象、数组与运行时常量的内存分配它是虚拟机管理最大的也是垃圾回收的主要内存区域。 内存划分 核心逻辑就是三大假说基于程序运行情况进行不断的优化设计。 堆内存为什么会存在新生代和老年代 分代收集理论当前商业虚拟机的垃圾收集器大多数都遵循了分代收集Generational Collection的理论进行设计分代收集名为理论实质是一套符合大多数程序运行实际情况的经验法则它建立在两个分代假说之上 弱分代假说Weak Generational Hypothesis绝大多数对象都是朝生夕灭的。 强分代假说Strong Generational Hypothesis熬过越多次垃圾收集过程的对象就越难以消亡。 这两个分代假说共同奠定了多款常用的垃圾收集器的一致的设计原则收集器应该将Java堆划分出不同的区域然后将回收对象依据其年龄年龄即对象熬过垃圾收集过程的次数分配到不同的区域之中存储。 如果一个区域中大多数对象都是朝生夕灭难以熬过垃圾收集过程的话那么把它们集中放在一起每次回收时只关注如何保留少量存活而不是去标记那些大量将要被回收的对象就能以较低代价回收到大量的空间 如果剩下的都是难以消亡的对象那把它们集中放在一块虚拟机便可以使用较低的频率来回收这个区域。 这就同时兼顾了垃圾收集的时间开销和内存的空间有效利用。 为什么新生代里面需要有两个Survivor区域呢 这个咱们在垃圾收集器小节进行解释 内存模型变迁 JDK1.7 Young 年轻区 主要保存年轻对象分为三部分Eden区、两个Survivor区。 Tenured 年老区主要保存年长对象当对象在Young复制转移一定的次数后对象就会被转移到Tenured区。 Perm 永久区 主要保存class、method、filed对象这部份的空间一般不会溢出除非一次性加载了很多的类不过在涉及到热部署的应用服务器的时候有时候会遇到OOM : PermGen space 的错误。 Virtual区 最大内存和初始内存的差值就是Virtual区。 JDK1.8 由2部分组成新生代Eden 2\Survivor 年老代OldGen JDK1.8中变化最大的是Perm永久区用Metaspace进行了替换 注意Metaspace所占用的内存空间不是在虚拟机内部而是在本地内存空间中。区别于JDK1.7 JDK1.9 取消新生代、老年代的物理划分 将堆划分为若干个区域Region这些区域中包含了有逻辑上的新生代、老年代区域 储物收纳 内存信息案例 package com.hero.jvm.memory;/*** -Xms100m -Xmx100m*/ public class HeapDemo {public static void main(String[] args) {System.out.println(start);try {Thread.sleep(1000000L);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(end);} }JDK6堆内存结构 C:\develop\java\jdk1.6.0_45\bin\javac HeapDemo.java C:\develop\java\jdk1.6.0_45\bin\java -Xms100m -Xmx100m HeapDemo C:\develop\java\jdk1.6.0_45\bin\jmap -heap 3612JDK7堆内存结构 C:\develop\java\jdk1.7.0_80\bin\javac HeapDemo.java C:\develop\java\jdk1.7.0_80\bin\java -Xms100m -Xmx100m HeapDemo C:\develop\java\jdk1.7.0_80\bin\jmap -heap 10420JDK8堆内存结构 C:\develop\java\jdk1.8.0_251\bin\javac HeapDemo.java C:\develop\java\jdk1.8.0_251\bin\java -Xms100m -Xmx100m HeapDemo C:\develop\java\jdk1.8.0_251\bin\jmap -heap 18276JDK11堆内存结构 C:\develop\java\jdk-11.0.7\bin\javac HeapDemo.java C:\develop\java\jdk-11.0.7\bin\java -Xms100m -Xmx100m HeapDemo C:\develop\java\jdk-11.0.7\bin\jhsdb jmap --heap --pid 193801.3.2 虚拟机栈 1栈帧是什么 栈帧(Stack Frame)是用于支持虚拟机进行方法执行的数据结构。 栈帧存储了方法的局部变量表、操作数栈、动态连接和方法返回地址等信息。每一个方法从调用至执行完成的过程都对应着一个栈帧在虚拟机栈里从入栈到出栈的过程。 栈内存为线程私有的空间每个线程都会创建私有的栈内存生命周期与线程相同每个Java方法在执行的时候都会创建一个栈帧Stack Frame。栈内存大小决定了方法调用的深度栈内存过小则会导致方法调用的深度较小如递归调用的次数较少。 2当前栈帧 一个线程中方法的调用链可能会很长所以会有很多栈帧。只有位于JVM虚拟机栈栈顶的元素才是有效的即称为当前栈帧与这个栈帧相关连的方法称为当前方法定义这个方法的类叫做当前类。 执行引擎运行的所有字节码指令都只针对当前栈帧进行操作。如果当前方法调用了其他方法或者当前方法执行结束那这个方法的栈帧就不再是当前栈帧了。 3什么时候创建栈帧 调用新的方法时新的栈帧也会随之创建。并且随着程序控制权转移到新方法新的栈帧成为了当前栈帧。方法返回之际原栈帧会返回方法的执行结果给之前的栈帧(返回给方法调用者)随后虚拟机将会丢弃此栈帧。 4栈异常的两种情况 如果线程请求的栈深度大于虚拟机所允许的深度Xss默认1m会抛出StackOverflowError异常如果在创建新的线程时没有足够的内存去创建对应的虚拟机栈会抛出OutOfMemoryError异常 【不一定】 5栈异常案例 如果线程请求的栈深度大于虚拟机所允许的深度将会抛出StackOverflowError异常-Xss package com.hero.jvm.memory;public class StackErrorMock {private static int index 1;public void call() {index;call();}public static void main(String[] args) {StackErrorMock mock new StackErrorMock();try {mock.call();} catch (Throwable e) {System.out.println(Stack deep: index);e.printStackTrace();}} }C:\develop\java\jdk1.8.0_251\bin\javac StackErrorMock.java C:\develop\java\jdk1.8.0_251\bin\java -Xss1m StackErrorMock C:\develop\java\jdk1.8.0_251\bin\java -Xss256k StackErrorMock 补充案例用来演示大量创建线程撑爆内存会发生什么 思考题如果创建海量线程线程的时候同时每个线程疯狂递归请问到底是先OOM还是 StackOverflowError public class TestThread {public static void main(String[] args) {for (int i 0; i 100000; i) {new Thread(Thread- i) {Overridepublic void run() {try {String name Thread.currentThread().getName();System.out.println(name);recursive(30000);TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}System.out.println();}}.start();}}public static void recursive(double d) {if (d 0)return;recursive(d - 1);} }1.3.3 本地方法栈 本地方法栈和虚拟机栈相似区别就是虚拟机栈为虚拟机执行Java服务字节码服务而本地方法栈为虚拟机使用到的Native方法比如C方法服务。 简单地讲一个Native Method就是一个Java调用非Java代码的接口。 public class IHaveNatives {native public void Native1(int x);native static public long Native2();native synchronized private float Native3(Object o);native void Native4(int[] ary) throws Exception; }为什么需要本地方法 Java是一门高级语言我们不直接与操作系统资源、系统硬件打交道。如果想要直接与操作系统与硬件打交道就需要使用到本地方法了。说白了Java可以直接通过native方法调用cpp编写的接口多线程底层就是这么实现的在多线程部分我们会看一下Thread实现的源码到时候就可以理解了。 1.3.4 方法区 方法区Method Area是可供各个线程共享的运行时内存区域方法区本质上是Java语言编译后代码存储区域它存储每一个类的结构信息例如运行时常量池、成员变量、方法数据、构造方法和普通方法的字节码指令等内容。很多语言都有类似区域。 方法区的具体实现有两种永久代PermGen、元空间Metaspace 1方法区存储什么数据 {width“4.999998906386701in” height“3.3958333333333335in”} 主要有如下三种类型第一Class 类型信息比如Classcom.hero.User类 方法信息比如Method方法名称、方法参数列表、方法返回值信息 字段信息比如Field字段类型字段名称需要特殊设置才能保存的住 类变量静态变量JDK1.7之后转移到堆中存储 方法表方法调用的时候 在A类的main方法中去调用B类的method1方法是根据B类的方法表去查找合适的方法进行调用的。 第二运行时常量池字符串常量池从class中的常量池加载而来JDK1.7之后转移到堆中存储 字面量类型 引用类型–内存地址 第三JIT编译器编译之后的代码缓存 如果需要访问方法区中类的其他信息都必须先获得Class对象才能取访问该Class对象关联的方法信息或者字段信息。 2永久代和元空间的区别是什么 JDK1.8之前使用的方法区实现是永久代JDK1.8及以后使用的方法区实现是元空间。 存储位置不同 永久代所使用的内存区域是JVM进程所使用的区域它的大小受整个JVM的大小所限制。 元空间所使用的内存区域是物理内存区域。那么元空间的使用大小只会受物理内存大小的限制。 存储内容不同 永久代存储的信息基本上就是上面方法区存储内容中的数据。 元空间只存储类的元信息而静态变量和运行时常量池都挪到堆中。 3为什么要使用元空间来替换永久代 字符串存在永久代中容易出现性能问题和永久代内存溢出。 类及方法的信息等比较难确定其大小因此对于永久代的大小指定比较困难太小容易出现永久代溢出太大则容易导致老年代溢出。 永久代会为 GC 带来不必要的复杂度并且回收效率偏低。 Oracle 计划将HotSpot 与 JRockit 合二为一。 方法区实现变迁历史 {width“6.160161854768154in” height“3.147603893263342in”} 移除永久代的工作从JDK1.7就开始了。JDK1.7中存储在永久代的部分数据就已经转移到了Java Heap。但永久代仍存在于JDK1.7中并没完全移除譬如字面量转移到了java heap类的静态变量(class statics)转移到了java heap。 4字符串OOM异常案例案例代码 以下这段程序以2的指数级不断的生成新的字符串这样可以比较快速的消耗内存 package com.hero.jvm.memory; import java.util.ArrayList; import java.util.List;public class StringOomMock {static String base string;public static void main(String[] args) {ListString list new ArrayListString();for (int i 0; i Integer.MAX_VALUE; i) {String str base base;base str;list.add(str.intern());}} }JDK1.6 JDK 1.6 的运行结果{width“5.867308617672791in” height“0.5523392388451444in”} 在JDK 1.6下会出现永久代的内存溢出。 JDK1.7 C:\develop\java\jdk1.7.0_80\bin\javac StringOomMock.java C:\develop\java\jdk1.7.0_80\bin\java -XX:PermSize8m -XX:MaxPermSize8m - Xmx16m StringOomMockJDK 1.7的运行结果{width“5.902015529308836in” height“0.9854155730533684in”} 在JDK 1.7中会出现堆内存溢出。 结论是JDK 1.7 已经将字符串常量由永久代转移到堆中。 JDK1.8 C:\develop\java\jdk1.8.0_251\bin\javac StringOomMock.java C:\develop\java\jdk1.8.0_251\bin\java -XX:PermSize8m -XX:MaxPermSize8m - Xmx16m StringOomMockJDK 1.8的运行结果 {width“5.782699037620297in” height“1.0887489063867017in”} 在JDK 1.8 中也会出现堆内存溢出并且显示 JDK 1.8中 PermSize 和 MaxPermGen 已经无效。 结论是可以验证 JDK 1.8 中已经不存在永久代的结论。 1.3.5 字符串常量池 1三种常量池的比较 class常量池一个class文件只有一个class常量池 字面量数值型int、float、long、double、双引号引起来的字符串值等符号引用Class、Method、Field等 运行时常量池一个class对象有一个运行时常量池 字面量数值型int、float、long、double、双引号引起来的字符串值等符号引用Class、Method、Field等 字符串常量池全局只有一个字符串常量池 双引号引起来的字符串值 {width“6.202195975503062in” height“3.19in”} 2字符串常量池如何存储数据 为了提高匹配速度 即更快的查找某个字符串是否存在于常量池 Java 在设计字符串常量池的时候还搞了一张StringTable StringTable里面保存了字符串的引用。StringTable类似于HashTable哈希表。在JDK1.7StringTable可以通过参数指定-XX:StringTableSize99991 什么是哈希表呢 哈希表Hash table也叫散列表是根据关键码值(Key value)而直接进行访问的数据结构。也就是说它通过把关键码值映射到表中一个位置来访问记录以加快查找的速度。这个映射函数叫做散列函数存放记录的数组叫做散列表。 哈希表本质上是一个数组链表 目的 : 为了加快数据查找的速度。 存在问题hash冲突问题一旦出现冲突那么就会形成链表链表的特点是增删快但查询慢。数组下标计算公式hash(字符串) % 数组长度 数组中存储的是Entry通过指针next形成链表 {width“4.741064085739283in” height“3.6678116797900264in”} HashMapString, Integer map new HashMap(); map.put(hello, 53); map.put(world, 35); map.put(java, 55); map.put(world, 52); map.put(通话, 51); map.put(重地, 55);3字符串常量池如何查找字符串 根据字符串的hashcode找到对应entry如果没有冲突它可能只是一个entry 如何有冲突它可能是一个entry的链表然后Java再遍历链表匹配引用对应的字符串如果找到字符串返回引用 如果找不到字符串在使用intern()方法的时候会将intern()方法调用者的引用放入到stringtable中 {width“4.354166666666667in” height“3.8333333333333335in”} 4字符串常量池案例 import java.util.HashMap;public class StringTableDemo {public static void main(String[] args) {HashMapString, Integer map new HashMap();map.put(hello, 53);map.put(world, 35);map.put(java, 55);map.put(world, 52);map.put(通话, 51);map.put(重地, 55);test();}public static void test() {String str1 abc;String str2 new String(abc);System.out.println(str1 str2); // falseString str3 new String(abc);System.out.println(str3 str2); // falseString str4 a b;System.out.println(str4 ab); // trueString s1 a;String s2 b;String str6 s1 s2;System.out.println(str6 ab); // falseString str7 abc.substring(0, 2);System.out.println(str7 ab); // falseString str8 abc.toUpperCase();System.out.println(str8 ABC); // falseString s5 a;String s6 abc;String s7 s5 bc;System.out.println(s6 s7.intern()); // true} }总结 单独使用“引号创建的字符串都是常量编译期就已经确定存储到String Pool中。使用new String(”)创建的对象会存储到heap中是运行期新创建的。 使用只包含常量的字符串连接符如aabb创建的也是常量编译期就能确定已经存储到StringPool中。 使用包含变量的字符串连接如aas创建的对象是运行期才创建的存储到heap中。 运行期调用String的intern()方法可以向String Pool中动态添加对象。 1.3.6 程序计数器 程序计数器Program Counter Register也叫PC寄存器是一块较小的内存空间它可以看作是当前线程所执行的字节码指令的行号指示器。字节码解释器的工作就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。分支循环跳转异常处理线程回复等都需要依赖这个计数器来完成。 为什么需要程序计数器 由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的在任何一个确定的时刻一个处理器针对多核处理器来说是一个内核都只会执行一条线程中的指令。因此为了线程切换系统上下文切换后能恢复到正确的执行位置每条线程都需要有一个独立的程序计数器各条线程之间计数器互不影响独立存储我们称这类内存区域为线程私有的内存。 存储的什么数据 如果一个线程正在执行的是一个Java方法这个计数器记录的是正在执行的虚拟机字节码指令的地址如果正在执行的是一个Native方法这个计数器的值则为空。 异常此内存区域是唯一一个在Java的虚拟机规范中没有规定任何OutOfMemoryError异常情况的区域。 1.3.7 直接内存 直接内存并不是虚拟机运行时数据区的一部分也不是Java 虚拟机规范中定义的内存区域。在JDK1.4 中新加入了NIO(New Input/Output)类引入了一种基于通道(Channel)与缓冲区Buffer的I/O 方式它可以使用native 函数库直接分配堆外内存然后通过一个存储在Java堆中的DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能因为避免了在Java堆和Native堆中来回复制数据。 本机直接内存的分配不会受到Java 堆大小的限制受到本机总内存大小限制。 直接内存堆外内存与堆内存比较 直接内存申请空间耗费更高的性能当频繁申请到一定量时尤为明显 直接内存IO读写的性能要优于普通的堆内存在多次读写操作的情况下差异明显 直接内存案例 package com.hero.jvm.memory; import java.nio.ByteBuffer;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);} }输出 在进行10000000次分配操作时堆内存 分配耗时:82ms 在进行10000000次分配操作时直接内存分配耗时:6817ms 在进行1000000000次读写操作时堆内存读写耗时1137ms 在进行1000000000次读写操作时直接内存读写耗时:512ms 为什么会是这样 从数据流的角度来看 非直接内存作用链本地IO–直接内存–非直接内存–直接内存–本地IO 直接内存作用链本地IO–直接内存–本地IO 直接内存的使用场景 有很大的数据需要存储它的生命周期很长适合频繁的IO操作例如网络并发场景 今日总结 0 1-JVM基本常识 什么是JVM广义上的JVM是指一种规范狭义上的JVM指的是Hotspot类的虚拟机实现 Java语言与JVM的关系Java语言编写程序生成class字节码在JVM虚拟机里执行。其他语言也可以比如Scala、Groovy 学习JVM主要学啥类加载子系统 --\ 运行时数据区 --\一个对象的一生–\ GC垃圾收集器 学了JVM可以干啥JVM调优底层能力 决定 上层建筑 0 2-类加载子系统 类加载四个时机1.new、getstatic、putstatic、invokestatic。2.反射。3.初始化子类发现父类没有初始化时。4.main函数的类 类加载主要过程加载 - 验证 - 准备 - 解析 -初始化 -使用 -卸载 类加载主要做了三件事 全限定名称 二进制字节流加载class文件字节流的静态数据结构 方法区的运行时数据结构创建字节码Class对象 可以从哪些途径加载字节码 Jar、war JSP生成的class 数据库中二进制字节流网络中二进制字节流 动态代理生成的二进制字节流 类加载器有哪些 启动类加载器BootstrapClassLoader扩展类加载器ExtensionClassLoader应用类加载器ApplicationClassLoader自定义类加载器UserClassLoader检查顺序自底向上加载顺序自顶向下 什么是双亲委派当一个类加载器收到加载任务会先交给其父类加载器去加载 为何要打破双亲委派父类加载器加载范围受限无法加载的类需要委托子类加载器去完成加载 0 3-运行时数据区 堆JVM启动是创建的最大的一块内存区域对象数组运行时常量池都在这里内存划分Eden、2个Survivor、老年代 为什么要划分新生代与老年代基于分代收集理论里的量大假说弱分代和强分代假说。提升垃圾收集的效率。 内存模型变迁史JDK1.7 —取消永久代多了元空间— JDK1.8 —取消新生代与老年代物理划分— JDK1.9 虚拟机栈栈空间为线程私有每个线程都会创建栈内存生命周期与线程相同 线程内的栈内存占满了会出现StackOverflowError 栈帧是什么栈帧(StackFrame)是用于支持虚拟机进行方法执行的数据结构。 本地方法栈与虚拟机栈类似区别在于本地方法栈为本地方法服务也就是native方法方法区方法区的实现有两种永久代PermGen、元空间Metaspace 方法区存什么数据类型信息方法信息字段信息类变量信息方法表指向类加载器的引用指向Class实例的引用 永久代和元空间有什么区别 存储位置不同存储内容不同 为什么要使用元空间来替换永久代 基于性能、稳定性、GC垃圾收集的复杂度考虑当然也有Oracle收购了Java原因 字符串常量池 {#字符串常量池-1} 三种常量池class常量池、运行时常量池、字符串常量池 字符串常量池如何存储数据使用哈希表【哈希冲突哈希碰撞…】字符串常量池如何查找字符串类似于HashMap 程序计数器 存储什么数据当前线程执行时的字节码指令地址为什么需要程序计数器因为系统的上下文切换 直接内存 相对堆内存直接内存申请空间更耗时 直接内存IO读写的性能要优于普通的堆内存
http://www.zqtcl.cn/news/188394/

相关文章:

  • 有没有专门做名片的网站忘记网站后台账号
  • 重庆建设工程招标网站印尼建设银行网站
  • 什么是网站流量优化四川住房建设厅网站
  • 现在还有企业做网站吗做百度推广送的网站
  • 公司年前做网站好处互联网推广运营是做什么的
  • 公司网站建设杭州钓鱼网站制作的报告
  • 宁海有做网站的吗网络规划设计师需要掌握哪些
  • 百度云注册域名可以做网站明码有了主机如何做网站
  • 门户网站推广方案连云港市电信网站建设
  • 网站程序如何制作app商城开发价格
  • 用易语言做攻击网站软件国药控股北京有限公司
  • 宁津 做网站湛江招聘网最新招聘
  • 网站建设优化服务器asp企业网站
  • 门窗网站源码建筑模板厂家联系方式
  • 太原网站建设解决方案做建筑机械网站那个网站好
  • 丹徒做网站产品外贸营销推广方案
  • 信息技术 网站建设教案做是么网站
  • 网站建设培训报名wordpress 到小程序
  • 郑州做网站软件建设网站培训
  • 做网站卖东西赚钱吗凡科互动官网登陆
  • 免费写作网站通道一通道二通道三免费
  • 腾讯云做网站选哪个网络广告推广员
  • 昆明网站开发哪家好做网站怎么做小图标
  • 泉州做外贸网站成都idc机房托管
  • 南京林业大学实验与建设网站宁波软件开发制作
  • 北京专业网站制作服务郑州有学网站制作
  • 搭建wordpress需要什么样的环境专注软件优化分享的网站
  • 网站备案后 如何建设下载免费ppt模板
  • 重慶网站建设网络服务器可提供的常见服务有什么、什么、什么和什么服务
  • 网站制作有限公司英文都不懂 学网站建设维护难吗