精华内容
下载资源
问答
  • 2021-03-01 19:52:50

    简介

    在c语言中,我们需要手动分配和释放对象的内存,但是在java中,所有的内存管理都交给了java虚拟机,程序员不需要在手动进程内存的分配和释放,大大的减少了程序编写的难度。

    同样的,在javascript中,内存管理也是自动进行的,虽然有自动的内存管理措施,但是这并不意味着程序员就不需要关心内存管理了。

    本文将会进行详细的介绍javascript中的内存管理策略。

    内存生命周期

    对于任何程序来说,内存的生命周期通常都是一样的。

    可以分为三步:

    1. 在可用空间分配内存
    2. 使用该内存空间
    3. 在使用完毕之后,释放该内存空间

    所有的程序都需要手动执行第二步,对于javascript来说,第1,3两步是隐式实现的。

    我们看下javascript中分配内存空间的例子。

    通过初始化分配内存空间:

    var n = 123; // 为数字分配内存
    var s = 'azerty'; // 为String分配内存
    
    var o = {
      a: 1,
      b: null
    }; // 为对象分配内存
    
    // 为数组分配内存
    var a = [1, null, 'abra']; 
    
    function f(a) {
      return a + 2;
    } // 为函数分配内存
    

    通过函数调用分配内存空间:

    var d = new Date(); // 通过new分配date对象
    
    var e = document.createElement('div'); // 分配一个DOM对象
    
    var s = 'azerty';
    var s2 = s.substr(0, 3); // 因为js中字符串是不可变的,所以substr的操作将会创建新的字符串
    
    var a = ['ouais ouais', 'nan nan'];
    var a2 = ['generation', 'nan nan'];
    var a3 = a.concat(a2); 
    // 同样的,concat操作也会创建新的字符串
    

    释放空间最难的部分就是需要判断空间什么时候不再被使用。在javascript中这个操作是由GC垃圾回收器来执行的。

    垃圾回收器的作用就是在对象不再被使用的时候进行回收。

    JS中的垃圾回收器

    判断一个对象是否可以被回收的一个非常重要的标准就是引用。

    如果一个对象被另外一个对象所引用,那么这个对象肯定是不能够被回收的。

    引用计数垃圾回收算法

    引用计数垃圾回收算法是一种比较简单和简洁的垃圾回收算法。他把对象是否能够被回收转换成了对象是否仍然被其他对象所引用。

    如果对象没有被引用,那么这个对象就是可以被垃圾回收的。

    我们举一个引用计数的例子:

    var x = { 
      a: {
        b: 2
      }
    }; 
    //我们创建了两个对象,a对象和a外面用大括号创建的对象。
    // 我们将大括号创建的对象引用赋值给了x变量,所以x拥有大括号创建对象的引用,该对象不能够被回收。
    // 同时,因为a对象是创建在大括号对象内部的,所以大括号对象默认拥有a对象的引用
    // 因为两个对象都有引用,所以都不能够被垃圾回收
    
    var y = x;  //我们将x赋值给y,大括号对象现在拥有两个引用
    
    x = 1;   // 我们将1赋值给x,这样只有y引用了大括号的对象
    
    var z = y.a;  // 将y中的a对象引用赋值给z,a对象拥有两个引用
    
    y = 'flydean';  // 重新赋值给y,大括号对象的引用数为0,大括号对象可以被回收了,但是因为其内部的a对象还有一个z在被引用
                    // 所以暂时不能被回收
    
    z = null;       // z引用也被重新赋值,a对象的引用数为0,两个对象都可以被回收了
    

    引用计数的一个缺点就是可能会出现循环引用的情况。

    考虑下面的一个例子:

    function f() {
      var x = {};
      var y = {};
      x.a = y;        // x references y
      y.a = x;        // y references x
    
      return 'flydean';
    }
    
    f();
    

    在上面的例子中,x中的a属性引用了y。而y中的a属性又引用了x。

    从而导致循环引用的情况,最终导致内存泄露。

    在实际的应用中,IE6 和IE7 对DOM对象使用的就是引用计数的垃圾回收算法,所以可能会出现内存泄露的情况。

    var div;
    window.onload = function() {
      div = document.getElementById('myDivElement');
      div.circularReference = div;
      div.lotsOfData = new Array(10000).join('*');
    };
    

    上面的例子中,DOM中的myDivElement元素使用circularReference引用了他本身,如果在引用计数的情况下,myDivElement是不会被回收的。

    当myDivElement中包含了大量的数据的时候,即使myDivElement从DOM tree中删除了,myDivElement也不会被垃圾回收,从而导致内存泄露。

    Mark-and-sweep回收算法

    讲到这里,大家是不是觉得JS的垃圾回收算法和java中的很类似,java中也有引用计数和mark-and-sweep清除算法。

    这种回收算法的判断标准是对象不可达。

    在javascript中,通过扫描root对象(JS中的root对象那些全局对象),然后找到这些root对象的引用对象,然后再找到这些被引用对象的引用对象,一层一层的往后查找。

    最后垃圾回收器会找到所有的可达的对象和不可达的对象。

    使用不可达来标记不再被使用的对象可以有效的解决引用计数法中出现的循环引用的问题。

    事实上,现在基本上所有的现代浏览器都支持Mark-and-sweep回收算法。

    调试内存问题

    如果发送了内存泄露,我们该怎么调试和发现这个问题呢?

    在nodejs中我们可以添加–inspect,然后借助Chrome Debugger来完成这个工作:

    node --expose-gc --inspect index.js
    

    上面的代码将会开启nodejs的调试功能。

    我们看下输出结果:

    Debugger listening on ws://127.0.0.1:9229/88c23ae3-9081-41cd-98b0-d0f7ebceab5a
    For help, see: https://nodejs.org/en/docs/inspector
    

    结果告诉了我们两件事情,第一件事情就是debugger监听的端口。默认情况下将会开启127.0.0.1的9229端口。并且分配了一个唯一的UUID以供区分。

    第二件事情就是告诉我们nodejs使用的调试器是Inspector。

    使用Chrome devTools进行调试的前提是我们已经开启了 --inspect模式。

    在chrome中输入chrome://inspect:

    我们可看到chrome inspect的界面,如果你本地已经有开启inspect的nodejs程序的话,在Remote Target中就可以直接看到。

    选中你要调试的target,点击inspect,即可开启Chrome devTools调试工具:

    你可以对程序进行profile,也可以进行调试。

    闭包Closures中的内存泄露

    所谓闭包就是指函数中的函数,内部函数可以访问外部函数的参数或者变量,从而导致外部函数内部变量的引用。

    我们看一个简单闭包的例子:

     function parentFunction(paramA)
     {
     var a = paramA;
     function childFunction()
     {
     return a + 2;
     }
     return childFunction();
     }
    

    上面的例子中,childFunction引用了parentFunction的变量a。只要childFunction还在被使用,a就无法被释放,从而导致parentFunction无法被垃圾回收。事实上Closure默认就包含了对父function的引用。

    我们看下面的例子:

     <html>
     <body>
     <script type="text/javascript">
     document.write("Program to illustrate memory leak via closure");
     window.onload=function outerFunction(){
     var obj = document.getElementById("element");
     obj.onclick=function innerFunction(){
     alert("Hi! I will leak");
     };
     obj.bigString=new Array(1000).join(new Array(2000).join("XXXXX"));
     // This is used to make the leak significant
     };
     </script>
     <button id="element">Click Me</button>
     </body>
     </html>
    

    上面的例子中,obj引用了 DOM 对象element,而element的onclick是outerFunction的内部函数,从而导致了对外部函数的引用,从而引用了obj。

    这样最终导致循环引用,造成内存泄露。

    怎么解决这个问题呢?

    一个简单的办法就是在使用完obj之后,将其赋值为null,从而中断循环引用的关系:

     <html>
     <body>
     <script type="text/javascript">
     document.write("Avoiding memory leak via closure by breaking the circular
     reference");
     window.onload=function outerFunction(){
     var obj = document.getElementById("element");
     obj.onclick=function innerFunction()
     {
     alert("Hi! I have avoided the leak");
     // Some logic here
     };
     obj.bigString=new Array(1000).join(new Array(2000).join("XXXXX"));
     obj = null; //This breaks the circular reference
     };
     </script>
     <button id="element">"Click Here"</button>
     </body>
     </html>
    

    还有一种很简洁的办法就是不要使用闭包,将其分成两个独立的函数:

     <html>
     <head>
     <script type="text/javascript">
     document.write("Avoid leaks by avoiding closures!");
     window.onload=function()
     {
     var obj = document.getElementById("element");
     obj.onclick = doesNotLeak;
     }
     function doesNotLeak()
     {
     //Your Logic here
     alert("Hi! I have avoided the leak");
     }
     </script>
     </head>
     <body>
     <button id="element">"Click Here"</button>
     </body>
     </html>
    

    本文作者:flydean程序那些事

    本文链接:http://www.flydean.com/js-memory-management/

    本文来源:flydean的博客

    欢迎关注我的公众号:「程序那些事」最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!

    更多相关内容
  • 图书管理系统实验报告-面向对象的分析与设计

    万次阅读 多人点赞 2020-07-05 11:47:17
    随着我国信息化的飞速发展,传统的图书管理模式已经满足不了对于大量图书管理的需求了,传统图书馆当前图书管理中存在许多问题: ① 手工检索效率低 图书馆内图书数量非常的庞大,且种类繁多。虽然图书馆已经将...

    背景、意义;需求分析;用例分析、类图、顺序图、通信图、活动图


    1.研究背景及意义
    图书馆是一所学校或是一座城市的一个文化标志,可以为学生以及社会上的各界人士提供丰富的图书文化资源,是一个学校管理机制中一个重要的环节。随着我国信息化的飞速发展,传统的图书管理模式已经满足不了对于大量图书管理的需求了,传统图书馆在当前图书管理中存在许多问题:
    ① 手工检索效率低
    图书馆内图书数量非常的庞大,且种类繁多。虽然图书馆已经将图书进行了分类,图书的检索已经比较的简单,但因为手工检索存在着效率低下的问题,所以图书检索问题普遍存在于各个图书馆中,并随着图书数量的增多变得愈加严重。
    ② 手工借书还书失误高
    图书馆的工作量大、效率低图书馆的日常工作就是借书与还书,这也是图书馆的主要工作。在借书和还书的高峰期,图书馆的图书归还登记以及图书借出登记工作量将会大量的增加,直接导致工作人员的工作量大幅度增加,而大量的信息使用人工操作难免会出现差错,如果没能将一些特殊情况及时的详细记录的话,就会给图书馆带来损失。
    随着教育改革的不断推进,当前人们对于教育的重视程度在不断地攀升,这导致各个院校的发展进程也在不断的加快,图书馆的图书管理工作也随之更加的繁重。但是,随着我国信息技术的发展,计算机的应用范围不断扩大,所以在将计算机技术应用在图书管理已经逐渐成为图书管理工作的一项必然的发展趋势。
    因此建立一款依托于互联网技术,能够让读者更快捷、更便利地对图书进行搜索、借阅和归还,并且能够根据读者的不同需求提供对应服务,实现图书信息资源的共享的图书管理系统是非常有必要的。
    与传统的手工管理模式相比较而言,图书馆内图书实现自动化、电子化管理,可以为使用人员提供更加快速准确、精细的图书管理信息,如,检索方便快捷、检索结果准确、存储的数据量大,并且成本低,可以很好地节省资源、并且还具有人机交互界面等众多的功能。并且使图书馆的管理变得不再繁琐;读者借书阅读也变得方便,快捷;大大减少了图书的丢失和人员管理带来的不必要麻烦;减少人力与管理费用;提高信息准确度;改进管理和服务;建立高效的信息传输和服务平台,提高信息处理速度和利用率。
    从根本上看,图书馆里系统的最终目的就是为了减少成本的投入,同时大大地提高了工作效率,还要兼具系统在运行过程中可靠性很高、安全性稳定、存储容量大等特点。此外还要保证系统能够简单上手、灵活操作、实用性强。此外该系统需要必备两方面的特点:第一,图书检索功能需要方便快捷,可以在大量的图书中快速准确的检索出图书的相关信息;第二,由于图书以及用户借阅资料非常的多,因此管理者需要及时、准确、有效的查询图书信息,并及时的修改图书信息。
    2.系统的需求分析
    (1)开发该系统的可行性
    图书管理信息系统的搜索程序大大方便了用户的需求,从现在的情况来看,实现该系统的可行性非常之高, 可以方便快捷有效地完成借阅、查询等的各项操作,录入数据合法性校验程序高,查询速度快。具有较高的安全性,系统对不同的用户提供不同的功能模块,操作权限有不同级别限制。一般的读者用户只能查看图书信息和借阅情况。另外系统还具有一定的保护机制,防止系统被恶意攻击,信息被恶意修改和窃取。有完善的备份机制,如果系统被破坏应该能快速恢复。
    (2)系统的功能性需求分析
    图书管理系统主要用户:管理员、读者、
    每类用户的主要功能:
    1)读者:
    登录、注册读者信息
    借阅、归还:读者可进行图书的借阅与归还的操作,但不能对信息进行更改。
    查询:读者可查看个人信息;
    可检索各种图书信息及图书状态、查看借还图书记录,自己已借阅的书籍和期限。

    2)管理员:管理员登录管理员账号后,可以执行系统所有功能
    管理图书信息:
    可以浏览所有图书信息,检索特定图书的信息;
    可以查看图书状态:借阅、归还、预约
    可以对图书信息进行维护:添加图书、删除图书、修改图书信息。
    具体信息包括:图书编号、图书名称、作者、出版社、出版日期、印刷次数、单价、图书类别等。

    管理读者信息:
    可以浏览读者信息,检索特定读者信息;
    可以对读者信息进行维护:增加、删除、修改;
    可以查看读者借阅、归还、预约等信息;
    具体信息:读者类型、书证编号、读者姓名、身份证号、出生日期、性别、电话、院系、注册日期等。

    管理图书借还:借书、还书、预约、续借、催还
    图书借阅功能:输入读者编号,输入要借阅的图书的信息,记录系统当前时间即借阅时间;
    图书归还功能:输入读者编号,选择其名下已借阅的图书,判断否超过了规定的期限;超过期限,交罚款,没超过期限,归还图书
    具体信息包括:书证号、借阅人姓名、图书编号、图书名称、借阅日期、归还日期、状态等

    系统维护:系统管理人员维护系统日常工作
    3.用例分析
    在这里插入图片描述
    表1 图书借阅用例描述

    用例名称图书借阅
    参与者借阅者、管理人员
    主要成功情节借阅者查询图书信息,确定要借阅的图书;
    向管理人员提交借阅请求;
    管理人员收到请求;
    验证借阅者信息和图书信息为可借阅,并且在库、无预约;
    管理人员添加借阅记录,修改图书状态;
    向借阅者表示借阅成功
    扩展验证借阅者信息失败—借阅失败;
    验证图书信息不可借阅—借阅失败;
    验证图书信息为借出或者有预约—借阅失败,提醒借阅者可预约

    表2 图书归还用例描述

    用例名称归还图书
    参与者借阅者、管理人员
    主要成功情节借阅者提交归还图书申请;
    管理人员审核归还申请;
    查看是否逾期,逾期缴纳罚金;
    修改借还记录、修改图书状态信息;
    退还押金
    扩展图书丢失—归还失败,需赔偿

    表3 图书预约用例描述

    用例名称图书预约
    参与者借阅者、管理人员
    主要成功情节借阅者查看图书信息和预约记录,确定预约图书;
    提交预约申请;
    管理人员审核预约申请;
    验证读者信息和图书信息;
    添加预约记录,预约成功
    扩展验证图书信息为不能借阅—预约失败;

    4. 数据表分析与设计
    表1 借还记录数据表设计

    字段名类型备注
    图书编号Varchar(10)主码
    读者账号Varchar(10)主码
    借阅日期Data主码
    归还日期Datanot null
    续借日期Data
    续借时长Varchar(10)
    是否逾期Char(2)“是”或“否”
    逾期时间Varchar(10)
    是否缴纳罚款Char(2)“是”或“否”
    缴纳罚款Varchar(10)
    押金Varchar(10)not null

    表2 图书预约数据表设计

    字段名类型备注
    图书编号Varchar(10)主码
    读者账号Varchar(10)主码
    预约时间Data主码
    等待时长Varchar(10)

    表3 读者信息数据表设计

    字段名类型备注
    账号Varchar(10)主码
    密码Varchar(10)not null
    姓名Varchar(10)not null
    性别Char(2)“男”或“女”
    身份证号Char(18)not null
    身份Char(3)“学生”“教师”“其他”
    联系方式Char(11)not null

    表4 图书信息数据表设计

    字段名类型备注
    图书编号Varchar(10)主码
    书名Varchar(10)not null
    作者Varchar(5)not null
    出版社Varchar(10)not null
    库存intnot null
    出版时间Datanot null
    单价Varchar(10)not null
    能否被借阅Char(3)“能”或“不能”
    当前状态Char(3)“在库”或“借出”

    表5 管理人员数据表设计

    字段名类型备注
    IDVarchar(10)主码
    姓名Varchar(10)not null
    身份证号Char(18)not null
    联系方式Char(11)not null

    表6 管理图书数据表设计

    字段名类型备注
    管理人员IDVarchar(10)主码
    图书编号Varchar(10)主码
    操作日期Data主码
    操作类型Varchar(10)主码“增”、“删”、“改”、“查”

    表7 管理读者数据表设计

    字段名类型备注
    管理人员IDVarchar(10)主码
    读者账号Varchar(10)主码
    操作日期Data主码
    操作类型Varchar(10)主码“增”、“删”、“改”、“查”

    ①图书借还类图
    在这里插入图片描述
    ②图书预约类图
    在这里插入图片描述
    将概念数据模型转化为对象关系模型:
    注:被预约的图书不能被续借、固定每次借阅标椎时长15天
    借还记录(读者账号、图书编号、借阅日期、归还日期、续借日期、续借时长、是否逾期、逾期时间、是否缴纳罚金、缴纳金额、押金)
    图书预约(图书编号、读者账号、预约时间、等待时长)
    图书信息(编号、书名、作者、出版社、库存、出版时间、单价、是否能被借阅、当前状态)
    读者信息(账号、密码、姓名、身份证号、性别、身份、联系方式)
    管理人员(ID、姓名、身份证号、联系方式)
    管理读者(管理人员ID、读者账号、操作日期、操作类型
    管理图书(管理人员ID、图书编号、操作日期、操作类型

    5.系统主要交互流程设计
    1、顺序图
    控制类:borrowCotrol
    实体类:四个:图书信息、读者信息、借还记录、图书预约
    边界类:两个参与者两个边界类:readerForm、librarianForm
    ①图书借阅
    在这里插入图片描述
    ②图书归还
    在这里插入图片描述
    ③图书预约
    在这里插入图片描述
    2、通信图
    ①图书借阅
    在这里插入图片描述
    ②图书归还
    在这里插入图片描述
    ③图书预约
    在这里插入图片描述
    3、活动图
    ①图书借阅
    在这里插入图片描述
    ②图书归还
    在这里插入图片描述
    ③图书预约
    在这里插入图片描述
    6.系统实现
    管理人员的界面设计:
    1、管理人员登录页面
    在这里插入图片描述
    2、查看、修改用户信息
    在这里插入图片描述
    在这里插入图片描述
    3、查看、添加、修改图书信息
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    4、图书借阅
    在这里插入图片描述
    5、图书归还
    在这里插入图片描述
    在这里插入图片描述
    7. 总结
    该系统主要以满足图书馆的图书管理需求为导向,将解决学校图书馆的需求为主要目的,基于此建立一个能够满足图书馆工作人员和借阅者要求,同时还具备读者信息管理、、图书检索、图书信息管理、图书借阅管理、等众多功能的图书馆管理系统。结合读者与管理者的实际需求,运用计算机技术在图书管理中,可以使图书管理工作更加的高效。
    通过以上基于UML的图书管理信息系统的分析与设计,能够了解 UML对图书管理信息系统的福利,可以看出采用UML来进行建模,系统的相关参与者的所需功能以及各自所要操作的流程多能很好地在UML的相关图中得到很好的表述,它的设计优劣直接影响到用户对工程产品的满意度。尽管每个模型都有不同的设计方案和表达形式,但是最好的模型总是能够最大可能的切合实际,节约成本。而且越庞大的项目,建模的重要性越大。这样对于系统的开发人员来说,就能很好地做到对系统需求的把握以及在接下来的软件设计工作中避免一些不必要的错误产生,增加各部门之间的交互能力,加快项目开发进度,使得项目的开发更加科学、有效。

    参考文献:

    1. 陈平.图书馆图书管理系统的结构设计及其功能实现. 2019-09-16
    2. 庄礼金.基于JSP的图书在线借阅系统的分析与设计. 2019-11-01
    3. 尹斌.图书管理系统/基于UML的图书管理信息系统分析与设计.2019-12-05
    4. 张日如.基于UML的图书管理系统的设计. 2019-12-29
    5. 张景峰.基于UML的用例图模型创建. 2019-03-10
    展开全文
  • 今天闲来无事,有空闲的时间,所以想坐下来聊一聊Java的GC以及Java对象在内存的分配。 和标题一样,本篇绝对是用直接通俗易懂的大白话来聊 文章基本不会有听起来很高大上专业术语,也不会有太多概念性的...

    铁子们,快扫码关注啦!或 wx搜索:“聊5毛钱的java”,关注可领取博主的Java学习视频+资料,保证都是干货

    前言:

    今天闲来无事,有空闲的时间,所以想坐下来聊一聊Java的GC以及Java对象在内存中的分配。

    和标题一样,本篇绝对是用最直接最通俗易懂的大白话来聊

    文章中基本不会有听起来很高大上专业术语,也不会有太多概念性的描述,本着一看就懂的原则来写。

    因为之前看很多文章都是概念性的东西太多,让人越看越迷糊,越看越觉得有距离感,不接地气,看完之后跟没看一样。最不希望的就是这个,所以我写的东西都是尽量用最通俗易懂、最接地气的大白话来描述,我写所有博客的愿景是让看文章的人看完后觉得有所收获,希望对看到文章的老铁们有所帮助。当然,写的如有不对或不妥之处,还请海涵,欢迎下方留言指正,共同进步

    一、Java的垃圾回收GC

    先来聊GC,是因为这个过程中会涉及到JVM对堆内存的分代管理,以及eden区等等这些概念,有了这些概念后,再去聊Java对象在内存中的分配会好理解很多,所以先来聊一聊GC。

    1、确定垃圾对象

    GC(Garbage Collection)顾名思义就是垃圾回收的意思

    那既然要回收垃圾对象,首先得知道哪些对象是垃圾吧,怎么判定哪些是垃圾对象呢?

    有两个办法:

    (1)第一种是引用标记法,简单来说就是这个对象被一个地方引用了,这个计数的标记就加一,有地方释放了这个对象的引用,这个标记就减一。这个算法认为当一个对象的引用数量为零,那就意味着没有地方引用这个对象了,那此时这个对象就是垃圾对象。但是引用标记法有个致命的问题,那就是解决不了循环引用的问题,所以Java并没有采用引用标记法。具体什么是循环引用,并不是这里的重点,可以自行查一下

    (2)第二种是可达性分析法,简单说,就是从一个根对象出发,到某个对象如果有可达的路径,就认为它不是垃圾对象,否则认为是垃圾对象。根对象也被称为GC Root,那有个关键的问题:哪些对象可以被视为根对象呢?

    在《深入理解JVM虚拟机》中有这样的描述,以下三种可以作为根对象

    1.虚拟机栈:栈帧中的局部变量表引用的对象

    2.方法区中的静态变量和常量引用的对象

    3.native方法引用的对象

    确定了根对象后,通过这个算法就可以定位到哪些对象是垃圾对象了

    2、回收垃圾对象

    通过上边的方法知道了哪些对象是垃圾对象后,就可以回收垃圾了,回收垃圾同样也有几种不同的方法

    常见的有三种

    (1)标记-清除法:简单的说就是把那些已经标记为垃圾的对象进行清除,这种算法有一个缺点就是清理完成后会产生大量内存碎片(因为这些垃圾对象在内存中很多都是分散分布的,不可能总是连续的在一起的,所以清理完会导致内存不连续)

    (2)标记-压缩法:简单来说就是把那些已经标记为垃圾的对象进行清除后,再把分散开的内存碎片进行整理

    (3)复制法:简单说就是在要回收的内存区域之外,再另准备一块空白的内存,把不是垃圾的对象直接复制到这个空白内存区域里,然后就可以简单粗暴的把要回收的那个内存区域全部清空。

    不同的算法有不同的特点,不同的算法适用于不同的场景:

    少量对象存活,适合使用复制算法

    大量对象存活,适合使用标记清理或者标记压缩法

    所以JVM把堆内存进行了分代来管理,分为年轻代、老年代和永久代。不同的代,适用不同的回收算法

    由于年轻代的对象大部分是朝生夕死,只有少部分对象存活,所以很适合用复制算法
    但是复制算法有一个很大的缺点,就是需要两块一样大小的内存来进行轮换,这就导致了会浪费一半的空间。但是经过研究统计发现,年轻代每次只有大概10%的对象存活,所以就又把年轻代分为了eden区、servivor from区、servivor to区,他们的比例是8:1:1,也就是eden区占80%,servivor from和servivor to 它俩作为轮换区域,分别占10%(也就是用尽量少的内存资源来实现复制算法)。这个8:1:1面试的时候可能会被问到,需要注意一下

    但是每次大概只有10%对象存活,这是个统计的概率事件,实际中并不一定每次都只有10%或者少于10%的对象存活
    所以那要万一有时候存活的对象大于10%呢,那准备的servivor区的空间不就不够复制算法运行了吗?

    这怎么办?所以这时候需要担保,具体的说就是用老年代来作为担保,每次年轻代GC(minor GC)的时候都会去检查老年代最大连续可用空间是否大于年轻代中所有对象总和的大小,如果大于则认为这个担保是没有风险的,就进行正常的minor GC;

    但是如果发现小于,那就会继续判断你是否设置了允许担保失败,如果你设置的是不允许担保失败,那这次minor GC就要改为一次Full GC;如果你设置的是允许担保失败,那它会继续去判断老年代最大连续可用空间是否大于历次晋升到老年代的对象的平均大小,如果大于,就尝试着去minor GC一次,尽管这次GC是有风险的,如果尝试后失败了,那就Full GC;如果小于的话,那这次minor GC也要改为一次Full GC。

    可以发现如果开启了允许担保失败的话,有可能会出现绕了一大圈最后发现还是失败了的情况,最后还是得去Full GC

    虽然会出现这种情况,但是还是建议设置开启这个允许担保失败,因为开启了允许担保失败后,会在一定程度上减少Full GC的次数,要知道一次Full GC的时间是minor GC的几倍甚至几十倍,所以要尽量避免Full GC

    画一下上述空间担保分配的简要流程图

    以上只是对垃圾回收的大体总结,并不涉及具体的细节

    有了以上大体了解后,建议拜读《深入理解JVM虚拟机》一书,写的确实很好的一本书,相信读后定会收获颇丰。

    讲垃圾回收一定绕不开那七种垃圾回收器:Serial(年轻代)、ParNew(年轻代)、Paralle Scavenge(年轻代)、Serial Old(年老代)、Parallel Old(年老代)、CMS(Concurrent Mark Sweep年老代)、G1

    不同垃圾回收器使用的算法不同(但都是基于标记清除或标记整理或复制算法这三种的),用的场景也不同

    关于这七种垃圾回收器,本篇并不展开详述,只是抛砖引玉,如有需要,也可自行参考《深入理解JVM虚拟机》

    二、Java对象在内存中的那些事

    下面来聊一聊Java对象在内存中的那些事。

    我总结了大概这几方面:对象在内存中的创建、对象在内存中的布局、对象在内存中的访问定位

    下面分别展开详述

    1、对象在内存中的创建

    对象在内存中创建到底是怎么样一个过程呢?大致有以下几个步骤:

    (1)虚拟机遇到一条new指令时,首先检查这个对应的类能否在常量池中定位到一个类的符号引用

    (2)判断这个类是否已被加载、解析和初始化

         如果没有,则必须进行相应的加载过程

    (3)为新生对象在Java堆中分配内存空间

         分配内存有两种方式:指针碰撞和空闲列表

         指针碰撞是指:假设Java堆中内存绝对规整,所有已使用的内存都放在一边,空闲的内存都放在另一边,中间放着一个指针作为分界点,那这时候分配内存就仅仅是把这个指针向空闲的那边挪动一段(挪动的大小就是需要分配的对象的大小),这种分配方式就称为“指针碰撞”。

         空闲列表是指:如果Java堆内存并不是规整的,已经使用的内存和空闲的内存相互交错,那肯定就没办法使用指针碰撞的方式进行内存分配了,这时候虚拟机就必须维护一个列表来记录哪些内存块儿是可用的,然后在需要进行内存分配的时候就从列表中找到一块儿足够大的内存划分给对象,并且更新列表上的记录,这种分配方式称之为“空闲列表”。

         到底用哪种方式进行分配是由堆内存是否规整决定的,而堆内存是否规整又是由你具体使用的哪种垃圾回收器决定的,

    如果你使用的是“标记-清除”这种类型的垃圾回收器,那么会导致堆内存不规则产生内存碎片,适合使用空闲列表的方式;

    如果你使用的是“标记-整理(压缩)”这种类型的垃圾回收器,适合使用指针碰撞的方式。

    除了讨论使用哪种方式进行内存分配外,还有一个问题需要考虑:对象创建在虚拟机中是非常频繁的行为,即使是仅仅修改一个指针指向的位置(指针碰撞的方式)或者找到空闲空间给对象分配,并更新列表(空闲列表的方式),在并发情况下也并不是安全的,因为上述的操作并不能保证其原子性,很可能出现不同的对象申请到同一块内存的情况

    解决这个问题有几种方案:基于硬件指令的CAS方式来保证操作的原子性或者使用TLAB的方式,再或者可以使用栈上分配

    栈上分配和TLAB的具体内容本篇不展开讨论,具体内容可参考我之前写的另一篇:用大白话来聊一聊Java对象的栈上分配和TLAB

    (4)内存分配完之后,虚拟机需要将分配到的内存空间都初始化为零值

             比如int 型的零值是0,布尔的零值是false,引用数据类型零值是null等等

    (5)设置对象头相关数据:GC分代年龄、对象的哈希码 hashCode、元数据信息等等

    (6)执行<init>方法

            执行<init>方法包括但不仅限于:构造代码块儿、构造函数,具体步骤如下

       1)父类静态变量,父类静态代码块执行初始化
       2)子类静态变量,子类静态代码块执行初始化 
       3)父类全局变量,父类构造代码块执行初始化
       4)父类构造函数执行 
       5)子类全局变量,子类构造代码块执行初始化 
       6)子类构造函数执行 

    上述就是针对对象创建底层步骤的一些总结

    对象在内存里创建出来后,那创建完的对象在内存中是什么样的?对象里都包括哪些内容?

    2、对象在内存中的布局

    在虚拟机中,对象在内存中的存储布局可分为三块:对象头、实例数据和对齐填充

    (1)对象头:对象头用于存储对象的元数据信息

    对象头又可以分为两块内容:第一部分用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,这部分数据的长度在32位和64位的虚拟机中分别位32bit和64bit,官方称它为 Mark Word。对象头的另一部分是类型指针,指向它的类元数据的指针,用于判断对象属于哪个类的实例,另外,如果对像是一个数组,那在对象头中还必须有一块用于记录数组长度的数据,因为虚拟机可以通过普通Java对象的元数据信息确定Java对象的大小,但是从数组的元数据中却无法确定数组的大小。

    !!!注意:对象头这一块很重要,它是实现synchronized锁的基础,也是后续偏向锁,轻量级锁,自旋锁等锁优化,锁升级的基础,关于锁的介绍,可以参考我写的另一篇文章:Synchronized、偏向锁、自旋锁、轻量级锁以及锁的升级过程

    (2)实例数据

    实例数据部分是对象真正存储的有效信息,也是在程序代码中所定义各种类型的字段内容。无论是从父类继承下来的,还是在子类中定义的,都需要记录下来。父类定义的变量会出现在子类定义的变量的前面。各字段的分配策略为longs/doubles、ints、shorts/chars、bytes/boolean、oops(ordinary object pointers),相同宽度的字段总是被分配到一起,便于之后取数据。

    (3)对齐填充

    对齐填充并不是必然存在的,也没有特别的含义,它仅仅起着占位符的作用。为什么需要有对齐填充呢?由于hotspot VM的自动内存管理系统要求对象起始地址必须是8字节的整数倍,换句话,就是对象的大小必须是8字节的整数倍。而对象头正好是8字节的倍数。因此,当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。

    再给个图帮助理解记忆

    以上就是对象在内存中布局的一些总结

    对象创建好了,也知道创建的对象在内存中存储的布局结构了

    咱们创建对象肯定是为了访问它,使用它,那怎么访问它呢?

    3、对象在内存中的访问定位

    建立对象是为了使用对象,我们的Java程序需要通过栈上的reference数据来操作堆上的具体对象。由于reference类型在Java虚拟机规范中只规定了一个指向对象的引用,并没有定义这个引用应该通过何种方式去定位访问堆中的对象的具体位置,所以对象的访问方式取决于具体的虚拟机实现而定。目前主流的访问方式有使用句柄和直接指针两种。

    (1)句柄的访问方式

    如图所示,如果使用句柄访问的话,那么Java堆中将会划分出一块内存来作为句柄池,句柄池中放的是一个一个的句柄,句柄中存的是对象实例数据与对象类型数据的指针引用。栈中局部变量里reference中存储的是对象句柄的地址,而句柄中包含了对象实例数据与类型数据的具体地址信息,相当于二级指针

    (2)直接指针的访问方式

    如图所示,直接指针访问对象,栈中局部变量里reference中存储的就是对象地址,相当于一级指针

    (3)对比

    这两种对象访问方式各有利弊,使用句柄访问的最大好处就是在移动对象时(如垃圾回收的标记整理算法在回收完垃圾对象后需要把剩下存活的对象进行整理移动,以减少内存碎片),reference中存储的地址是稳定的地址,不需要修改,仅需要修改对象句柄的地址;但是如果使用直接指针方式的话,在对象被移动的时候需要修改reference中存储的地址。从效率方面比较的话,直接指针的效率要高于句柄,因为直接指针的方式只进行了一次指针定位,节省了时间开销,HotSpot采用的直接指针的实现方式。

    上述就是对Java对象的访问定位的理解与总结

    咱们说完了对象在内存中分配的以及访问的具体细节后,下面从宏观上来说一下对象分配的几个特点

    4、对象分配的几个特点

    对象的分配有以下几个特点

    (1)对象优先分配在eden区

    (2)大对象直接进去老年代,这个阈值可以自定义

    (3)年长的对象进去老年代,默认的年长值是15

    (4)动态对象年龄判断

    以上几点的具体内容也可参考《深入理解JVM虚拟机》一书

    OK,今天就先聊到这里

    铁子们,如果觉得文章对你有所帮助,可以点关注,点赞

    也可以关注下公众号:扫码或 wx搜索:“聊5毛钱的java”,欢迎一起学习交流,关注公众号可领取博主的Java学习视频+资料,保证都是干货

    3Q~

    纯手敲原创不易,如果觉得对你有帮助,可以打赏支持一下,哈哈,感谢~

               

    展开全文
  • 本文通过对象的创建步骤的检查加载->分配内存->内存空间初始化->设置->对象初始化,对象的内存布局,什么是垃圾的两种算法以及四种引用,讲述JVM中对象及引用。

    本文通过对象的创建步骤中的检查加载->分配内存->内存空间初始化->设置->对象初始化,对象的内存布局,什么是垃圾的两种算法以及四种引用,讲述JVM中对象及引用,本篇篇幅较长,适合点赞+收藏。有什么错误希望大家直接指出~

    对象的创建

    当JVM加载后遇到一条new指令首先检查是否被类加载器加载,如果没有,那必须先执行相应的类加载过程。类加载就是把 class 加载到 JVM 的运行时数据区的过程(类加载后面有专门的专题讲)。

    一、检查加载

    首先检查这个指令的参数是否能在常量池中定位到一个类的符号引用( 符号引用:符号引用以一组符号来描述所引用的目标),并且检查类是否已经被加载。
    解析和初始化过。

    二、分配内存

    接下来虚拟机将为新生对象分配内存。为对象分配空间的任务等同于把一块确定大小的内存从 Java 堆中划分出来。
    指针碰撞
    如果 Java 堆中内存是绝对规整的,所有用过的内存都放在一边,空闲的内存放在另一边,中间放着一个指针作为分界点的指示器,那所分配内存就仅仅是把那个指针向空闲空间那边挪动一段与对象大小相等的距离,这种分配方式称为“ 指针碰撞”。


    空闲列表
    如果 Java 堆中的内存并不是规整的,已使用的内存和空闲的内存相互交错,那就没有办法简单地进行指针碰撞了,虚拟机就必须维护一个列表,记录上哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录,这种分配方式称为“ 空闲列表”。

    选择哪种分配方式由 Java 堆是否规整决定,而 Java 堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。(这部分知识先了解,后续结合垃圾回收器一起去理解)
    如果是 Serial、ParNew 等带有压缩的整理的垃圾回收器的话,系统采用的是指针碰撞,既简单又高效。
    如果是使用 CMS 这种不带压缩(整理)的垃圾回收器的话,理论上只能采用较复杂的空闲列表。

    并发安全
    除如何划分可用空间之外,还有另外一个需要考虑的问题是对象创建在虚拟机中是非常频繁的行为,即使是仅仅修改一个指针所指向的位置,在并发情况下也并不是线程安全的,可能出现正在给对象 A 分配内存,指针还没来得及修改,对象 B 又同时使用了原来的指针来分配内存的情况。
    CAS机制
    解决这个问题有两种方案,一种是对分配内存空间的动作进行同步处理——实际上虚拟机采用 CAS 配上失败重试的方式保证更新操作的原子性;
    分配缓冲
    另一种是把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在 Java 堆中预先分配一小块私有内存,也就是本地线程分配缓冲(Thread Local Allocation Buffer,TLAB),JVM 在线程初始化时,同时也会申请一块指定大小的内存,只给当前线程使用,这样每个线程都单独拥有一个 Buffer,如果需要分配内存,就在自己的 Buffer 上分配,这样就不存在竞争的情况,可以大大提升分配效率,当 Buffer 容量不够的时候,再重新从 Eden 区域申请一块继续使用。
    TLAB 的目的是在为新对象分配内存空间时,让每个 Java 应用线程能在使用自己专属的分配指针来分配空间,减少同步开销。
    TLAB 只是让每个线程有私有的分配指针,但底下存对象的内存空间还是给所有线程访问的,只是其它线程无法在这个区域分配而已。当一个 TLAB 用满(分配指针 top 撞上分配极限 end 了),就新申请一个 TLAB。
    参数:
    -XX:+UseTLAB:允许在年轻代空间中使用线程本地分配块(TLAB)。默认情况下启用此选项。要禁用 TLAB,请指定-XX:-UseTLAB。(Enables the use of thread-local allocation blocks (TLABs) in the young generation space. This option is enabled by default. To disable the use of TLABs, specify -XX:-UseTLAB.)

    三、内存空间初始化

    (注意不是构造方法)内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(如 int 值为 0,boolean 值为 false 等等)。这一步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。

    四、设置

    接下来,虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息(Java classes 在 Java hotspot VM 内部表示为类元数据)、对象的哈希码、对象的 GC 分代年龄等信息。这些信息存放在对象的对象头之中。

    五、对象初始化

    在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但从 Java 程序的视角来看,对象创建才刚刚开始,所有的字段都还为零值。所以,一般来说,执行 new 指令之后会接着把对象按照程序员的意愿进行初始化(构造方法),这样一个真正可用的对象才算完全产生出来。

    对象的内存布局

    在 HotSpot 虚拟机中,对象在内存中存储的布局可以分为 3 块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。

    对象头包括两部分信息,第一部分用于存储对象自身的运行时数据,如哈希码(HashCode)、GC 分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。
    对象头的另外一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。如果对象是一个 Java 数组,那么在对象头中还有一块用于记录数组长度的数据。
    对齐填充并不是必然存在的,也没有特别的含义,它仅仅起着占位符的作用。由于 HotSpot VM 的自动内存管理系统要求对对象的大小必须是 8 字节的整数倍。当对象其他数据部分没有对齐时,就需要通过对齐填充来补全。

    对象的访问定位

    建立对象是为了使用对象,我们的 Java 程序需要通过栈上的 reference 数据来操作堆上的具体对象。目前主流的访问方式有使用句柄和直接指针两种。
    句柄
    如果使用句柄访问的话,那么 Java 堆中将会划分出一块内存来作为句柄池,reference 中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息。
    使用句柄来访问的最大好处就是 reference 中存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而 reference 本身不需要修改。


    直接指针
    如果使用直接指针访问, reference 中存储的直接就是对象地址。
    这两种对象访问方式各有优势,使用直接指针访问方式的最大好处就是速度更快,它节省了一次指针定位的时间开销,由于对象的访问在 Java 中非常频繁,因此这类开销积少成多后也是一项非常可观的执行成本。
    对 Sun HotSpot 而言,它是使用直接指针访问方式进行对象访问的。

    判断对象存活

    在堆里面存放着几乎所有的对象实例,垃圾回收器在进行回收前,要做的事情就是确定这些对象中哪些还是“存活”着,哪些已经“死去”(死去代表着不可能再被任何途径使用得对象了)

    什么是垃圾 ?

    C 语言申请内存:malloc free ,C++: new delete。 C/C++ 手动回收内存
    Java: new。Java 是自动内存回收,编程上简单,系统不容易出错。
    手动释放内存,容易出两种类型的问题:
    1、忘记回收
    2、多次回收
    没有任何引用指向的一个对象或者多个对象(循环引用)

    引用计数法

    在对象中添加一个引用计数器,每当有一个地方引用它,计数器就加 1,当引用失效时,计数器减 1。Python 在用,但主流虚拟机没有使用,因为存在对象相互引用的情况,这个时候需要引入额外的机制来处理,这样做影响效率,
    在代码中看到,只保留相互引用的对象还是被回收掉了,说明 JVM 中采用的不是引用计数法。

    可达性分析( 面试时重要的知识点,牢记)

    来判定对象是否存活的。这个算法的基本思路就是通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots 没有任何引用链相连时,则证明此对象是不可用的。

    作为 GC Roots 的对象包括下面几种(重点是前面 4 种):

    1、虚拟机栈(栈帧中的本地变量表)中引用的对象;各个线程调用方法堆栈中使用到的参数、局部变量、临时变量等。
    2、方法区中类静态属性引用的对象;java 类的引用类型静态变量。
    3、方法区中常量引用的对象;比如:字符串常量池里的引用。
    4、本地方法栈中 JNI(即一般说的 Native 方法)引用的对象。
    5、JVM 的内部引用(class 对象、异常对象 NullPointException、OutofMemoryError,系统类加载器)。(非重点)
    6、所有被同步锁(synchronized 关键)持有的对象。(非重点)
    7、JVM 内部的 JMXBean、JVMTI 中注册的回调、本地代码缓存等(非重点)
    8、JVM 实现中的“临时性”对象,跨代引用的对象( 在使用分代模型回收只回收部分代的对象,这个后续会细讲,先大致了解概念)(非重点)

    以上的回收都是对象,类的回收条件:

    注意 Class  要被回收,条件比较苛刻,必须同时满足以下的条件(仅仅是可以,不代表必然,因为还有一些参数可以进行控制):
    1、该类所有的实例都已经被回收,也就是堆中不存在该类的任何实例。
    2、加载该类的 ClassLoader 已经被回收。
    3、该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
    4、参数控制:-Xnoclassgc:禁用类的垃圾收集(GC) 。这样可以节省一些GC时间, 从而缩短了应用程序运行期间的中断时间。
    当您-Xnoclasagc在启动时指定时,应用程序中的类对象在GC期间将保持不变,并且始终被认为是活动的。这可能会导致更多的内存被永久占用,如果不谨慎使用,将抛出内存不足异常。

    废弃的常量和静态变量的回收其实就和 Class 回收的条件差不多

    Finalize  方法

    即使通过可达性分析判断不可达的对象,也不是“非死不可”,它还会处于“缓刑”阶段,真正要宣告一个对象死亡,需要经过两次标记过程,一次是没有找到与 GCRoots 的引用链,它将被第一次标记。随后进行一次筛选(如果对象覆盖了 finalize),我们可以在 finalize 中去拯救。
    代码演示:

    /**
     * @author macfmc
     * @date 2020/7/31-22:39
     */
    public class FinalizeGC {
        public static FinalizeGC instance = null;
        public void isAlive() {
            System.out.println("I am still alive!");
        }
        @Override
        protected void finalize() throws Throwable {
            super.finalize();
            System.out.println("finalize method executed");
            FinalizeGC.instance = this;
        }
        
        public static void main(String[] args) throws Throwable {
            instance = new FinalizeGC();
    
            //对象进行第1次GC
            instance = null;
            System.gc();
            Thread.sleep(1000);//Finalizer 方法优先级很低,需要等待
            if (instance != null) {
                instance.isAlive();
            } else {
                System.out.println("I am dead! ");
            }
    
            //对象进行第2次GC
            instance = null;
            System.gc();
            Thread.sleep(1000);
            if (instance != null) {
                instance.isAlive();
            } else {
                System.out.println("I am dead! ");
            }
        }
    }

    运行结果:

    finalize method executed
    I am still alive!
    I am dead! 

    可以看到,对象可以被拯救一次(finalize  执行第一次,但是不会执行第二次)
    代码改一下,再来一次。

    /**
     * @author macfmc
     * @date 2020/7/31-22:39
     */
    public class FinalizeGC {
        public static FinalizeGC instance = null;
        public void isAlive() {
            System.out.println("I am still alive!");
        }
        @Override
        protected void finalize() throws Throwable {
            super.finalize();
            System.out.println("finalize method executed");
            FinalizeGC.instance = this;
        }
        
        public static void main(String[] args) throws Throwable {
            instance = new FinalizeGC();
            //对象进行第1次GC
            instance = null;
            System.gc();
            // Thread.sleep(1000);//Finalizer 方法优先级很低,需要等待
            if (instance != null) 
                instance.isAlive();
            else 
                System.out.println("I am dead! ");
            //对象进行第2次GC
            instance = null;
            System.gc();
            // Thread.sleep(1000);
            if (instance != null) {
                instance.isAlive();
            } else {
                System.out.println("I am dead! ");
            }
        }
    }

    运行结果:

    finalize method executed
    I am dead! 
    I am dead! 

    对象没有被拯救,这个就是 finalize()方法执行缓慢,还没有完成拯救,垃圾回收器就已经回收掉了。
    所以建议大家尽量不要使用 finalize, 因为这个方法太不可靠 。 在生产中你很难控制方法的执行或者对象的调用顺序 , 建议大家忘了 finalize  方法 ! 因为在finalize  方法能做的工作,java中有更好的,比如 try-finally或者其他方式可以做得更好

    各种引用

    强引用

    一般的 Object obj = new Object() ,就属于强引用。在任何情况下,只有有强引用关联(与根可达)还在,垃圾回收器就永远不会回收掉被引用的对象。

    软引用 SoftReference

    一些有用但是并非必需,用软引用关联的对象,系统将要发生内存溢出(OuyOfMemory)之前,这些对象就会被回收(如果这次回收后还是没有足够的空间,才会抛出内存溢出)。参见代码:VM 参数 -Xms10m -Xmx10m -XX:+PrintGC
    代码演示:

    import java.lang.ref.SoftReference;
    import java.util.LinkedList;
    import java.util.List;
    
    /**
     * @author macfmc
     * @date 2020/7/31-22:56
     */
    public class gcRoot {
        public static void main(String[] args) {
            gcRoot u = new gcRoot(); //new是强引用
            SoftReference<gcRoot> userSoft = new SoftReference<>(u);
            u = null;//干掉强引用,确保这个实例只有userSoft的软引用
            System.out.println(userSoft.get());
            System.gc();//进行- -次GC垃圾回收
            System.out.println("After gc");
            System.out.println(userSoft.get());
            //往堆中填充数据,导致00M
            List<byte[]> list = new LinkedList<>();
            try {
                for (int i = 0; i < 100; ++i) {
                    System.out.println("*************" + userSoft.get());
                    list.add(new byte[1024 * 1024 * 1]); //1M的对象
                }
            } catch (Throwable e) {
                //抛出了00M异常时打印软引用对象
                System.out.println("Excepti n*************" + userSoft.get());
            }
        }
    }
    

    运行结果:

    main.java.JVM.gcRoot@4554617c
    [GC (System.gc())  3341K->856K(125952K), 0.0043645 secs]
    [Full GC (System.gc())  856K->662K(125952K), 0.0074266 secs]
    After gc
    main.java.JVM.gcRoot@4554617c
    *************main.java.JVM.gcRoot@4554617c
    *************main.java.JVM.gcRoot@4554617c
    [GC (Allocation Failure)  33072K->32502K(125952K), 0.0102263 secs]
    *************main.java.JVM.gcRoot@4554617c
    *************main.java.JVM.gcRoot@4554617c
    [GC (Allocation Failure)  64906K->64247K(125952K), 0.0123316 secs]
    [Full GC (Ergonomics)  64247K->64149K(194560K), 0.0198417 secs]

    例如,一个程序用来处理用户提供的图片。如果将所有图片读入内存,这样虽然可以很快的打开图片,但内存空间使用巨大,一些使用较少的图片浪费内存空间,需要手动从内存中移除。如果每次打开图片都从磁盘文件中读取到内存再显示出来,虽然内存占用较少,但一些经常使用的图片每次打开都要访问磁盘,代价巨大。这个时候就可以用软引用构建缓存。

    弱引用 WeakReference

    一些有用(程度比软引用更低)但是并非必需,用弱引用关联的对象,只能生存到下一次垃圾回收之前,GC 发生时,不管内存够不够,都会被回收。
    代码演示:

    import java.lang.ref.WeakReference;
    
    /**
     * @author macfmc
     * @date 2020/7/31-22:56
     */
    public class gcRoot {
        public static void main(String[] args) {
            gcRoot u = new gcRoot();
            WeakReference<gcRoot> userWeak = new WeakReference<>(u);
            u = null;//干掉强引用,确保这个实例只有userWeak的弱引用
            System.out.println(userWeak.get());
            System.gc();//进行一次GC垃圾回收
            System.out.println("After gc");
            System.out.println(userWeak.get());
        }
    }
    

    运行结果:

    main.java.JVM.gcRoot@4554617c
    [GC (System.gc())  3341K->752K(125952K), 0.0014105 secs]
    [Full GC (System.gc())  752K->625K(125952K), 0.0103507 secs]
    After gc
    null

    注意:软引用 SoftReference 和弱引用 WeakReference,可以用在内存资源紧张的情况下以及创建不是很重要的数据缓存。当系统内存不足的时候,缓存中的内容是可以被释放的。实际运用(WeakHashMap、ThreadLocal)

    虚引用 PhantomReference

    幽灵引用,最弱(随时会被回收掉)垃圾回收的时候收到一个通知,就是为了监控垃圾回收器是否正常工作

    展开全文
  • 用户可以通过该组件操作数据库,包括新建、修改、删除数据库、表、视图等数据库对象,新建查询、设置关系图、设置系统安全、数据库复制、数据备份、恢复等操作,是SSMS最常用、也是最重要的一个组件。 (1)SSMS的...
  • 又称帕累托分析法或巴雷托分析法、柏拉图分析、主次因分析法 、ABC分析法、分类管理法、物资重点管理法、ABC管理法、abc管理、巴雷特分析法,平常我们也称之为“80对20”规则,EMBA、MBA等主流商管教育均对ABC分类法...
  • JSP九大内置对象

    万次阅读 多人点赞 2019-02-28 15:52:26
    JSP提供了由容器实现和管理的内置对象,也可以称之为隐含对象,由于JSP使用Java作为脚本语言,所以JSP将具有强大的对象处理能力,并且可以动态创建Web页面内容。但Java语法使用一个对象前,需要先实例化这个对象,...
  • 开源对象存储MinIO技术白皮书

    万次阅读 多人点赞 2019-09-27 13:38:36
    MinIO创始者是Anand Babu Periasamy, Harshavardhana(戒日王)等人, Anand是GlusterFS的初始开发者、Gluster公司的创始人...MinIO设计上汲取了GlusterFS的相关经验与教训,系统复杂度上作了大量简化。 一、Min...
  • 类图和对象图(用户管理模块建模)

    千次阅读 2018-12-24 11:35:32
    类图是逻辑视图的重要组成...类图是UML建模最基本和最重要的一类图。 程序设计的不同阶段,类图的作用也不相同。分析阶段,类图主要用于一些概念类的描述;设计阶段,类图主要用于描述类的外部特性;实...
  • 你好,我是前阿里手淘前端负责人winter,这篇文章来自于我极客时间专栏「重学前端」讲解JavaScript的部分。与其它的语言相比,JavaScript的“对象”总是显得不是那么合群。一些新人学习JavaScript面向对象时...
  • 使用面向对象语言,离不开的就是对象,现在回过头来思考一下,真的了解所使用语言的对象么?我自己有点心虚,对于Java的对象...要计算Java对象占用内存的大小,首先需要了解Java对象在内存的实际存储方式和存储格式。
  • 这个3D游戏世界基本上就可以涵盖市面上所有类型的游戏了,我们通过各种手段去管理好这个游戏世界,就能快速的进行各种游戏逻辑的开发。 笔记与总结(请先学习视频内容): 1.什么是场景物体?什么是游戏场景? ...
  • Java引用对象

    万次阅读 2018-12-11 10:09:03
    简介 写了15年C/C++之后,我于1999年开始写Java。借助指针切换(pointer handoffs)等编码实践或者Purify等工具,我认为...我不需要再管理内存后我才意识到之前耗费了多少精力。 接着我就遇到了第一个OutOfMemor...
  • 文章目录《C++面向对象程序设计》✍千处细节、万字总结一、面向对象程序设计二、C++基础2.1 C++的产生和特点2.2 一个简单的C++示例程序2.3 C++非面向对象方面对C语言的扩充输入和输出cinconst修饰符void型指针内联...
  • Java的基本操作单元 - 类和对象

    千次阅读 多人点赞 2020-02-26 18:51:25
    说到类和对象,这都是两个比较抽象的词,如果用程序,那就更不知道是什么意思了。其实大家可以先不联系程序,我们对一大堆东西分类的时候自然是有一个标准的,符合某些特征的分到一类,符合另外一些特征的分到...
  • JVM中对象创建与内存分配机制--JVM系列(3)

    万次阅读 热门讨论 2020-11-12 10:52:38
    Padding(字节对齐):64位操作系统保证对象是8个字节的整数倍,对于大部分处理器,对象以8字节整数倍来对齐填充都是高效的存取方式,字节对齐深层次原因是:各个硬件平台对存储空间的处理上有很大的不同,...
  • 他认为,任何一组东西最重要的只占其中一小部分,约20%,其余80%尽管是多数,却是次要的,因此又称二八定律。 二八现象 1.管理学:通常一个企业80%的利润来自它20%的项目[3];这个80/...
  • CANOpen系列教程09_CANOpen对象字典

    千次阅读 多人点赞 2018-10-22 23:08:04
    初学者学习如果难理解通信接口(上一篇)有些内容,如:PDO、SDO及网络管理对象,可以结合对象字典索引来学习和记忆。 2 对象字典重要内容 对象字典OD :Object dictionary。 一组带有 16 位索引和 8 位子索引的数据...
  • 第一题: 面向对象设计应该遵循哪些准则? 简述每条准则的内容,并说明遵循这条准则的必要性。 答: 面向对象设计准则: 1、模块化;把程序整体划分成一个个独立命名且可独立访问的完成单个子功能的...
  • 深入理解工厂模式——由对象工厂生成对象

    万次阅读 多人点赞 2018-05-27 20:12:45
    1.3 开源框架的使用 1.4 为什么要用工厂模式 二 简单工厂模式 2.1 介绍 2.2 适用场景 2.3 简单工厂模式角色分配: 2.4 简单工厂实例 2.5 使用反射机制改善简单工厂 3 工厂方法模式 3.1 介绍 3.2 适用场景 ...
  • 对象存储(云存储)概述

    万次阅读 多人点赞 2019-03-08 17:54:09
    NAS与SAN概述1、NAS(Network Attached Storage)优点局限2、SAN(Storage Area Network and SAN Protocols)优点局限对象存储的特性1、优秀的扩展性2、基于策略的自动化管理3、多租户技术扩展知识:多租户技术介...
  • Spring IOC的Bean对象

    万次阅读 2020-05-11 16:13:15
    Spring IOC的Bean (一)Bean是什么 突然发现提到了好多次Bean,居然忘记了讲Bean是什么。没事,现在讲也不晚。Java的Bean是一种规范,是一种特殊的java类。所以我们先来看看Bean的规范。 Bean必须生成public ...
  • Java基础知识面试题(2020最新版)

    万次阅读 多人点赞 2020-02-19 12:11:27
    对象相等判断 == 和 equals 的区别是什么 hashCode 与 equals (重要) 对象的相等与指向他们的引用相等,两者有什么不同? 值传递 当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化...
  • 计算机存储的发展(块存储,文件存储,对象存储)

    万次阅读 多人点赞 2018-09-15 15:04:08
    对象存储 1、对象 2、对象存储设备 3、元数据服务器(Metadata Server,MDS) 4、对象存储系统的客户端Client 三者之间异同比较 参考文献 如果要实现一个计算机,那么这个计算机一定要有以下的三个部分构成:...
  • 面试官的因势利导下,很多人对jvm的内存模型已经耳熟能详,但是对我们经常new 出来的对象,比如new Object(),你了解它的内存模型吗?本篇文章将带你走进对象内部,真正去了解这个你熟悉,也不熟悉的的对象。 ...
  • 面向对象的概念介绍

    万次阅读 多人点赞 2018-08-24 09:02:18
    这一篇主要是介绍面向对象各种概念性的东西,并不会说具体的代码怎样写,所以可以用一种轻松的心态去看。 一、面向过程和面向对象 面向对象是相对于面向过程的一种编程方式。 面向过程的编程方式由来已久,我刚...
  • 阿里云的对象存储服务,oss 简介

    千次阅读 2021-10-22 13:50:19
    电子科大的21学年的 数据库新技术 课程,我接触到了一个 对象存储的 概念。 过往接触的就是那些 mysql 这些二维表的数据库,做一些java 项目,比如 springboot 的项目,也是直接讲对象的字段,逐一插入 ...
  • 现如今还是菜菜,如若有错误的地方还望指正!...首先还是来一句OC的内存管理的黄金法则比较好  如果对一个对象使用了alloc、[Mutable]copy,retain,那么你必须使用相应的realease或者autorelease。(黄金法
  • C++每个对象都有数据(体现了‘属性’)和函数(用来对数据进行操作,以实现某些功能)两部分组成。类:具有共性的实体的抽象。类是对象的抽象,不占有内存。对象是类的特例,即类的具体表现形式,占有存储空间...
  • 面向对象设计的七大设计原则详解

    万次阅读 多人点赞 2018-10-03 12:32:21
    面向对象的七大设计原则 文章目录面向对象的七大设计原则简述七大原则之间的关系一、开闭原则(The Open-Closed Principle ,OCP)概念理解系统设计需要遵循开闭原则的原因开闭原则的实现方法一个符合开闭原则的...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 694,197
精华内容 277,678
关键字:

在各种管理对象中最重要的是