一直以来觉得虚拟机是Java最难的一部分,涉及最底层的原理,学起来难度很大,而且工作中基本上用不到这些原理,所以对这部分“敬而远之”。现如今工作五年了,从Java基础到算法、数据结构、网络、数据库、设计模式都有涉猎,虚拟机部分在脑海里还是空空荡荡,连经常被谈起的垃圾回收机制都不了解,实在是惭愧。了解虚拟机通往高级Java程序员的必由之路,同时学好虚拟机也能提高我们代码的质量,知道对象是怎么创建的,放在哪里,怎么执行,怎么回收的?明白这些问题让我们在程序的世界里当一个“明白人”。
 
一、Java内存区域
学习java时都知道Java内存分为两大快堆和栈,堆存放对象实例和数组对象,栈存放基本数据类型和对象的引用,这样有点笼统,实际这里说的堆指的是图中左边的Java堆,栈指的是本地方法栈,更具体的应该是栈里面栈帧的局部变量表。
内存区域总共分两大块:左边的堆内存区域和右边的栈内存加计数器,左边的堆内存是线程共享的,只有一份;右边部分每个线程独立一份,随线程而生,随线程而灭,是线程运行的内存区域。
  1. Java堆:是程序中内存管理最大的一部分,主要存放Java中的对象的实例、数组,堆里面为了内存回收方便化分了老年代和新生代区域。
  2. 方法区:方法区也可以理解为常说的永久代,和堆类似,只是逻辑上存放的数据不同,主要存放被虚拟机加载的类信息、常量、静态变量、缓存的常量池等。既然是永久代,一般方法去的内存很少被回收,相对来说最稳定。
  3. 虚拟机栈:存放线程运行时的上下文信息,栈内部包括栈帧,每个栈帧代表一个方法调用,方法的调用体现在栈帧的入栈和出栈,每个栈帧内部都存在一个局部变量表,用于存放方法内的变量,包括基本数据类型和引用数据类型,引用数据类型时这里只存放引用,地址指向的是堆中的一块内存区域。
  4. 本地方法栈:与虚拟机栈类似,不同为这里存放的是本地方法调用的运行数据,在java中声明的native方法。
  5. 程序计数器:用于记录当前线程执行到那个位置,线程内执行流程的控制依赖程序计算器来完成。
 
二、垃圾回收与内存分配
虚拟机从加载程序到运行程序都要进行内存分配,分配的时候也伴随的内存的回收,当对象“已死”(无引用)的时候进行回收。
 
1、对象可回收的两种判断算法
如何判断对象已死呢,一般有两种方式:
  • 引用计数算法:通过建立对象引用的计算器,每增加一个引用引用数+1;引用失效时-1;引用数为0代表这个时候这个对象已经没有被用到了,可以回收。
  • 可达性分析算法:通过路径查找的方式判断对象是否可以到达,通过维护一个“GC Roots”集合代表顶层对象,在此顶层对象的“引用链”之外的对象,说明是一个不能到达的对象,可以放心回收了。
引用计数算法和可达性分析算法各有利弊,引用计数算法实现起来简单,但是需要维护一个引用计数,更新的次数太频繁,而且引用计数表也需要占一定内存;可达性分析是相对更普遍的一种实现方法,在回收时再进行一次检查,不用每次引用发生变化时发生更新,缺点是实现起来更复杂,维护“GC Roots”的算法比较复杂。
 
2、垃圾收集算法
一般虚拟机实现都采用了分代的方法,把内存划分了老年代和新生代,老年代存放的是相对稳定的对象;新生代存放的是活跃的对象,短期需要回收的。针对这两类的特点分别作出不同的策略,提高回收的效率。
  • 标记-清除算法:最基础的一个算法,第一步先标记出需要回收的对象,然后统一清除。标记清除有两个缺点:第一,执行效率不稳定,如果大部分都是需要回收的对象,标记清除效率较低;第二,清除后会造成内存的不连续,大量的碎片,如果创建一个大对象没有连续的内存又需要执行垃圾回收。
  • 标记-复制算法:标记复制算法是为了避免标记清除算法对于大部分对象需要回收执行效率率低的问题,把内存区域划分了两部分,把需要回收的一部分复制到另外一边,然后执行整块区域的回收,两块区域交替的使用。这种算法缺点是浪费了一半内存空间,所以有一个优化的方案,把内存区域拆分成三快,一块Eden两块Survivor,HotSpot的两者比例是8:1,Eden存放新分配的对象,每次回收时把存放的对象复制到其中一块空闲的Survivor,清除Eden另外一块Survivor空间,交替的使用、清除Survivor空间;这种情况下存放数据的区域有90%,只有10%的空间浪费,空间利用很好,但是需要考虑当存活的对象大于10%时,这种情况就需要借用老年代,把它分配到老年代。
  • 标记-整理算法:整理算法是在标记清除和标记复制之间折中的一种算法,使用标记清除,但是定期整理,把不连续的内存整理到一块去,解决了内存的碎片和空间上的浪费。缺点是每次整理是一个很负重的操作,会造成用户程序的暂停。
这三种算法中,标记清除和标记整理适合老年代,需要回收的对象占少部分的情况;标记复制算法适合新生代,每次绝大部分对象需要回收,只需要把小量存活的挪到另一块位置。
 
3、内存分配的几条策略
  • 大多数情况下对象在堆中的新生代Eden空间分配,当Eden没有空间时会触发一次GC。
  • 当Eden空间不够或一个大的对象(例如大的数组)创建将分配到老年代。
  • 长期存活的新生代对象会转移到老年代,在新生代的对象每熬过一次GC,年龄加1,默认15岁时将会移动到老年代。
 
三、类加载的过程
程序通过new、静态方法、静态字段引用、子父类的引用、反射调用等方式会触发类的加载,把类的字节码加载到虚拟机。加载流程:
  1. 加载:类的字节码加载到虚拟机,通过类加载器加载到虚拟机,默认通过Java的引导类加载(Bootstrap),也可以通过自定义的类加载器加载,加载的不一定必须是一个本地文件,只要是符合要求的二进制字节码即可,可以来源于网络或数据库。
  2. 验证:验证字节码的正确性,是否是一个合格的字节码文件,保证虚拟机的运行安全。
  3. 准备:分配内存和初始化零值。
  4. 解析:符号引用替换成直接引用,符号引用是字面量的形式,前面已经分配了内存,这里替换成指向的内存地址。
  5. 初始化:类加载的最后一步,执行程序代码里的初始化,包括静态代码块,构造方法,默认字段值。
 
四、Java内存模型
Java内存模型是定义了程序中变量的访问规则。
每个线程都有一个工作内存,工作内存通过读写操作和主内存交互,达到变量的共享。
 
交互操作:
  • lock和unclock: 对主内存的变量进行加锁和解锁,锁定后其他线程将不可操作。
  • read和load: read从主内存读取一个变量到工作内存,load放入读取的变量放到工作内存中。
  • store和write: store把一个工作内存的变量传递到主内存中,write把传递过来的变量写入主内存。
  • use: 把一个工作内存中的变量传递给执行引擎使用。
  • assign: 把从执行引擎接收到的赋值给工作内存的变量。
 
 
 
 

java虚拟机学习记录(内存划分、垃圾回收、类加载等机制)的更多相关文章

  1. Java虚拟机学习 - 对象内存分配与回收 ( 5 )

    对象优先在Eden上分配 大多数情况下,对象优先在新生代Eden区域中分配.当Eden内存区域没有足够的空间进行分配时,虚拟机将触发一次 Minor GC(新生代GC).Minor GC期间虚拟机将E ...

  2. java虚拟机学习-JVM内存管理:深入Java内存区域与OOM(3)

    概述 Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人却想出来. 对于从事C.C++程序开发的开发人员来说,在内存管理领域,他们即是拥有最高权力的皇帝又 ...

  3. java虚拟机学习-JVM内存管理:深入垃圾收集器与内存分配策略(4)

    Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人却想出来. 概述: 说起垃圾收集(Garbage Collection,下文简称GC),大部分人都把这项 ...

  4. java虚拟机学习-慢慢琢磨JVM(2-1)ClassLoader的工作机制

    ClassLoader的工作机制 java应用环境中不同的class分别由不同的ClassLoader负责加载. 一个jvm中默认的classloader有Bootstrap ClassLoader. ...

  5. Java 虚拟机学习记录

    参考资料 JVM高级特性与最佳实践-周志明 HotSpot 虚拟机垃圾回收调优指导 JVM 标准(Java SE 8) JSR 133 Java平台内存模型与线程修订版 命令行工具 JDK Vs JR ...

  6. JAVA虚拟机运行时内存划分--运行时数据区域

    Java虚拟机在执行java程序时会把内存划分为以下几个不同的数据区域: java虚拟机内存划分(运行时)1.线程私有的: 程序计数器(Program Counter Register):可以看作当前 ...

  7. 深入理解JAVA虚拟机之JVM性能篇---垃圾回收

    一.基本垃圾回收算法 1. 判断对象是否需要回收的方法(如何判断垃圾): 1) 引用计数(Reference Counting)  对象增加一个引用,即增加一个计数,删除一个引用则减少一个计数.垃圾回 ...

  8. 转!!Java虚拟机堆的内存分配和回收

    Java内存分配和回收,主要就是指java堆的内存分配和回收.java堆一般分为2个大的区域,一块是新生代,一块是老年代.在新生代中又划分了3块区域,一块eden区域,两块surviver区域.一般称 ...

  9. Java虚拟机面试重点-------------内存分配和回收策略

    1 对象优先分配在Eden区 对象优先在Eden进行分配,大多数情况下,对象在新生代Eden区进行分配.当Eden区没有足够的空间进行分配时,虚拟机会发起一次Minor GC. 新生代GC(Ninor ...

随机推荐

  1. 关于scanf与gets的区别

    以下内容主要来源: scanf与gets读取字符串 scanf与gets函数读取字符串的区别 前两天有个同学问我scanf与gets的区别说了半天也没说出来个所以然,就搜了一下,scanf()和get ...

  2. Python几个简单实用的模块

    今天整理了下,工作中常用的一些高阶函数,后面持续更新...... 一.collections 二.itertools 三.functools

  3. kali linux下运行.sh文件权限不够解决办法

    我要装一个生成免杀的神奇,系统提示权限不够 2 于是我想到了sudo,可还是不行 3 于是找到了方法 chmod   a+x   文件名 4 再运行一下,成功 5 有时有的方法很简答,只要你愿意找.

  4. LeetCode--二叉树2--运用递归解决树的问题

    LeetCode--二叉树2--运用递归解决树的问题 在前面的章节中,我们已经介绍了如何利用递归求解树的遍历. 递归是解决树的相关问题最有效和最常用的方法之一. 我们知道,树可以以递归的方式定义为一个 ...

  5. 深度解析互联网大厂面试难题自定义@EnableXX系列

    深度解析互联网大厂面试难题自定义@EnableXX系列   其实是一个@Import的设计技巧 创建注解@EnableXX(任何名称注解都行,只是这个名字好一些) XXConfiguration类不能 ...

  6. JS 获取一段时间内的工作时长小时数

    本来想是想找轮子的,但是并没有找到能用的,多数都是问题很大,所以就自己写了一个 需求说明 支持自选时间段,即开始时间与结束时间根据用户的上班及下班时间判定返回小时数 技术栈 moment.js 思考过 ...

  7. 零基础JavaScript编码(二)

    任务目的 在上一任务基础上继续JavaScript的体验 学习JavaScript中的if判断语法,for循环语法 学习JavaScript中的数组对象 学习如何读取.处理数据,并动态创建.修改DOM ...

  8. JZOJ 1774. 合并果子 (Standard IO)

    1774. 合并果子 (Standard IO) Time Limits: 1000 ms Memory Limits: 65536 KB Description 在一个果园里,多多已经将所有的果子打 ...

  9. 谈谈集合.Queue

    之前说到,Java中集合的主要作用就是装盛其他数据和实现常见的数据结构.所以当我们要用到"栈"."队列"."链表"和"数组&quo ...

  10. .netCore下的jwt的梳理-->借鉴于“老张的哲学”

    之前在公司的项目中有用到jwt进行token验证,但是公司里用的框架已经集成好了jwt,所以对jwt的的了解不够清晰,感觉还是隔着一层.在看了“老张的哲学”的jwt部分后对jwt的认识才更加深刻了一些 ...