包
包的创建规则
包可以有层次,在创建包时可以一次性创建多级包,上下级之间用“.”分割,如下图所示。
访问修饰符
简介
访问修饰符包括:private、protected、public。三者都可用于修饰成员变量、成员方法,从而影响成员变量、成员方法的访问级别。
只有public可用于修饰类,从而影响类的访问级别。
成员的访问级别
成员变量、成员方法有4种访问级别,它们与修饰符的关系见下表:
类的访问级别
类有2种访问级别,它们与修饰符的关系见下表:
垃圾回收
谁是垃圾
栈中数据会随着方法调用的完成而出栈,不存在垃圾。堆中数据可以被栈引用,当栈中数据出栈后,引用将消失,此时堆中的数据没有销毁,就是垃圾。
清理垃圾、回收内存
所谓垃圾回收,是指清理堆中的垃圾数据,从而回收内存。Java的垃圾回收是自动进行的,即JVM会定时的扫描堆内存,完成垃圾清理的任务。开发者也可以通过调用“System.gc()”来催促JVM尽快进行垃圾回收,但不建议这样做,因为这会打乱JVM的节奏,降低它的运行效率。
扩展阅读:
GC(Garbage Collection)大部分人都把这项技术当做Java语言的伴生成物。事实上,GC的历史要远远比Java久远,1960年诞生于MIT的Lisp是第一门真正石永红内存动态分配和垃圾收集技术的语言。当Lisp还在胚胎时期时,人们就在思考GC需要完成的三件事情:哪些内存需要回收?
什么时候回收?
如何回收?
经过半个多世纪的发展,内存的动态分配与内存回收技术已经相当成熟,一切看起来进入了“自动化”时代,那为什么我们还要去了解GC和内存分配呢?
答案:当需要排查各种内存溢出、内存泄露问题时,当垃圾收集器成为系统达到更高并发亮的瓶颈时,我们就需要对这些“自动化”的技术实施必要的监控和调节。
垃圾收集器在对堆进行回收前,第一件事就是要确定对象有哪些还“存活”着,哪些已经“死去”(即不可能再被任何途径使用的对象)?
引用计数器算法
很多人在面试时的回答就是引用计数器算法,那么什么是引用计数器算法呢?
给对象添加一个引用计数器,每当有一个地方引用它时,计数器加1;当应用失效时计数器就减1;任何时刻计数器都为0的对象是不可能被使用的,就认定它时死的对象。
该算法实现简单,判定效率也很高,在大多数情况下它是一个不错的算法,比较著名的应用案例,如:微软的COM(Component Object Model)技术、Python语言、Squireel。但是Java语言中没有选用该算法,其中最主要的原因就是它很难解决对象之间的相互循环应用的问题。
举例:
package com.jinxf;
public class ReferenceCountGC {
public Object instance = null;
private static final int _1MB = 1024*1024;
//这个成员属性的唯一意义就是占点内存,以便能在GC日志中看清楚是否被回收过 private byte[] bigSize = new byte[2*_1MB];
public static void main(String args[]){
ReferenceCountGC objA = new ReferenceCountGC();
ReferenceCountGC objB = new ReferenceCountGC();
objA.instance = objB;
objB.instance = objA;
objA = null;
objB = null;
//假设在这行放生GC,那么objA和objB是否能被回收? System.gc();
}
}
main方法:对象objA和objB都有字段instance,通过该字段让他们两个互相引用,然后将他们两个置为null不可能再被访问,但是他们因为互相引用着对象,导致它们的引用计数器都不为0,于是引用计数算法无法通知GC收集器收集它们。
com.jinxf.ReferenceCountGC
[GC (System.gc()) [PSYoungGen: 9359K->936K(153088K)] 9359K->944K(502784K), 0.0007702 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (System.gc()) [PSYoungGen: 936K->0K(153088K)] [ParOldGen: 8K->699K(349696K)] 944K->699K(502784K), [Metaspace: 3337K->3337K(1056768K)], 0.0038525 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
PSYoungGen total 153088K, used 3947K [0x0000000715c00000, 0x0000000720680000, 0x00000007c0000000)
eden space 131584K, 3% used [0x0000000715c00000,0x0000000715fdafd8,0x000000071dc80000)
from space 21504K, 0% used [0x000000071dc80000,0x000000071dc80000,0x000000071f180000)
to space 21504K, 0% used [0x000000071f180000,0x000000071f180000,0x0000000720680000)
ParOldGen total 349696K, used 699K [0x00000005c1400000, 0x00000005d6980000, 0x0000000715c00000)
object space 349696K, 0% used [0x00000005c1400000,0x00000005c14aec08,0x00000005d6980000)
Metaspace used 3350K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 365K, capacity 388K, committed 512K, reserved 1048576K
Process finished with exit code 0
说明:
GC开头的说明此次垃圾回收为Minor GC,而Full GC开头的说明此次垃圾回收为stop-the-world的类型
PSYoungGen表示年轻代,ParOldGen表示老年代
方括号内的"9359K->936K(153088K)]“表示"GC前该内存区域已使用容量->GC后该内存区域已使用容量(该内存区域总容量)”
方括号外的"9359K->944K(502784K)“表示"GC前Java堆已使用容量->GC后Java堆已使用容量(Java堆总容量)”
“0.0009304 secs"表示该内存区域本次GC所占用的时间,单位是秒。有的收集器会给出更具体的时间数据,如”[Time:user=0.01 sys=0.01 real=0.02 secs]",这里的user、sys、real与Linux的time命令所输出的时间含义一致,分别代表用户态消耗的CPU时间、内核态消耗的CPU时间、操作从开始到结束所经过的墙钟时间。CPU时间和墙钟时间的区别是,墙钟时间包括各种非运算的等待耗时,如等待磁盘I/O、等待线程阻塞,而CPU时间不包括这些耗时,但当系统有多CPU或者多核的话,多线程操作会叠加这些CPU时间,所以若看到user或sys时间超过real时间也是完全正常的。
eden、from、to表示年轻代中各区域的划分
Metaspace表示元空间的大小,这里用的是JDK1.8,如果是JDK1.7则没有该区域
但是从GC日志中得出虚拟机并没有因为这两个对象相互引用就不回收他们,这也从侧面上说明虚拟机并不是通过引用计数算法来判断对象是否存活的。
根搜索算法
Java、C#、Lisp都是使用根搜索算法(GC Roots Tracing)判定对象是否存活的。该算法的思路是通过一系列的名为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径成为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连(用图论的话来说就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的。
如下图所示,对象object5、object6、object7虽然互相有关联,但是他们到GC Roots是不可达的,所以它们将会被判定为是可回收的对象。
Java语言里,可作为GC Roots的对象包括下面几种:虚拟机栈(栈帧中的本地变量表)中的引用对象
方法区中的类静态属性引用的对象。
方法区中常量引用的对象
本地方法栈中JNI(即一般说Native方法)的引用的对象
作业创建名为“com.jinxf.hello”的包,然后去workspace下找到这个项目,观察src和bin下面的目录结构。
2.在Eclipse中导入老师上课所写的“成员的访问级别示例”项目。在Demo类的main方法中,调用“v.”,观察弹出窗口中所能访问到的Vehicle对象的内容。
在Car类的test方法中,调用“super.”,观察弹出窗口中所能访问到的父类的内容。
在Test类的main方法中,调用“v.”,观察弹出窗口中所能访问到的Vehicle对象的内容。