自动内存管理

查看pdf/深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)周志明.pdf

java内存区域与内存异常

垃圾收集器与内存分配策略

  1. 背景
    1.1. 内存动态分类与内存回收技术已经相当成熟,为什么还要去了解垃圾收集和内存分配?答案:当需要排查各种内存溢出、内存泄漏问题时,当垃圾收集成为系统达到更高并发量瓶颈时,必须对这些“自动化”的技术实施必要的监控和调节。
    1.2. 程序计数器、虚拟机栈、本地方法栈在类结构确定下来时,就知道分配多少内存;java堆和方法区有着明显的不确定性,这部分内存的分配和回收时动态的

  2. 判断对象已死?
    2.1. 引用计数(没有主流Java虚拟机选用)
    2.2. 可达性分析(主流商用程序语言选用)
    2.2.1. 固定可以作为GC Roots的对象

    • 虚拟机栈中引用的对象(栈帧的本地变量表)
    • 方法区中类的静态属性以用的对象
    • 方法区中常量引用的对象
    • 本地方法栈中JNI(Native方法)引用的对象
    • Java虚拟机内部的引用,基本类型class对象、异常对象、系统类加载器
    • 所有被同步锁(synchronized关键字)持有的对象
    • 反应Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等
      2.2.2. 第一次标记,处于缓刑阶段,至少两次标记确定是否回收
  1. 生存还是死亡?
    3.1. 两次标记过程:
    如果对象在进行可达性分析后发现没有鱼GC Roots相连接的引用链,将会第一次标记;
    随后进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法,当对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过,虚拟机将这两种情况视为“没有必要执行”。
    如果这个对象被判定有必要执行finalize()方法,那么这个对象将会被放置到一个名为F-Queue的队列中,并在稍后由一条由虚拟机自动创建、低优先级的Finalizer线程去执行它们的finalize()方法。,这里“执行”是值虚拟机会触发这个方法开始运行,并不承诺一定等待它运行结束,原因:如果某个对象的finalize()方法执行缓慢,或者更极端地发生了死循环,将可能导致F-Queue队列中的其他对象永久处于等待,甚至导致整个内存回收子系统的崩溃
    3.2. 强调
    finalize()方法是对象逃脱死亡命运的最后一次机会—————只要重新与引用链上的任何一个对象建立关联即可。譬如把自己赋值给某个类变量或对象的成员变量,那么在第二次标记时他将被移除出“即将回收”的集合;如果对象这个时候还没有逃脱,那基本上它就真的要被回收了
    注意:任何一个对象的finalize()方法都只会被系统自动调用一次,如果对象面临下一次回收,它的finalize()方法不会被再次执行。
    特别说明:finalize()能做的所有工作,使用try-finally或者其他方式都可以做的更好,不建议使用finalize()

  2. 回收方法区
    4.1. 方法区的垃圾收集主要回收两部分内容:废弃的常量、不再使用的类型
    说明:方法区回收性价比很低
    4.2. 判断一个常量是否“废弃”相对简单,当前系统是否有任何一个对象的值是此常量,换句话说,若没有任何对象引用常量池中的常量,且虚拟机中也没有其他地方引用这个常量,如果在这时候发生内存回收,而且垃圾收集器判断却有必要的话,此常量就将会被系统清理出常量池。常量池中的其他类(接口)、方法、字段的符号引用也与此类似。
    4.3. 判断一个类型是否属于“不再被使用的类”的条件比较苛刻,同时满足三个条件,才被允许回收:

    • 该类所有的实例都已经被回收,也就是Java堆中不存在该类及其任何派生子类的实例。
    • 加载该类的ClassLoader已经被回收。(很难达成,除非精心设计过可替换类加载器的场景)
    • 该类对应的java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
    • 说明:在大量使用反射、动态代理、CGlib等字节码框架,动态生成JSP以及OSGi这类频繁自定义类加载器的场景中。需要jvm具备类型卸载能力。以保证不会对方法区造成过大内存压力
  3. 垃圾收集算法(方法论)
    只说思想理论,不聊程序细节
    5.1. 从判定对象是否消亡角度出发,垃圾收集算法可分为:引用计数式垃圾收集(直接垃圾收集)【主流不涉及,暂不讨论】、追踪式垃圾收集(间接垃圾收集)
    5.2. 追踪式垃圾收集相关算法
    5.2.1. 分代收集理论【商业jvm的选择】
    分代收集,名为理论,实质是一套符合大多数程序运行实际情况的经验法则,两个分代假说:
  • 弱分代假说:绝大多数对象都是朝生夕死的
  • 强分代假说:熬过越多次垃圾收集过程的对象就越难消亡
    这两个分代假说共同奠定了多款常用的垃圾收集器的一致的设计原则:收集器应该将Java堆划分出不同的区域,然后将回收对象依据其年龄(年龄即对象熬过垃圾收集过程的次数)分配到不同的区 域之中存储。
    简单来说:如果一个区域中大多数对象都是朝生夕死,难以熬过垃圾收集过程的话,那么把他们集中在一起,每次收集时只关注如果保留少量存活,以较低的代价回收大量的空间;如果剩下的都是难以消亡的对象,那么把他们集中在一起,虚拟机便可以使用较低的频率来回收这个区域,同时兼顾了垃圾收集的时间开销和内存空间有效利用
    Java堆划分出不同区域之后,才出现回收类型的划分(partial[minor/young major/old、mixed]、full GC);也才能够针对不同的区域安排与里面存储对象存亡特征相匹配的垃圾收集算法(标记-复制、清除、整理)
    现在常用jvm至少分为两个区域:新生代、老年代
    至少存在一个明显的困难:对象不是孤立的,对象之间会存在跨代引用。保证GC Roots可达性分析的正确性,需要跨代遍历,内存回收负担很大。添加第三条经验法则
  • 跨代引用假说:跨代引用相对于同代引用来说仅占极少数
    解决跨代引用的办法:只需在新生代上建立一个全局的数据结构(该结构被称 为“记忆集”,Remembered Set),这个结构把老年代划分成若干小块,标识出老年代的哪一块内存会 存在跨代引用。此后当发生Minor GC时,只有包含了跨代引用的小块内存里的对象才会被加入到GC Roots进行扫描。
    注意:值得注意的是,分代收集理论也有其缺陷,最新出现(或在实验中)的几款垃圾收集器都展现出了 面向全区域收集设计的思想,或者可以支持全区域不分代的收集的工作模式。
    5.2.2. 标记—清除算法
    5.2.3. 标记—复制算法
    5.2.4. 标记—整理算法
  1. 经典垃圾收集器(实践)
    在JDK 7 Update 4之后(在这个版本中正式提供了商用的G1收集器,此前G1仍处于实验状态)、JDK 11正式发布之前,OracleJDK中的HotSpot虚拟机所包含的全部可用的垃圾收集器

虚拟机执行执行子系统

类文件结构

虚拟机类加载机制

  1. aaa
  2. aaaa

补充

类加载

.class文件

  1. Java中,程序是直接运行在jvm中
  2. 通常,编写.java源码文件,但不能直接运行
  3. javac XXX.java,进行源文件的编译,生成XXX.class文件(C或python正确编译的问题件jvm也可以识别运行)
  4. java XXX,执行命令

Java环境变量

  1. JAVA_HOME
    JDK安装位置
  2. PATH
    将Java程序包路径(jdk\bin;jdk\jre\bin)配置在PATH后,java、javac等命令在命令行中就不需要输入全路径了
  3. CLASSPATH
    jar包路径,包括(.;jdk\lib;jdk\lib\tools.jar),其中【.】表示当前文件夹

Java类加载流程

JVM设计者把类加载阶段中的通过’类全名’来获取定义此类的二进制字节流这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块称为“类加载器”。

  1. 类与类加载器
    对于任何一个类,都需要由加载它的类加载器和这个类来确立其在JVM中的唯一性。也就是说,两个类来源于同一个Class文件,并且被同一个类加载器加载,这两个类才相等。
  2. 从虚拟机的角度来说,只存在两种不同的类加载器
  • 启动类加载器(Bootstrap ClassLoader),该类加载器使用C++语言实现,属于虚拟机自身的一部分。
  • 所有其它的类加载器,这些类加载器是由Java语言实现,独立于JVM外部,并且全部继承自抽象类java.lang.ClassLoader。
  1. 三个常用类加载器
  • Bootstrap ClassLoader最顶层的加载类,主要加载核心类库,jdk\lib下的rt.jar、resources.jar、charsets.jar和calss等。通过启动jvm时指定java -Xbootclasspath路径来改变Bootstrap ClassLoader的加载目录
  • Extention ClassLoader扩展的类加载器,加载目录jdk\lib\ext目录下的jar包和class文件,通过启动jvm时指定-D java.ext.dirs目录
  • AppclassLoader(SystemAppClass)加载当前工程目录的classpath的所有类
  1. 加载顺序

    1. Bootstrap ClassLoader—System.getProperty()
    2. ExtClassLoader
    3. AppClassLoader
      源码(sun.misc.Launcher,Jvm的入口应用) ```java public class Launcher { private static Launcher launcher = new Launcher(); private static String bootClassPath = System.getProperty(“sun.boot.class.path”);

    public static Launcher getLauncher() { return launcher; }

    private ClassLoader loader;

    public Launcher() { // Create the extension class loader ClassLoader extcl; try {

    1. extcl = ExtClassLoader.getExtClassLoader();

    } catch (IOException e) {

    1. throw new InternalError(
    2. "Could not create extension class loader", e);

    }

    // Now create the class loader to use to launch the application try {

    1. loader = AppClassLoader.getAppClassLoader(extcl);

    } catch (IOException e) {

    1. throw new InternalError(
    2. "Could not create application class loader", e);

    }

    //设置AppClassLoader为线程上下文类加载器,这个文章后面部分讲解 Thread.currentThread().setContextClassLoader(loader); }

    /*

    • Returns the class loader used to launch the main application. / public ClassLoader getClassLoader() { return loader; } /
    • The class loader used for loading installed extensions. */ static class ExtClassLoader extends URLClassLoader {}

    /**

    • The class loader used for loading from java.class.path.
    • runs in a restricted security context. */ static class AppClassLoader extends URLClassLoader {} }
      1. ```java
      2. static class ExtClassLoader extends URLClassLoader {}
      3. static class AppClassLoader extends URLClassLoader {}
  2. 类加载器都有父加载器(区别父类)
    getParent()在ClassLoader.java中

    1. public abstract class ClassLoader {
    2. // The parent class loader for delegation
    3. // Note: VM hardcoded the offset of this field, thus all new fields
    4. // must be added *after* it.
    5. private final ClassLoader parent;
    6. // The class loader for the system
    7. // @GuardedBy("ClassLoader.class")
    8. private static ClassLoader scl;
    9. private ClassLoader(Void unused, ClassLoader parent) {
    10. this.parent = parent;
    11. ...
    12. }
    13. protected ClassLoader(ClassLoader parent) {
    14. this(checkCreateClassLoader(), parent);
    15. }
    16. protected ClassLoader() {
    17. this(checkCreateClassLoader(), getSystemClassLoader());
    18. }
    19. public final ClassLoader getParent() {
    20. if (parent == null)
    21. return null;
    22. return parent;
    23. }
    24. public static ClassLoader getSystemClassLoader() {
    25. initSystemClassLoader();
    26. if (scl == null) {
    27. return null;
    28. }
    29. return scl;
    30. }
    31. private static synchronized void initSystemClassLoader() {
    32. if (!sclSet) {
    33. if (scl != null)
    34. throw new IllegalStateException("recursive invocation");
    35. sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
    36. if (l != null) {
    37. Throwable oops = null;
    38. //通过Launcher获取ClassLoader
    39. scl = l.getClassLoader();
    40. try {
    41. scl = AccessController.doPrivileged(
    42. new SystemClassLoaderAction(scl));
    43. } catch (PrivilegedActionException pae) {
    44. oops = pae.getCause();
    45. if (oops instanceof InvocationTargetException) {
    46. oops = oops.getCause();
    47. }
    48. }
    49. if (oops != null) {
    50. if (oops instanceof Error) {
    51. throw (Error) oops;
    52. } else {
    53. // wrap the exception
    54. throw new Error(oops);
    55. }
    56. }
    57. }
    58. sclSet = true;
    59. }
    60. }
    61. }


结合Launcher源码
AppClassLoader 父加载器 ExtClassLoader
ExtClassLoader 父加载器 null

  1. 双亲委托
    概念:
    当某个类加载器需要加载某个.class文件时,它首先把这个任务委托给他的上级类加载器,递归这个操作,如果上级的类加载器没有加载,自己才会去加载这个类。(父加载器和class文件联合决定唯一性)
    工作流程:

    1. 当前ClassLoader首先从自己已经加载的类中查询是否此类已经加载,如果已经加载则直接返回原来已经加载的类。每个类加载器都有自己的加载缓存,当一个类被加载了以后就会放入缓存,等下次加载的时候就可以直接返回了。
    2. 当前classLoader的缓存中没有找到被加载的类的时候,委托父类加载器去加载,父类加载器采用同样的策略,首先查看自己的缓存,然后委托父类的父类去加载,一直到bootstrp ClassLoader.
    3. 当所有的父类加载器都没有加载的时候,再由当前的类加载器加载,并将其放入它自己的缓存中,以便下次有加载请求的时候直接返回。
  2. 问题

    1. int.class.getClassLoader基础类获取加载器空指针报错,是由Bootstrap ClassLoader加载的
    2. ExtClassLoader 父加载器 null,但Bootstrap ClassLoader可以做ExtClassLoader的父加载器?

      • Bootstrap ClassLoader 是 由C/C++编写的,本身就是jvm的一部分,并不是java类,所以无法获取它的引用,可以解释原因
    3. aa

spring ClassPathResource

  1. 一般来说,我们项目的配置文件及静态资源都会放置在resources目录下。有时我们在项目中使用到resources目录下的文件,这时我们可以使用Spring下的Resouce接口来读取。
    深入理解JVM - 图1

  2. org.springframework.core.io.ClassPathResource 用以表达类路径下的资源。

  3. classpath,顾名思义,就是存放.class类文件的路径,是ClassLoader加载类时找到 .class文件的路径。
    一个WEB项目发布后的目录结构大致如下:
    深入理解JVM - 图2
    以Tomcat为例,看一下WEB项目类加载时候的目录,参考Tomcat Class Loader How-To 中的说明:WebappX — A class loader is created for each web application that is deployed in a single Tomcat instance. All unpacked classes and resources in the /WEB-INF/classes directory of your web application, plus classes and resources in JAR files under the /WEB-INF/lib directry of your web application, are made visible to this web application, but not to other ones.
    对于部署在Tomcat上的WEB应用来说,/WEB-INF/classes和/WEB-INF/lib目录就是我们所指的classpath。AppclassLoader加载内容

  4. ClassPathResource是org.springframework.core.io.Resource接口的实现类。可以使用ClassLoader或Class类加载资源。支持转换为java.io.File对象(在Jar文件中的资源除外)。

    1. private final String path;// 应在类路径下能够被ClassLoader所加载。
    2. @Nullable
    3. private ClassLoader classLoader;// classLoader
    4. @Nullable
    5. private Class<?> clazz; // Class类
    6. // 通过类路径创建resource
    7. public ClassPathResource(String path){...}
    8. // 通过类路径和给定的ClassLoader创建resource
    9. public ClassPathResource(String path, @Nullable ClassLoader classLoader){...}
    10. // 通过类路径和给定的Class类创建resource
    11. public ClassPathResource(String path, @Nullable Class<?> clazz){...}
    12. // 通过类路径和给定的ClassLoader或Class创建resource
    13. protected ClassPathResource(String path, @Nullable ClassLoader classLoader, @Nullable Class<?> clazz){...}
  1. ClassPathResource的使用:
    1. Resource resource = new ClassPathResource("conf/custom-beans.xml");


获取到了Resource对象也就等于获取到了该资源文件,后面可以根据方法的定义对文件进行相关操作。

  1. System.out.println(resource.getURL());
  2. System.out.println(resource.getFilename());
  3. System.out.println(resource.getFile().getPath());
  1. ResourceUtils
    获取文件org.springframework.util.ResourceUtils;
    1. public static File getFile(String resourceLocation) throws FileNotFoundException {
    2. Assert.notNull(resourceLocation, "Resource location must not be null");
    3. if (resourceLocation.startsWith(CLASSPATH_URL_PREFIX)) {
    4. String path = resourceLocation.substring(CLASSPATH_URL_PREFIX.length());
    5. String description = "class path resource [" + path + "]";
    6. ClassLoader cl = ClassUtils.getDefaultClassLoader();
    7. URL url = (cl != null ? cl.getResource(path) : ClassLoader.getSystemResource(path));
    8. if (url == null) {
    9. throw new FileNotFoundException(description +
    10. " cannot be resolved to absolute file path because it does not exist");
    11. }
    12. return getFile(url, description);
    13. }
    14. try {
    15. // try URL
    16. return getFile(new URL(resourceLocation));
    17. }
    18. catch (MalformedURLException ex) {
    19. // no URL -> treat as file path
    20. return new File(resourceLocation);
    21. }
    22. }


静态变量展示,图片示例

  1. //默认文件夹下有数据
  2. File file = null;
  3. try {
  4. file = ResourceUtils.getFile("classpath:model"); // 资源路径
  5. } catch (FileNotFoundException e) {
  6. e.printStackTrace();
  7. }
  8. String[] filelist = file.list();// 获取文件对象数组
  9. if (filelist != null && filelist.length != 0) {
  10. for (int i = 0; i < filelist.length; i++) {
  11. ModelBriefInfoVo modelBriefInfoVo = new ModelBriefInfoVo();
  12. Long id = (long) (i + 1);
  13. modelBriefInfoVo.setId(id.toString());
  14. String showPic = picPrefixPath + filelist[i];
  15. modelBriefInfoVo.setShowPic(showPic);
  16. String[] infos = filelist[i].split(",");
  17. if (infos.length != 0) {
  18. modelBriefInfoVo.setModelIdentification(infos[0]);
  19. int index = infos[1].lastIndexOf(".");
  20. modelBriefInfoVo.setModelVersion(infos[1].substring(0,index));
  21. }
  22. modelBriefInfoVos.add(modelBriefInfoVo);
  23. }
  24. }
  1. aaa

虚拟机字节码执行引擎

类加载及执行子系统的案例