一、数据类型

  • java虚拟机中,数据类型可以分为两类,基本类型和引用类型。
  • 基本类型的变量保存原始值,即:它代表的值就是数值本身,而引用类型的变量保存引用值,可以简单理解为存储地址。
  • “引用值”代表了某个对象的引用,而不是对象本身,对象本身存放在这个引用值所表示的地址的位置。
  • 基本类型共八种,包括:byte、short、int、long、char、float、double、boolean。
  • 引用类型包括:类类型、接口类型和数组。

二、堆与栈

SRE实战 互联网时代守护先锋,助力企业售后服务体系运筹帷幄!一键直达领取阿里云限量特价优惠。
  • 堆和栈是程序运行后的关键,很有必要把他们的关系理解清楚。
  • 在java中,Main函数就是栈的起始点,也是程序的起始点。程序要运行总要有一个起点的(程序执行的入口)。
  • 总结
    • 栈是程序执行的到单为,而栈是数据存储的单元
    • 栈解决程序的运行问题,即程序如何执行,或者说如何处理数据;堆解决数据的存储问题,即数据怎么放,放在那。
    • 在java中一个线程就会相应有一个线程栈与之对应,因为不同的线程执行逻辑有所不同,因此需要一个独立的线程栈;而堆则是所有线程共享的。
  • 为什么要把堆和栈区分出来,栈不也是可以存储数据吗?
    • 从软件设计的角度来看,栈代表了处理逻辑,而堆代表了数据。这样分开,是的处理逻辑更为清晰。这种隔离、模块化的思想在软件设计的方方面面都有提现。
    • 堆与栈的分离,使得堆中的内容可以被多个栈共享。提供可一种有效的数据交互方式(共享内存);堆中的共享常量和混存可以被所有栈访问,节省了时间。
    • 栈因为运行的需求,比如保存系统运行的上下文,需要进行地址段的划分。由于栈只有向上增长,因此就会限制住栈的存储能力;而堆不同,堆中的对象是可以根据需要动态增长的,因为栈与堆的拆分使得动态增长成为可能,相应栈中只需要记录堆中的一个地址即可。
    • 面向对象就是堆与栈的完美结合。其实,面向对象方式的程序与以前结构化的程序在执行上没有任何区别。但是面向对象的引入,使得对待问题的思考方式发生了改变,而更接近于自然方式的思考。当我们把对象拆开,就会发现,对象的属性其实就是数据,存放在堆中,对象的行为(方法)就是运行逻辑,存放在栈中。我们在编写对象的时候,其实就是编写了数据结构,也编写了处理数据的逻辑。
  • 堆中存什么?栈中存什么?
    • 栈存储的信息都是当前线程(或程序)相关的信息(局部变量、程序运行状态、方法、方法返回值)等,栈中存的是基本数据类型和堆中对象的引用。一个对象的大小不可估计,或者说是可以动态变化的,但是在栈中,一个对象只对应了一个4byte的引用(堆栈分离的好处)。
    • 堆只负责存储对象信息。
  • 为什么不把基本类型放到堆中?
    • 其占用的空间一般都是1~8byte,需要空间比较少。
    • 因为是基本类型,所有不会出现动态增长的情况,长度固定,因此在栈中存储就够了,所以把他放在堆中是没有什么意义的,还会浪费空间。
  • java中的参数传递是传值?还是传引用
    • 对象传递是引用值的传递,基本数据类型传递是值传递。实际上这个传入函数的值是对象引用的拷贝,即传递的是引用的地址值,所以还是按值传递的。
    • 堆和栈中,栈是程序运行最根本的东西。程序运行可以没有堆,但是不能没有栈,而堆是为栈进行数据存储服务的,说白了堆就是一块共享的内存,不过正是堆和栈分离的思想,才使得java的垃圾回收成为可能。
    • java中,栈的大小通过-Xss来设置的,当栈存储的数据比较多时,需要适当调大这个值,否则会出现java.lang.StackOverflowError异常。常见的出现这个异常是无返回值的递归,因为此时栈中保存的信息都是方法返回的记录点。
  • java的大小如何计算?
    • 基本数据类型的大小是固定的。
    • 在java中,一个空的Object对象的大小是8byte,这个大小只是保存堆中一个没有任何属性的对象的大小。Object ob = new Object()这样在程序中完成一个java对象的申明,但是它所占的空间为 4byte + 8byte。
    • 因为所有的java非基本类型的对象都是需要默认继承Object对象,因此不论什么样的java对象,其大小都必须是大于8byte的。
    • class Obj {
        int age;
        String name;
        Boolean flag;
      }

       其大小为:空对象大小(8byte) + int大小(4byte) + Boolean大小(1byte) + 空Object引用大小(4byte) = 17byte,但是java在对象内存分配时都是以8的整数倍来分的,因此大于17byte的最接近8的整数倍就是24,因此的此对象大小为24byte.

三、引用类型

  • 对象引用类型分为强引用、软引用、弱引用和虚引用。
  • 强引用:我们一般申明对象是虚拟机生成的引用,强引用环境下,垃圾回收时需要严格判断当前对象是否被强引用,如果被强引用,则不会被垃圾回收,创建一个对象,new 出来的对象都是分配在java堆中的。
  • 软引用:一般被作为缓存来使用。与强引用的区别是,软引用在来及回收时,虚拟机会根据当前系统的剩余内存来决定是否对软引用进行回收。如果剩余内存比较紧张,则虚拟机会回收软引用所占用的空间,如果剩余内存相对富裕,则不会进行回收,换句话说,虚拟机发生OutOfMemory时,肯定是没有软引用的存在。
  • 弱引用:弱引用与软引用类似,都是作为缓存来使用,但是弱引用在进行垃圾回收时,是一定会被回收掉的,因此其生命周期只存在于一个垃圾回收周期内。
  • 虚引用:虚引用比不会决定对象的生命周期,如果一个对象仅持有虚引用,那么他和没有任何引用一样,在任何时候都可能会被垃圾回收期回收,虚引用主要用来跟踪对象被垃圾回收器回收的活动。

四、垃圾回收算法

  • 按照基本回收策略分
    • 引用计数(Reference Counting):比较古老的回收算法。原理是此对象有一个引用,即增加一个计数,删除一个引用则减少一个计数。垃圾回收时,只收集计数为0的对象。此算法最致命的是无法处理循环引用的问题。
    • 标记-清除(Mark-Sweep):分两个阶段:第一阶段从引用根节点开始标记所有被引用的对象;第二阶段遍历整个堆,把为标记的对象清除。此算法需要暂停整个应用,同时会产生内存碎片。
    • 复制(Copying):把内存空间划分为两个相等的区域,每次只是用其中一个区域,垃圾回收时遍历当前使用区域,把正在使用的对象复制到另外一个区域中。此算法每次只处理正在被使用中的对象,因为复制成本比较少,同时复制过去以后还能进行相应的内存整理,不会出现碎片问题,缺点就是需要两倍内存空间
    • 标记-整理(Mark-Compact):结合了标记-清除和复制两个算法的优点。也分为两个阶段:第一阶段从根节点开始标记所有被引用对象;第二阶段遍历整个堆,清除未标记对象并且把存活对象压缩到堆中的其中一块,按顺序排放,此算法避免了碎片的问题,同时也避免了空间问题。
  • 按分区对待的方式分
    • 增量收集(Incremental Collecting):实时垃圾回收算法,即在应用进行的同时进行垃圾回收。
    • 分代收集(Generational Collecting):基于对象生命周期分析后得出的垃圾回收算法。把对象分为年轻代、年老代、持久代。对不同生命周期的对象使用不同的算法进行回收。
  • 按系统线程分
    • 串行收集:使用单线程处理所有的垃圾回收工作,因为无需多线程交互,实现容易,而且效率较高,但是局限性也比较明显,即无法使用多处理器的优势,所以适合单处理器机器。
    • 并行收集:使用多线程处理垃圾回收工作,因而速度快,效率高,而且从理论上CPU数目越多,越能体现出并行收集的有序。
    • 并发收集:GC线程和应用线程大部分时间是并发执行,只是在初始标记和二次标记时需要stop-the-world,这可以大大缩短停顿时间,所以适用于响应时间优先的应用,减少用户等待时间。
扫码关注我们
微信号:SRE实战
拒绝背锅 运筹帷幄