我是一个Java类(必看,附带精彩吐槽)

来自:互联网全栈架构(微信号:InternetFullStack),作者:践行精神自由

大家好,我是一个Java类,很高兴能向大家介绍一下我的前世今生。要知道,自从面向对象这种编程思想横空出世以来,它变得越来越受欢迎,而类这个概念又在其中扮演了非常核心的角色,对于这一点,我感到很骄傲。

在Java语言中,一切都是对象,那么对象是来自哪里呢?其实就是从我这里出来的啦。你可以把我理解为工厂里的一个模具,对象就是通过这个模具生成的,可能这个比喻也不是很准确,但大抵上是这个意思吧。

先来看看我长什么样子,极简的定义甚至可以是这样:

class JavaClass{
}


怎么样,很简单吧?
在关键字class后面,加上类的名字,一个精致的Java类就脱颖而出了。刚才说过,我就像是一个模具,需要生产出真正的产品才有意义,而这个生产过程也是相当的简便和直观,使用new这个关键字就可以了:

JavaClass javaClass = new JavaClass();


这个JavaClass里面啥都没有,生产出来的对象也基本上没什么卵用,在实际编程中,我们不可能就定义这么一个空玩意儿,还是得有血有肉才行,一个模具生产出来的产品毫无用处,那还有什么存在的价值呢?
当然,模具只是一个比喻,更准确一点讲,Java类,也就是我,应该是一种类型,里面可以定义属性和方法,也只有定义了这些,我才变得有作用、有意义,才会有血有肉,否则就是空壳一个。比如我们可以定义一个Person类,ta有身高、年龄、爱好这些属性,并且会走路、跑步、交谈:

public class Person {
    private int age;
    private int height;
    private String hobbies;
    public void walk(){
        System.out.println("I can walk!");
    }
    public void run(){
        System.out.println("I can run!");
    }
    public void talk(){
        System.out.println("I can talk!");
    }
}


定义完毕,一个活灵活现、有血有肉、丰富饱满的Java类就跃然于纸上了。
接下来,我们写一个“世界你好”的程序,然后在这个程序中使用Person类。唉,还是不能免俗,依然是从HelloWorld入手:

public class HelloWorld {
    public static void main(String[] args){
        Person person = new Person();
        System.out.println("Hello World");
        person.walk();
    }
}


除了在屏幕上打印出熟悉的Hello World之外,他还骄傲地宣布I can Walk!
在程序中,我们首先创建了一个Person对象,并且用person这个引用指向它(引用和对象的关系,就像是遥控器和电视机的关系一样,这个比喻确实比较贴切),然后在屏幕上打印Hello World,最后调用Person类中的walk方法,这个walk方法也是在屏幕上打印东西,所以就出现了上面所说的结果。


说到这里,有人可能会想:我脱了外衣,屏气凝神,你就给我看这个?是不是太简单了?看官别急,待我一一道来,虽然我们知道那个Hello World程序的输出结果,但我们还不知道这底层的原理到底是什么,也就是只知其然,但不知其所以然。

众所周知,Java是一个跨平台的编程语言,简单来说,就是你写好的Java程序,不用修改源码就可以运行在任何平台上,也就是说不用针对每个平台去进行适配,包括Linux、Windows、Unix等等,这确实相当地酷。这是怎么做到的呢?其实就是靠Java虚拟机了,也就是JVM。这可是个大家伙,非常的重要,先来看看它的架构吧:


从上图可以看出,JVM主要由类加载器子系统、运行时数据区域和执行引擎所组成。
代码编写完成后,使用javac命令就把我们这些Java类编译成了一个个的.class文件,有些高端玩家还会使用javap命令,查看对应的字节码,深入了解我们的运行机制,嗯,比较有逼格的感觉。


接下来,类加载器就要登场了,它的主要职责就是把.class文件加载到内存中,类加载器有三种:
根加载器(Bootstrap Classloader),扩展加载器(Extension  Classloader),系统加载(System  Classloader),当然,用户也可以自定义一个类加载器,虽然可以自己定义,但整个加载过程不会遭到破坏,类加载机制中的双亲委派机制了解一下。


JVM会把它所管理的内存划分为几个区域,也就是图中运行时数据区的那五个:
方法区,堆,虚拟机栈,程序计数器,本地方法栈。简要地说,方法区用来存放已加载的类相关的信息;是对象的生存场所,我们主要就是在这个区域活动,后面会详细介绍;虚拟机栈由栈帧组成,栈帧用于存储局部变量表、操作栈、中间结果等信息,局部变量表里保存了对象的引用;程序计数器记录了每个线程执行的位置;本地方法栈与虚拟机栈非常类似,只不过它是为Native方法服务的。


我们主要的生活区域就是在堆里面,先来看看它的构成吧:


JVM给了我们一套三居室:
年轻代,老年代和永久代。现在房价这么贵,JVM还舍得给我们这么大的一个房间,还真是挺不错的,当然,我们在里面不能自由活动,需要受到JVM这个家伙的调遣:我们刚刚出生时,就在Eden区里待着,如果Minor GC过后我们还活着,就会挪到Survivor区(就是图中的S0和S1),各种Minor GC扫荡以后如果还活着,JVM就会把我们塞到老年代的区域,如果不断有对象进到这个区域,空间越来越小,那完了,肯定炸锅,然后就抛出OutOfMemoryError异常。永久代主要存放类的元数据信息,到了JDK1.8,这块区域变成了元空间。

刚才提到的GC,是Garbage Collector的缩写,也即是垃圾回收器的意思。对于这个称呼,我在这里要狠狠地吐槽一下,什么叫Garbage,啥是垃圾?我们兢兢业业地工作,勤勤恳恳地劳动,奉献了我们的青春,洒上了我们的热血,最后没有引用指向我们的时候,就把我们称为垃圾,这未免也太过份了,有这么对待功臣的么,我们没有功劳也有苦劳,没有苦劳也有疲劳,而且还经常过劳,最后落得这么一个名份,实在是太伤人自尊了(此处省去128个脏字)!所以我强烈建议大家以后不要再称呼我们为Garbage了,不妨叫做Retired Objects(退休的对象),或者叫做Rebirth Objects(重生的对象),或者叫做其他什么名字都行,但千万别再是Garbage,这实在让人受不了,每次看到这个就感觉受到了1024次暴击。

不光是名字讨厌,GC这个家伙干的事也挺让人反感的,他的任务就是把我们干掉,然后回收内存,好不容易分到的房子,就被他拿回去了,所以每次他要出现的时候,我们都会被吓得在角落里瑟瑟发抖,生怕自己被他抓走,这家伙为了达到这个目的,还经常stop the world,就是放下其他所有事情,专心致志地来搞定我们,我去,真够狠的。

为了把事情说明白,只能面对现实,暂时继续沿用这个称呼了。Java虚拟机规范中没有明确规定垃圾回收的具体算法,但常用的一些算法有:

  • 引用计数法 (Reference Counting)

  • 标记-清除算法 (Mark-Sweep)

  • 复制算法 (Copying)

  • 标记-压缩算法 (Mark-Compact)

  • 增量算法 (Incremental Collecting)

  • 分代 (Generational Collecting)


具体的垃圾收集器有Serial收集器,ParNew收集器,Parallel Scavenge收集器,CMS收集器等等,具体的信息大家可以上网搜索,同时也可以在参数中设置使用哪些垃圾收集器、垃圾回收的停顿时间等。
要不是为了完整地介绍整个过程,其实我根本就不想碰GC这破玩意。

GC一旦出现,我们的好日子就不多了,也到了跟大家说再见的时候了,不过请不要伤感,只要有需要,重新运行程序,我们又会回来跟大家见面的,所以我前面建议,不要用Garbage这个词,用Retired Objects或者Rebirth Objects,我们只不过是暂时退休或者等待重生罢了,退休也是可以返聘的嘛,不是吗?

这就是我,一个朴实的Java类,为了人类的幸福,为了世界的和平,在自己的岗位上克己奉公,认认真真地完成自己的职责,一丝不苟地执行命令,忠于职守,奉献到底,我为我有这样的表现而感到无上的光荣。

谢谢大家!

推荐↓↓↓
Java编程
上一篇:咱们从头到尾说一次 Java 垃圾回收 下一篇:记一次愚蠢的操作--String不可变性