1.关于HashCode
不能根据hashCode值判断两个对象是否相等,但可以直接根据hashCode值判断两个对象不相等。
如果两个对象的hashCode值不等,一定是不同的对象,要判断两个对象是否真正相等,必须通过equals()方法
如果调用equals()方法得到的结果为true,则两个对象的hashCode值一定相等
如果equals()方法得到的结果为false,则两个对象的hashCode值不一定不同
如果两个对象的hashcode值不等,则equals方法得到的结果必定为false;
如果两个对象的hashcode值相等,则equals方法得到的结果未知。
在重写equals()方法时,必须重写hashCode方法,让equals()方法和hashCode()方法始终在逻辑上保持一致性
hashCode方法的主要作用是为了配合基于散列的集合一起正常运行,这样的散列集合包括HashSet、HashMap以及HashTable
Java里静态语句块是优先对象存在,也就是优先于构造方法存在,我们通常用来做只创建一次对象使用,类似于单列模式而且执行的顺序是:
父类静态语句块 -> 子类静态语句块 -> 父类非静态代码块、父类构造方法 -> 子类非静态代码块构造方法
静态语句块可以看作在类加载的时候执行,且只执行一次
Java对象初始化顺序:
静态代码块内容先执行(父>子),接着执行父类非静态代码块和构造方法,然后执行子类非静态代码块和构造方法
首先执行父类静态的内容,父类静态的内容执行完毕后,接着去执行子类的静态的内容,当子类的静态内容执行完毕之后,再去看父类有没有非静态代码块,如果有就执行父类的非静态代码块,
父类的非静态代码块执行完毕,接着执行父类的构造方法;父类的构造方法执行完毕之后,它接着去看子类有没有非静态代码块,如果有就执行子类的非静态代码块。子类的非静态代码块执行完毕再去执行子类的构造方法
2.JVM内存模型
程序计数器
用来指示 执行哪条指令的
如果线程执行的是非native方法,则程序计数器保持的是当前需要执行的指令的地址
如果线程执行的是native方法,程序计数器中的值是undefined
Java栈
存放一个个的栈帧,每个栈帧对应一个被调用的方法。
栈帧中包括:局部变量表、操作数栈、指向当前方法所属的类的运行时常量池的引用、方法返回地址和一些额外的附加信息
当线程执行一个方法时,就会随之创建一个对应的栈帧,并将建立的栈帧压入栈,当方法执行完后,便会将栈帧出栈
线程当前执行的方法所对应的栈帧位于Java栈的顶部
栈帧
局部变量表
操作数栈
指向运行时常量池的引用
方法返回地址
额外的附加信息
堆
存储对象本身以及数组
方法区 --- 线程共享区域
1、存储了每个类的信息(包括:类名称、方法信息、字段信息)、静态变量、常量以及编译后的代码等
2、常量池
本地方法栈
3.Hash表
散列表,能快速定位到想要查找的记录,而不是与表中存在的记录的关键字比较来进行查找。
Hash表的设计,采用了函数映射思想,将记录的存储位置与记录的关键字关联起来,从而能够很快速地进行查找
eg:
张三 13980593357
李四 15828662334
王五 13409821234
张帅 13890583472
Hash表能通过"李四"这个信息直接获取到该记录在表中的位置,复杂度为O(1)
原理:
Hash表采用一个映射函数
f:key --> address
将关键字key映射到该记录在表中的存储位置
说明:
1.映射关系 称为 Hash函数
2.通过Hash函数和关键字计算出来的存储位置(hash表中的存储位置,不是实际的物理地址) 称为 Hash地址
联系人信息采用Hash表存储时,当想找 "李四" 的信息时,直接根据 "李四" 和 Hash函数,计算出Hash地址即可
4.Java线程安全的本质:线程中并没有存放任何对象数据,而是在执行时,去主内存(堆)中去同步数据,所有的对象数据都存在JVM的堆中,因此需要对资源进行共享锁!!!
堆 --- JVM的核心数据存储区 --- 线程共享的主内存
堆中为JVM的所有对象分配了内存空间用以存储和维护变量值等
栈 --- 线程私有的内存区,由栈帧(线程栈)组成,存放8中基本数据类型和对象引用
每个线程都会生成一个自有的线程栈,线程栈中用存储了该线程的基本数据常量,变量值,以及对象长变量的引用
每个线程执行时,根据代码顺序,压栈 栈帧(栈内存)
对象变量在线程执行时的过程:!!! --- 由JVM内存模型决定
1.线程根据栈中的引用去堆上同步该对象数据下来,然后在线程自己的内存中进行操作
2.操作之后再将线程栈撒花姑娘的运算结果同步到堆(主内存)中
3.多线程时,因为每个线程都操作自己从主内存(JVM堆)中同步过来的数据,如果不加锁,会导致线程安全问题(数据提交到主内存时不一致)
5.堆 --- JVM中所有对象的内存空间 分为: Young Gen, Old Gen
Young Gen 又分为:Eden区和两个大小相同的Survivor区(from 和 to)
Eden和Survivor默认比例 8:1 由 -XX:SurvivorRation设置
堆大小 -Xmx -Xms 设置
Young Gen -Xmn 设置
-XX:NewSize和-XX:MaxNewSize
用于设置年轻代的大小,建议设为整个堆大小的1/3或者1/4,两个值设为一样大。
Minor GC --- 发生在新生代的垃圾回收,Java 对象大多都具备朝生夕灭的特性,所以 Minor GC 非常频繁,一般回收速度也比较快,
年轻代的GC使用复制算法(将内存分为两块,每次只用其中一块,当这块内存用完,就将还活着的对象复制到另外一块上面,复制算法不会产生内存碎片)
Full GC --- 发生在年老代的GC, Full GC比较慢,尽量避免
新创建对象都会被分配到Eden区(一些大对象特殊处理),当Eden区满则进行Minor GC,
这些对象经过第一次Minor GC后,如果仍然存活,将会被移到Survivor区, 对象在Survivor区中每熬过一次Minor GC,年龄增加1岁,
当年龄增加到一定程度后(默认15岁),会被移动到年老代中,
当年老代满时,经常Full GC
线程Stack 每个线程独有的操作数栈局部变量表方法入口 -Xss 设置
方法区 -XX:PermSize和-XX:MaxPermSize设置
6.JVM class类加载加载机制 --- JVM把描述类的数据从class加载到内存,并对数据进行校验,转化解析和初始化,最终得到可被虚拟机直接使用的Java类型
类装载器: 寻找类的字节码文件, 并构造出类在JVM内部表示的对象组件
JVM类加载器把一个类装入JVM过程:
(1) 装载:查找和导入Class文件;
(2) 链接:把类的二进制数据合并到JRE中;
(a)校验:检查载入Class文件数据的正确性,确保被加载类信息符合JVM规范;
(b)准备:给类的静态变量分配存储空间,并将其初始化为默认值;
(c)解析:将虚拟机常量池中符号引用转成直接引用;
(3) 初始化:对类的静态变量,静态代码块执行初始化操作,为类的静态变量赋初始值
说明:
1.类加载的双亲委托机制
某个特定的类加载器在接到加载类的请求时,首先将加载任务交给父类加载器,父类加载器又将加载任务向上委托,直到最高层的父类加载器,
如果最高层的父类加载器可以完成类加载任务,就成功返回,否则向下传递加载任务,由其子类加载器进行加载
2.初始化步骤
1.如果类没有被加载和链接,则先进行加载和链接
2.如果类存在直接父类,并且父类未被初始化(注意:在一个类加载器中,类只能初始化一次),那就初始化直接的父类
3.如果存在static块,一次执行这些初始化语句
4.static块,会在类第一次被使用时执行(调用类的静态变量,初始化对象,Class.forName等)
7.正则表达式总计:
^ 和$表示以字符串开始和以字符串结尾。例:^abc 表示必须以abc开始(如:abcd,abcefd),abc$ 表示必须以abc结尾(如:);^abc$ 只能是abc(abc是个整体,abcabc不匹配) ;abc 表示包含abc的字符串
* 和 + 和 ? 分别表示出现0次或多次,1次或多次,0次或1次。例:abc*表示有0个或多个abc,其他两个同理
上面的*+?完全可以用范围代替,abc{2}表示ab后面有至少两个c,如abcc,dfdabccccc都是符合的;abc{2}$ 只有以abcc结尾的符合,如343abcc
abc{1,2} 表示ab后面跟着1或2个c;
abc{3,} 表示ab后面跟着至少3个c; {,3}这种是不正确的
| 或运算 ab|cd 表示字符串里有ab或者cd;
. 可以替换任意字符
下面是几种是需要记住的
"[ab]":表示一个字符串有一个"a"或"b"(相当于"a|b");
"[a-d]":表示一个字符串包含小写的'a'到'd'中的一个(相当于"a|b|c|d"或者"[abcd]");
"^[a-zA-Z]":表示一个以字母开头的字符串;
"[0-9]%":表示一个百分号前有一位的数字;
",[a-zA-Z0-9]$":表示一个字符串以一个逗号后面跟着一个字母或数字结束。
下面看看具体的实例,比如我今天做的:一个输入框,可以输入数字,也可以输入多个数字用逗号隔开,或者两个数字用~分隔。
我写的正则表达式 : ((^[0-9]+[~]?)?|^([0-9]+[,])+)[0-9]+$
8.JVM中的GC垃圾回收和内存分配策略 ---- JVM高级 参考:
http://www.cnblogs.com/whgw/archive/2011/09/29/2194997.html
http://www.cnblogs.com/zhguang/tag/Java/
http://blog.csdn.net/column/details/javavirtualmachine.html
http://blog.csdn.net/eric_sunah/article/details/7870906
1.判断回收对象
1.引用计数算法
给对象中添加一个引用计数器,每当有一个地方引用它,计数器值+1,当引用失效,计数器值-1,任何时刻计数器为0的对象就不可能再被使用
缺点:
无法解决对象的循环依赖问题!!!
2.可达性算法 --- JVM使用的GC判断算法
通过一系列称为 GC Roots 的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为 引用链,
当一个对象到 GC Roots 没有任何引用链相连时(即:GC Roots到这个对象不可达),则此对象是不可用的
Java中可作为 GC Roots的对象包括以下几种:
1.虚拟机栈(栈帧的本地变量表)中引用的对象
2.方法区中类静态属性引用的对象
3.方法区中常量引用的对象
4.本地方法栈中JNI(Native方法)引用的对象
2.JVM的垃圾回收机制
1.几个概念:
内存分代 新生代(Eden + 2 Survivor )、老年代、永久代
新创建的对象分配在Eden区,经历一次Minor GC后被移到 Survivor1区,再经历一次Minor GC被移到Survivor2区,直到升至老年代
大对象(长字符串或大数组)可能直接存放到老年代
对象创建都在堆上,类信息、常量、静态变量存储在方法区,堆和方法区是线性共享的
GC由守护线程执行,在从内存回收一个对象之前,会调用对象的finalize()方法
GC的触发由JVM堆内存大小决定,System.gc()和Runtime.gc()会向JVM发送执行GC的请求,但JVM不保证一定会执行GC
堆中没有内存创建新对象时,会抛出 OutOfMemoryError
2.GC回收什么对象?
可达性算法 --- 根搜索法
--- 通过一系列称为GC Roots的对象作为起始点,从GC Roots开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,证明此对象不可用,可以被回收了
程序把所有的引用关系看做一张图,从一个节点 GC Roots开始,寻找对应的引用节点,找到这个节点后,继续寻找这个节点的引用节点,当所有的引用节点寻找完毕后,剩余的节点被认为是没有被引用的节点,可被回收
--- 可被作为GC Roots的对象
1.虚拟机栈中引用的对象(本地变量表)
2.方法静态属性引用的对象
3.方法区中常量引用的对象
4.本地方法栈用引用的对象(Native对象)
引用计数法
--- 可能有循环依赖问题,导致计数器不为0,对象永远无法被回收
多次扫描,而都不可达的对象,经历几次扫描后会被回收
3.垃圾回收算法: 参考:http://www.cnblogs.com/sunniest/p/4575144.html
任何垃圾回收算法,只做2件基本事情:
1.发现无用可被回收对象 (可达性算法)
2.回收被无用对象占用的空间,使得该空间可被再次使用
1.标记-清除算法:
从GC Roots根集合进行扫描,对存活的对象进行标记,标记完成后,在扫描整个空间中未被标记的对象,进行回收
优点:不必移动对象,在存活对象比较多的情况下极为高效
缺点:GC时会暂停整个应用,容易造成不连续的内存碎片
2.复制算法:
为了解决内存碎片问题而产生的一种算法。它的整个过程可以描述为:标记所有的存活对象;通过重新调整存活对象位置来缩并对象图;更新指向被移动了位置的对象的指针
将内存划分为大小相等的2块,每次只是用其中一块,当一块用完后,将还存活的对象复制到另一块内存上,然后把已使用过的内存空间一次清理掉,
这样使得每次都是对其中的一块进行内存回收,不会产生碎片等情况,只要移动堆订的指针,按顺序分配内存即可,实现简单,运行高效
优点:不会造成内存碎片
缺点:内存缩小为原来的一半,需要移动对象
3.标记-整理算法:
先进行 标记-清除算法,然后在清理完无用对象后,将所有存活的对象移动到一端,并更新引用其对象的指针
4.分代算法:
基于对象生命周期分析得出的垃圾回收算法。把对象分为年轻代、年老代、持久代,对不同的生命周期使用不同的算法
年轻代: --- Minor GC, 对象生命周期短 使用: Serial、PraNew、Parallel Scavenge 收集器
1.所有新生成对象,目标是尽快收集掉生命周期短的对象
2.新生代内存按照8:1:1的比例分为一个eden区和两个survivor(survivor0,survivor1)区。一个Eden区,两个 Survivor区(一般而言)。大部分对象在Eden区中生成。回收时先将eden区存活对象复制到一个survivor0区,然后清空eden区,当这个survivor0区也存放满了时,则将eden区和survivor0区存活对象复制到另一个survivor1区,然后清空eden和这个survivor0区,此时survivor0区是空的,然后将survivor0区和survivor1区交换,即保持survivor1区为空, 如此往复。
Eden : Survivor0:Survivor1 = 8:1:1
3.当survivor1区不足以存放 eden和survivor0的存活对象时,就将存活对象直接存放到老年代。若是老年代也满了就会触发一次Full GC,也就是新生代、老年代都进行回收
4.新生代发生的GC也叫做Minor GC,MinorGC发生频率比较高(不一定等Eden区满了才触发)
老年代: --- Full GC 对象生命周期长 使用:Serial Old、Parallel Old、CMS收集器
1.在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。
2.内存比新生代也大很多(大概比例是1:2),当老年代内存满时触发Major GC即Full GC,Full GC发生频率比较低,老年代对象存活时间比较长,存活率标记高。
永久代: --- 用于存放静态文件,如Java类、方法等。持久代对垃圾回收没有显著影响
4.GC实现 --- 垃圾收集器
Serial --- 复制算法
新生代单线程收集器,标记和清理都是单线程,优点:简单高效
Serial Old --- 标记-整理算法
老年代单线程收集器
ParNew --- 停止复制算法
Serial收集器的多线程版本,在多核CPU环境下有着比Serial更好的表现
Parallel Scavenge ---
并行收集器,追求高吞吐量,高效利用CPU。吞吐量一般为99%, 吞吐量= 用户线程时间/(用户线程时间+GC线程时间)。适合后台应用等对交互相应要求不高的场景
Parallel Old收集器(停止-复制算法)
Parallel Scavenge收集器的老年代版本,并行收集器,吞吐量优先
CMS --- 标记清理算法
高并发、低停顿,追求最短GC回收停顿时间,cpu占用比较高,响应时间快,停顿时间短,多核cpu 追求高响应时间的选择
5.GC执行机制
Minor GC: 新对象在年轻代的Eden区申请内存失败时,触发Minor GC --- 年轻代
--- 只对年轻代的Eden去进行,不影响老年代
--- 频繁进行
--- 选用速度快、效率高的算法,使Eden尽快空闲
Full GC: 对整个堆进行整理,包括 年轻代、老年代、永久代
--- 对整个堆进行回收,速度比 Minor GC 慢
--- 尽量减少 Full GC次数
--- 对象JVM调优过程,大部分是针对Full GC的调节
可能导致Full GC的原因:
1.老年代满
2.持久代满
3.System.gc()被显示调用
4.上一次GC之后Heap的各域分配策略动态变化
6.容易出现泄露的地方:
1.静态集合类(HashMap、Vector),这些静态变量生命周期和应用程序一样,所有的对象也不能被释放
2.各种连接、数据了连接、网络连接、IO连接没被显示调用close(),不被GC回收导致内存泄露
3.监听器,在释放对象的同时没有相应删除监听器时可能导致内存泄露
9.JVM内存模型 http://www.infoq.com/cn/articles/java-memory-model-1
内存模型:描述了程序中各个变量(实例域、静态域和数组元素)直接的关系,以及在实际计算机系统中将变量存储到内存、从内存中取出变量这样的底层细节
JVM中存在一个主存,Java中所有对象成员变量都存储在主存中,对于所有线程是共享的,每条线程都有自己的工作内存,
工作内存中保存的是主存中某些对象成员变量的拷贝,线程对所有成员变量的操作都是在工作内存中进行,然后同步到主存,线程之间无法相互直接访问,变量传递都需要通过主存
JMM(Java内存模型,Java Memory Model简称)是控制Java线程之间、线程和主存之间通信的协议。
JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存中,每个线程都有一个私有的本地内存(local memory),
本地内存中存储了该线程以读/写共享变量的副本,线程在本地私有内存中操作完后,要将数据同步到主内存
Java内存模型中规定了:
所有变量都存储在主内存中,每个线程都有自己的工作内存,线程的工作内存中使用到的变量,是主内存的副本拷贝,
线程对变量的所有操作(读取、赋值)都必须在工作内存中进行,而不能直接读写主内存中的变量,操作完后,要将数据同步到主内存
不同的线程之间工作内存是独立的,线程间变量值的传递均需要在主内存来完成
特别注意:
(根据Java虚拟机规范的规定,volatile变量依然有共享内存的拷贝,但是由于它特殊的操作顺序性规定——
从工作内存中读写数据前,必须先将主内存中的数据同步到工作内存中,所有看起来如同直接在主内存中读写访问一般,因此这里的描述对于volatile也不例外)。
不同线程之间也无法直接访问对方工作内存中的变量,线程间变量值得传递均需要通过主内存来完成
8种内存间的交互操作:
Java内存模型定义了8种操作来完成主内存与工作内存之间交互的实现细节
1.lock(锁定)
作用于主内存的变量,它把一个变量标识为一条线程独占的状态
2.unlock(解锁)
作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
3.read(读取)
作用于主内存的变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用
4.load(载入)
作用于主内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中
5.use(使用)
作用于工作内存的变量,它把工作内存中的一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值得字节码指令时,将会执行这个操作
6.assign(赋值)
作用于工作内存的变量,它把一个从执行引擎接收到的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作
7.store(存储)
作用于工作内存的变量,它把工作内存中的一个变量的值传递到主内存中,一般随后的write操作使用
8.write(写入)
作用于主内存的变量,它把store操作从工作内存中得到的变量值放入主内存的变量中
8种基本操作必须满足的规则:
1.不允许read和load、store和write操作之一单独出现,以上两个操作必须按顺序执行,但没有保证必须连续执行(即:read和load直接、store和write之间是可插入其他指令的)
2.不允许一个线程丢弃它的最近的assign操作,即:变量在工作内存中改变了之后,必须把该变化同步回主内存
3.不允许一个线程无原因地(没有发生过任何assign操作)把数据从线程的工作内存同步回主内存中
4.一个新的变量只能从主内存中"诞生",不允许在工作内存中直接使用一个未被初始化(load或assign)的变量,
即:对一个变量实施use和store操作之前,必须先执行过了assign河load操作
5.一个变量在同一时刻只允许一条线程对其执行lock操作,但lock操作可以被同一个线程重复执行(可重入锁),
多次执行lock后,只有执行相同次数的unlock操作,变量才会被解锁
6.如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前,需要重新执行load或assign操作初始化变量的值
7.如果一个变量实现没有被lock操作锁定,则不允许对它执行unlock操作,也不允许去unlock一个被其他线程锁定的变量
8.对一个变量执行unlock操作之前,必须先把此变量同步回主内存(执行store和write操作)
volatile变量的特殊规则: --- 不能保证原子性
1.保证此变量对所有线程的可见性
2.禁止指令重排序优化
内存模型有哪些规则?
原子性、可见性、可排序性
10.Java反射与动态代理
Java反射,可以知道Java类的内部构造,就可以与它进行交互,包括创建新的对象和调用对象中的方法等。
这种交互方式与直接在源代码中使用的效果是相同的,但是又额外提供了运行时刻的灵活性。使用反射的一个最大的弊端是性能比较差
每个类被加载进内存后,系统就会为该类生成一个对应的 java.lang.Class对象,通过该Class对象即可访问到JVM中的这个类
加载完类之后,在堆内存中会产生一个Class类型的对象(一个类只有一个Class对象),这个对象包含了完整的类结构信息,这个Class对象就像一面镜子,可以看到类的结构
程序在运行状态中,可以动态加载一个只有名称的类(全路径),对于任意一个已经加载的类,都能够知道这个类的所有属性和方法,
对于任意一个对象,都能调用它的任意一个方法和属性
用法:
作用1:获取类的内部结构
Java反射API可以获取程序在运行时刻的内部结构,只需短短十几行代码,就可遍历出一个Java类的内部结构,包括:构造方法、声明的域和定义的方法等
java.lang.Class类的对象,可以通过其中的方法来获取到该类中的构造方法、域和方法(getConstructor、getFiled、getMethod)
这三个方法还有相应的getDeclaredXXX版本,区别在于getDeclaredXXX版本的方法只会获取该类自身所声明的元素,而不会考虑继承下来的。
Constructor、Field和Method这三个类分别表示类中的构造方法、域和方法。这些类中的方法可以获取到所对应结构的元数据。
作用2:运行时刻,对一个Java对象进行操作
动态创建一个Java类的对象,获取某个属性的值和调用某个方法
在Java源码中编写的对类和对象的操作,都可以在运行时刻通过反射API来实现
特别说明:
clazz.setAccessible(true) --- 可以获取到类中的private属性和private方法
Class对象的获取
1.对象的 getClass()方法
2.类.class属性 --- 最安全,性能最好
3.Class.forName(String classFullName) 动态加载类 --- classFullName是类的全限定名(包.类名) --- 最常用
从Class中获取信息
1.获取信息
构造器、包含方法、包含属性、包含的Annotation、实现的接口,所在包,类名,简称,修饰符等
获取内容 方法签名
构造器 Constructor
包含的方法 Method getMethod(String name, Class... parameterTypes)
包含的属性 Field getField(String name)
包含的Annotation A getAnnotation(Class annotationClass)
内部类 Class[] getDeclaredClasses()
外部类 Class getDeclaringClass()
所实现的接口 Class[] getInterfaces()
修饰符 int getModifiers()
所在包 Package getPackage()
类名 String getName()
简称 String getSimpleName()
2.判断信息
注解类型、是否是了指定注解、是否是数组、枚举、接口等
判断内容 方法签名
注解类型? boolean isAnnotation()
使用了该Annotation修饰? boolean isAnnotationPresent(Class annotationClass)
匿名类? boolean isAnonymousClass()
数组? boolean isArray()
枚举? boolean isEnum()
原始类型? boolean isPrimitive()
接口? boolean isInterface()
obj是否是该Class的实例 boolean isInstance(Object obj)
反射生成并操作对象
通过Method对象执行相应的方法
通过Constructor对象调用对应的构造器来创建实例
通过Field对象直接访问和修改对象的成员变量值
反射创建对象
2种方式:
1.使用Class对象的newInstance()创建该Class对象对应类的实例(要求改Class对应类有默认构造器)
2.先使用Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法创建该Class对象对应类的实例
--- 该方式,可选择指定的构造器来创建实例
1.Spring根据配置文件信息(类的全限定名),使用反射创建对象 ---- 方式一,默认构造器创建实例
eg:
模拟Spring,实现一个对象池,对象池根据文件读取key-value对,然后创建这些对象,并放入Map中,
对象池可以将id作为key,将对象实例作为value,可以通过id获取对象实例
即:
ObjectPool pool = ObjectPool.init("config.json");
User user = (User) pool.getObject("id1");
System.out.println(user);
Bean bean = (Bean) pool.getObject("id2");
System.out.println(bean);
参考:
http://blog.csdn.net/zjf280441589/article/details/50453776
配置文件
{
"objects": [
{
"id": "id1",
"class": "com.fq.domain.User"
},
{
"id": "id2",
"class": "com.fq.domain.Bean"
}
]
}
ObjectPool对象池
public class ObjectPool {
private Map
private ObjectPool(Map
this.pool = pool;
}
private static Object getInstance(String className) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
return Class.forName(className).newInstance();
}
private static JSONArray getObjects(String config) throws IOException {
Reader reader = new InputStreamReader(ClassLoader.getSystemResourceAsStream(config));
return JSONObject.parseObject(CharStreams.toString(reader)).getJSONArray("objects");
}
// 根据指定的JSON配置文件来初始化对象池
public static ObjectPool init(String config) {
try {
JSONArray objects = getObjects(config);
ObjectPool pool = new ObjectPool(new HashMap
if (objects != null && objects.size() != 0) {
for (int i = 0; i < objects.size(); ++i) {
JSONObject object = objects.getJSONObject(i);
if (object == null || object.size() == 0) {
continue;
}
String id = object.getString("id");
String className = object.getString("class");
pool.putObject(id, getInstance(className));
}
}
return pool;
} catch (IOException | ClassNotFoundException | InstantiationException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
public Object getObject(String id) {
return pool.get(id);
}
public void putObject(String id, Object object) {
pool.put(id, object);
}
public void clear() {
pool.clear();
}
}
实例类User和Bean
public class User {
private int id;
private String name;
private String password;
}
public class Bean {
private Boolean usefull;
private BigDecimal rate;
private String name;
}
反射调用方法
获取到某个类对应的Class对象后,可通过该Class对象的getMethod()来获取一个Method数组或Method对象,
每个Method对象对应一个方法,在获取Method对象后,可通过调用invoke()方法调用该Method对象对应的方法
eg:
通过动态调用对象方法 + 配置文件,来给对象设置值 --- 根据属性创建对象(调用setter方法,可设置属性和依赖的对象)
1.json格式的配置文件,用来定义对象、属性值及其依赖关系 --- config.json
注意:
其中fields代表该Bean所包含的属性, name为属性名称, value为属性值(属性类型为JSON支持的类型),
ref代表引用一个对象(也就是属性类型为Object,但是一定要引用一个已经存在了的对象)
这里定义了一个User对象,设置其3个属性
定义一个ComplexBean对象,设置其name属性为complex-bean-name,并设置其引用的对象是id2
{
"objects": [
{
"id": "id1",
"class": "com.fq.domain.User",
"fields": [
{
"name": "id",
"value": 101
},
{
"name": "name",
"value": "feiqing"
},
{
"name": "password",
"value": "ICy5YqxZB1uWSwcVLSNLcA=="
}
]
},
{
"id": "id2",
"class": "com.fq.domain.Bean",
"fields": [
{
"name": "usefull",
"value": true
},
{
"name": "rate",
"value": 3.14
},
{
"name": "name",
"value": "bean-name"
}
]
},
{
"id": "id3",
"class": "com.fq.domain.ComplexBean",
"fields": [
{
"name": "name",
"value": "complex-bean-name"
},
{
"name": "refBean",
"ref": "id2"
}
]
}
]
}
2.定义对象池,用于创建对象,并调用其方法给对象赋值
參考:反射.ObjectPool 的实现
package com.jay.advanced.java.反射;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.google.common.io.CharStreams;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 模拟Spring,创建一个对象池
* Created by hetiewei on 2017/2/17.
*/
public class ObjectPool {
private Map
private ObjectPool(Map
this.pool = pool;
}
//从指定文件,读取配置信息,返回解析后json数组
private static JSONArray getObjects(String config) throws IOException {
//获取输入流
Reader reader = new InputStreamReader(ClassLoader.getSystemResourceAsStream(config));
//读取输入流内容,变成json数组返回
return JSONObject.parseObject(CharStreams.toString(reader)).getJSONArray("objects");
}
//根据类名,获取类的对象实例
private static Object getIntance(String className, JSONArray fields) throws ClassNotFoundException,
IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
//配置的Class
Class clazz = Class.forName(className);
//目标Class的实例对象
Object targetObject = clazz.newInstance();
//遍历属性,赋值给实例对象 --- 注意区分,直接赋值,还是赋值引用对象
if (fields != null && fields.size() != 0) {
for (int i = 0; i < fields.size(); i++) {
JSONObject field = fields.getJSONObject(i);
//需要设置的成员变量名
String filedName = field.getString("name");
//需要设置的成员变量的值
Object fieldValue;
//如果是8种基本类型或String类型,直接获取value,得到属性赋值
if (field.containsKey("value")) {
fieldValue = field.get("value");
} else if (field.containsKey("ref")) {
//如果是引用类型, 先获得引用对象的id,然后根据id,从对象池中得到引用对象
String refBeanId = field.getString("ref");
fieldValue = OBJECTPOOL.getObject(refBeanId);
} else {
throw new RuntimeException("没有value 或 ref 引用");
}
//构造setterXxx
String setterName = "set" + filedName.substring(0, 1).toUpperCase() + filedName.substring(1);
//需要设置成员变量的setter方法
Method setterMethod = clazz.getMethod(setterName, fieldValue.getClass());
//调用setter方法设置值
setterMethod.invoke(targetObject, fieldValue);
}
}
return targetObject;
}
private static ObjectPool OBJECTPOOL;
//创建一个对象池实例(保存多线程安全)
private static void initSingletonPool() {
if (OBJECTPOOL == null) {
synchronized (ObjectPool.class) {
if (OBJECTPOOL == null) {
OBJECTPOOL = new ObjectPool(new ConcurrentHashMap
}
}
}
}
//指定根据的JSON配置文件来初始化对象池
public static ObjectPool init(String config) {
//初始化pool
initSingletonPool();
//解析Json配置文件
try {
JSONArray objects = getObjects(config);
for (int i = 0; i < objects.size(); i++) {
JSONObject object = objects.getJSONObject(i);
if (object == null || object.size() == 0) {
continue;
}
String id = object.getString("id");
String className = object.getString("class");
//初始化Bean,并放入对象池中
OBJECTPOOL.putObject(id, getIntance(className, object.getJSONArray("fields")));
}
return OBJECTPOOL;
} catch (IOException | ClassNotFoundException |
InstantiationException | IllegalAccessException |
NoSuchMethodException | InvocationTargetException e) {
throw new RuntimeException(e);
}
}
public void putObject(String id, Object obj) {
pool.put(id, obj);
}
public Object getObject(String id) {
return pool.get(id);
}
public void clear() {
pool.clear();
}
}
3.客户端使用对象池
//初始化对象池
ObjectPool pool = ObjectPool.init("config.json");
//从对象池中根据id获取对象实例
User user = (User) pool.getObject("id1");
Bean bean = (Bean) pool.getObject("id2");
ComplexBean complexBean = (ComplexBean) pool.getObject("id3");
4.ComplexBean类
public class ComplexBean {
private String name;
private Bean refBean;
}
反射访问并操作成员变量
通过Class对象的getField()方法可获取该类所包含的全部或指定的成员变量Field,
getDeclaredXxx方法可以获取所有的成员变量,无论private/public;
Field提供了读取和设置成员变量值的方法
getXxx(Object obj)
获取obj对象的该成员变量的值,此处的Xxx对应8中基本类型,如果该成员变量的类型是引用类型, 则取消get后面的Xxx;
setXxx(Object obj, Xxx val)
将obj对象的该成员变量值设置成val值.此处的Xxx对应8种基本类型, 如果该成员类型是引用类型, 则取消set后面的Xxx;
eg:
通过反射,设置成员变量值
User user = new User();
//反射获取对象的指定属性
Field idField = User.class.getDeclaredFiled("id");
//设置该属性即使为private也可被访问
idField.setAccessible(true);
//将对象的常用变量,设置为指定值 --- 这里将user对象的id属性,设置为30
idField.setInt(user, 30);
private void setAccessible(AccessibleObject object) {
object.setAccessible(true);
}
反射获取注解信息
只需要获取到Class Method Filed等这些实现了AnnotatedElement接口的类实例, 就可以获取到我们想要的注解信息
eg:
获取client()方法上的注解信息
Annotation[] annotations = this.getClass().getMethod("client").getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation.annotationType().getName());
}
eg:
获取某个注解中的元数据,需要强转成所需的注解类型,然通过注解对象的抽象方法来访问这些元数据
@Tag(name = "client")
public class Client {
@Test
public void client() throws NoSuchMethodException {
Annotation[] annotations = this.getClass().getAnnotations();
for (Annotation annotation : annotations) {
if (annotation instanceof Tag) {
Tag tag = (Tag) annotation;
System.out.println("name: " + tag.name());
System.out.println("description: " + tag.description());
}
}
}
}
反射获取泛型信息
为了通过反射操作泛型,Java新增了4种类型来代表不能归一到Class了下,但又和原始类型同样重要的类型
类型 含义
ParameterizedType 一种参数化类型, 比如Collection
GenericArrayType 一种元素类型是参数化类型或者类型变量的数组类型
TypeVariable 各种类型变量的公共接口
WildcardType 一种通配符类型表达式, 如? ? extends Number ? super Integer
动态代理:
代理模式:
代理对象和被代理对象一般实现相同的接口,调用者与代理对象进行交互。代理的存在对于调用者来说是透明的,调用者看到的只是接口。
代理对象则可以封装一些内部的处理逻辑,如访问控制、远程通信、日志、缓存等。比如一个对象访问代理就可以在普通的访问机制之上添加缓存的支持,
传统的代理模式的实现,需要在源代码中添加一些附加的类。这些类一般是手写或是通过工具来自动生成
动态代理:
JDK5引入了动态代理机制,允许开发人员在运行时刻动态的创建出代理类及其对象。
在运行时刻,可以动态创建出一个实现了多个接口的代理类,每个代理类的对象都会关联一个表示内部处理逻辑的InvocationHandler接口的实现,
当使用者调用代理对象所代理的接口中的方法时,这个调用信息被传递个InvocationHandler的invoke()方法,
在invoke()方法参数中可以获取到代理对象、方法对应的Method对象和调用的实际参数,invoke()方法的返回值被返回给使用者。
相当于对方法调用进行了拦截 --- 这是一个不需要依赖AspectJ等AOP框架的一种AOP实现方式
11.常用的内存调节参数
-Xms 初始堆大小,默认是物理内存1/64(<1G)
-Xmx 最大堆大小
-Xmn 新生代的内存空间大小(eden+ 2 survivor space),
整个堆大小=新生代大小 + 老生代大小 + 永久代大小
在保证堆大小不变的情况下,增大新生代后,将会减小老生代大小。此值对系统性能影响较大,Sun官方推荐新生代配置为整个堆的3/8
-XX:SurvivorRation 新生代Eden区和Survivor区容量比,默认是8, 两个Survivor区与一个Eden区的比值为2:8,一个Survivor区占整个年轻代的1/10
-Xss 每个线程的堆栈大小, 推荐 256K
-XX:PermSize 持久代(非堆内存)初始值, 默认是物理内存 1/64
-XX:MaxPermSize 持久代(非堆内存)最大值,默认物理内存1/4
-XX:+UseParallelGC 多核处理器,配置后提示GC效率
eg:
-vmargs -Xms128M -Xmx512M -XX:PermSize=64M -XX:MaxPermSize=256M
-vmargs 说明后面是VM的参数,所以后面的其实都是JVM的参数了
-Xms128m JVM初始分配的堆内存
-Xmx512m JVM最大允许分配的堆内存,按需分配
-XX:PermSize=64M JVM初始分配的非堆内存
-XX:MaxPermSize=128M JVM最大允许分配的非堆内存,按需分配
说明:
Java 虚拟机具有一个堆,堆是运行时数据区域,所有类实例和数组的内存均从此处分配。堆是在 Java 虚拟机启动时创建的。”“在JVM中堆之外的内存称为非堆内存(Non-heap memory)”。
JVM主要管理两种类型的内存:堆和非堆。简单来说堆就是Java代码可及的内存,是留给开发人员使用的;非堆就是JVM留给自己用的,
方法区,JVM内存处理货优化所需的内存、每个类结构(如运行时常数池、字段和方法数据)以及方法和构造方法的代码都在非堆内存中
内存分配方法:
1.堆上分配
2.栈上分配
12.JVM内存管理
程序计数器
方法区
堆
栈(JVM栈 + 本地方法栈)
说明:
GC主要发生在堆上,方法区也会发生GC, 栈与寄存器是线程私有的,不会GC
方法区:
存放内容:类信息、类的static属性、方法、常量池
已经加载的类的信息(名称、修饰符等)
类中的static变量
类中的field信息
类中定义为final常量
类中的方法信息
运行时常量池:编译器生成的各种字面量和符号引用(编译期)存储在class文件的常量池中,这部分内容会在类加载之后进入运行时常量池
使用实例: 反射
在程序中通过Class对象调用getName等方法获取信息数据时,这些信息数据来自方法区
调节参数:
-XX:PermSize 指定方法区最小值,默认16M
-XX:MaxPermSize 指定方法区最大值,默认64M
所抛异常:
方法区使用内存超过最大值,抛出 OutOfMemoryError
GC操作:
对类的卸载
针对常量池的回收
总结:
企业开发中, -XX:PermSize==-XX:MaxPermSize,都设置为256M
eg:
类中的static变量会在方法区分配内存,但是类中的实例变量不会(类中的实例变量会随着对象实例的创建一起分配在堆中,当然若是基本数据类型的话,会随着对象的创建直接压入操作数栈)
关于方法区的存放内容,可以这样去想所有的通过Class对象可以反射获取的都是从方法区获取的(包括Class对象也是方法区的,Class是该类下所有其他信息的访问入口)
堆:
存放内容:
对象实例(类中的实例变量会随着对象实例的创建一起分配在堆中,当然若是基本数据类型的话,会随着对象的创建直接压入操作数栈)
数组值
使用实例:
new创建的对象都在这里分配
调节参数:
-Xmx:最大堆内存,默认为物理内存的1/4但小于1G
-Xms:最小堆内存,默认为物理内存的1/64但小于1G
-XX:MinHeapFreeRatio,默认当空余堆内存小于最小堆内存的40%时,堆内存增大到-Xmx
-XX:MaxHeapFreeRatio,当空余堆内存大于最大堆内存的70%时,堆内存减小到-Xms
注意:
在实际使用中,-Xmx与-Xms配置成相等的,这样,堆内存就不会频繁的进行调整了!!!
所抛错误:
OutOfMemoryError:Java heap space
堆内存划分:
新生代:
Eden + from + to from 与 to 大小相等
-Xmn:整个新生代大小
-XX:SurvivorRation : 调整Eden:from(to)的比例,默认是 8:1 即: eden:from:to = 8:1:1
老年代:
新建对象直接分配到老年代的2种情况:
大对象:-XX:PretenureSizeThreshold(单位:字节)参数来指定大对象的标准
大数组:数据中的元素没有引用任何外部的对象
总结:
企业开发中 -Xmx==-Xms
eg:
可以看到,-Xms==-Xmx==2048m,年轻代大小-Xmn==512m,这样,年老代大小就是2048-512==1536m,这个比率值得记住,
在企业开发中,年轻代:年老代==1:3,而此时,我们配置的-XX:MaxTenuringThreshold=15(这也是默认值),年轻代对象经过15次的复制后进入到年老代
年轻代分为Eden和2个Survivor(from+to),默认 Eden:from:to==8:1:1
1.新产生的对象有效分配在Eden区(除非配置了-XX:PretenureSizeThreshold,大于该值的对象会直接进入年老代)
2.当Eden区满了或放不下时,其中存活的对象会复制到from区(注意:如果存活下来的对象from区放不下,则这些存活下来的对象全部进入老年代),之后Eden区的内存全部回收掉
注意:如果Eden区没有满,但来了一个小对象Eden区放不下,这时候Eden区存活对象复制到from区后,清空Eden区,之后刚才的小对象再进入Eden区
3.之后产生的对象继续分配在Eden区,当Eden区满时,会把Eden区和from区存活下来的对象复制到to(同理,如果存活下来的对象to区都放不下,则这些存活下来的对象全部进入年老代),之后回收掉Eden区和from区的所有内存;
4)如上这样,会有很多对象会被复制很多次(每复制一次,对象的年龄就+1),默认情况下,当对象被复制了15次(这个次数可以通过:-XX:MaxTenuringThreshold来配置),就会进入年老代了
5)当年老代满了或者存放不下将要进入年老代的存活对象的时候,就会发生一次Full GC(这个是我们最需要减少的,因为耗时很严重)
栈:
注意点:
每个线程都会分配一个栈,每个栈中有多个栈帧(每个方法对应一个栈帧) 每个方法在执行的同时都会创建一个栈帧,每个栈帧用于存储当前方法的局部变量表、操作数栈等,
每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程,说的更明白一点,就是方法执行时创建栈帧,方法结束时释放栈帧所占内存
存放内容:
局部变量表: 8种基本数据类型、对象引用, 该空间在编译期已经分配好,运行期不变
参数调节:
-Xss:设置栈大小,通常设置为1m就好
eg:
所抛异常:
StackOverFlowError 线程请求的栈深度大于虚拟机所允许的深度
eg:
没有终止调节的递归(递归基于栈)
每个方法的栈深度在javac编译之后就已经确定了
OutOfMemoryError: 虚拟机栈可以动态扩展,如果扩展时无法申请到足够内存,则抛该异常
注意:
栈可以动态扩展,但栈中的局部变量表不可以
C寄存器(程序计数器)
概念: 当前线程所执行的字节码的行号指示器,用于字节码解释器对字节码指令的执行。
多线程:通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个时刻,一个处理器(也就是一个核)只能执行一条线程中的指令,
为了线程切换后能恢复到正确的执行位置,每条线程都要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储。
内存分配概念:
在类加载完成后,一个对象所需的内存大小就可以完全确定了,具体的情况查看对象的内存布局。
为对象分配空间,即把一块儿确定大小(上述确定下来的对象内存大小)的内存从Java堆中划分出来
Java的对象内存布局 3 大块
对象头
存储对象自身的运行时数据:Mark Word(在32位和64位JVM上长度分别为32bit和64bit),包含信息如下:
对象hashCode
对象GC分代年龄
锁状态标识
线程持有的锁
偏向锁相关:偏向锁、自旋锁、轻量级锁以及其他的一些锁优化策略是JDK1.6加入的,这些优化使得Synchronized的性能与ReentrantLock的性能持平,
在Synchronized可以满足要求的情况下,优先使用Synchronized,除非是使用一些ReentrantLock独有的功能,例如指定时间等待等。
类型指针: 对象指向类元数据的指针
JVM通过这个指针来确定这个对象是哪个类的实例(根据对象确定其Class的指针)
实例数据
对象真正存储的有效信息
对齐填充
JVM要求对象的大小必须是8的整数倍,若不够,需要补位对齐
注意:
1.Mark Word是非固定的数据结构,以便在极小空间内存储尽量多的信息
2.如果对象是一个数组,对象头必须有一块用来记录数组长度的数据,JVM可以通过Java对象的元数据确定对象长度,但对于数组不行
3.基本数据类型和对象包装类所在的内存大小(字节)
boolean 1字节
byte 1字节
short 2字节
char 2字节
int 4字节
float 4字节
long 8字节
double 8字节
引用类型 在32位和64位系统上长度分别为4bit和8bit
内存分配的 2 种方式:
1.指针碰撞
适用场合:
堆内存规整(即:没有内存碎片,有大块完整内存)的情况下
原理:
用过的内存全部整合到一边,没用过的内存放在另一边,中间有个分界值指针,只需要向着没有用过的内存方向将该指针移动新创建的对象内存大小位置即可
GC收集器:
Serial、ParNew
2.空闲列表
适用场合:
堆内存不规整情况
原理:
JVM虚拟机会维护一个列表,该列表会记录哪些内存块是可用的,在分配时,找一块足够大的内存来划分给new的对象实例,最后更新列表记录
GC收集器:
CMS
注意:
1.2种内存分配方式,取决于Java堆内存是否规整
2.Java堆内存是否规整,取决于GC收集器的算法是 "标记-清除" 还是 "标记-整理"(也称作"标记-压缩"),值得注意的是,复制算法内存也是规整的
创建一个真正对象的基本过程:5步
1.类加载机制检查
JVM首先检查一个new指定的参数是否能在常量池中定位到一个符号引用,并且检查该符号应用代表的类是否已被加载、解析和初始化过
实际就是在检查new的对象所属的类是否已经执行过类加载机制,如果没有,则先进行加载机制加载类
2.分配内存
把一块确定大小的内存从堆中划分出来
3.初始化零值
对象的实例字段不需要赋初始值也可以直接通过其默认零值
每种类型的对象都有不同的默认零值
4.设置对象头
5.执行
为对象字符赋值(第3步只是初始化了零值,这里会根据参数,给实例赋值)
13.JVM 内存回收GC
1.内存回收区域
堆:GC主要区域
方法区: 回收无用的类和废气的常量
注意:
栈和PC寄存器是线程私有,不会发生GC
2.判断对象是否存活
1.引用计数法
原理:
给对象添加一个引用计数器,每当有一个地方使用它,计数器值+1,引用失效时,计数器值-1
缺点:
1.每次为对象赋值时,都要进行计数器值得加减,消耗较大
2.对于循环依赖无法处理
2.可达性分析(跟踪收集)
原理:
从根集合(GC Root)开始向下扫描,根集合中的节点可以到达的节点就是存活节点,根集合中的节点到达不了的节点就是要被回收的的节点
GC Root节点: 全局性的引用(常量和静态属性)和栈引用
1.Java栈中的对象引用
2.方法区中, 常量+静态变量
3. 3 种引用类型
强引用: A a = new A();//a是常引用
强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题
软引用: 内存不足时,是否弱引用所引用的对象,当内存足够时,就是一个普通对象(强引用)
A a = new A();
SoftReference sr = new SoftReference(a); //软引用
弱引用: 弱引用对象只能存活到下一次垃圾会话之前,一旦发生垃圾回收,立刻被回收掉
4.GC回收算法
1.标记-清楚算法 --- 年老代
2.标记-整理算法(标记-压缩) --- 年老代
3.复制算法 --- 年轻代
标记-清楚算法 --- 年老代
原理:
从根集合点扫描,标记处所有的存活对象,最后扫描整个内存空间,并清除没有标记的对象(即:死亡对象)
使用场合:
存活对象较多的情况下比较高效
适用于年老代
缺点:
容易产生内存碎片,再来一个比较大的对象时(典型情况:该对象的大小大于空闲表中的每一块儿大小但是小于其中两块儿的和),会提前触发垃圾回收
扫描了整个空间两次(第一次:标记存活对象;第二次:清除没有标记的对象)
注意:
在该情况下,内存不规整,对象的内存分配采用"空闲列表法"
标记-整理算法(标记-压缩) --- 年老代
原理:
从根集合节点进行扫描,标记出所有的存活对象,最后扫描整个内存空间并清除没有标记的对象(即死亡对象)(可以发现前边这些就是标记-清除算法的原理),清除完之后,将所有的存活对象左移到一起。
适用场合:
用于年老代(即旧生代)
缺点:
需要移动对象,若对象非常多而且标记回收后的内存非常不完整,可能移动这个动作也会耗费一定时间
扫描了整个空间两次(第一次:标记存活对象;第二次:清除没有标记的对象)
优点:
不会产生内存碎片
注意:
在该情况下,内存规整,对象的内存分配采用"指针碰撞法"
复制算法 --- 年轻代
原理:
从根集合节点进行扫描,标记出所有的存活对象,并将这些存活的对象复制到一块儿新的内存(图中下边的那一块儿内存)上去,之后将原来的那一块儿内存(图中上边的那一块儿内存)全部回收掉
适用场合:
存活对象较少的情况下比较高效
扫描了整个空间一次(标记存活对象并复制移动)
适用于年轻代(即新生代):基本上98%的对象是"朝生夕死"的,存活下来的会很少
缺点:
需要一块儿空的内存空间
需要复制移动对象
注意:
在该情况下,内存规整,对象的内存分配采用"指针碰撞法",见《第二章 JVM内存分配》
以空间换时间:通过一块儿空内存的使用,减少了一次扫描
14.关于Set --- HashSet、TreeSet、LinkedHashSet -- 都是去重的,都可用Iterator或foreach进行遍历
参考:http://blog.csdn.net/speedme/article/details/22661671
HashSet --- 去重,无序, add()时会调用hashcode和equals,所以存储在HashSet中的对象需要重写这两个方法,非同步的,元素只能放一个null
即:
HashSet:数据结构式哈希表,线程非同步。保证元素唯一性的原理,判断hashCode是否相同,如果相同,判断元素的equals方法
TreeSet --- 去重,可按照某种顺序排序, add()会将对象转为Comparable,然后调用compareTo()方法,所以存储在TreeSet中的对象必须实现Comparable,重写compareTo()方法
底层数据结构是 二叉树,保证元素唯一性的依据
支持2种排序:自然排序、定制排序
TreeSet判断两个对象不相等的方式是两个对象通过equals方法返回false,或者通过CompareTo方法比较没有返回0
自然排序
自然排序使用要排序元素的CompareTo(Object obj)方法来比较元素之间大小关系,然后将元素按照升序排列。
Java提供了一个Comparable接口,该接口里定义了一个compareTo(Object obj)方法,该方法返回一个整数值,实现了该接口的对象就可以比较大小。
obj1.compareTo(obj2)方法如果返回0,则说明被比较的两个对象相等,如果返回一个正数,则表明obj1大于obj2,如果是 负数,则表明obj1小于obj2。
如果我们将两个对象的equals方法总是返回true,则这两个对象的compareTo方法返回应该返回0
定制排序
自然排序是根据集合元素的大小,以升序排列,如果要定制排序,应该使用Comparator接口,实现 int compare(T o1,T o2)方法
2种排序方式比较:
方式一:让集合中的元素自身具有比较性,这就让加入到TreeSet集合中的对象必须实现comparable接口重写compareTo(Object obj)方法
这种方式也成为元素的自然排序或默认排序。(但是如果排序的元素不是本人写的,别人写的没有实现comparable接口时想排序使用第二种方式)
方式二:让集合容器具有比较性,自定义一个比较器实现comparator接口,重写compare(Object o1,Object o2)方法,在初始化TreeSet容器对象将这个
自定义的比较器作参数传给容器的构造函数,使得集合容器具有比较性,使用这种方式的优先级高于方式一,
LinkedHashSet --- HashSet的子类,去重,并保留存储顺序
HashSet 工作原理: 每次存储对象时,调用对象的hashCode(),计算一个hash值,在集合中查找是否包含hash值相同的元素
如果没有hash值相同的元素,根据hashCode值,来决定该对象的存储位置,直接存入,
如果有hash值相同的元素,逐个使用equals()方法比较,
比较结果全为false就存入.
如果比较结果有true则不存.
如何将自定义类对象存入HashSet进行去重复
* 类中必须重写hashCode()方法和equals()方法
* equals()方法中比较所有属性
* hashCode()方法要保证属性相同的对象返回值相同, 属性不同的对象尽量不同
TreeSet 工作原理:存储对象时,add()内部会自动调用compareTo()方法进行比较,根据比较结果使用二叉树形式进行存储 --- 二叉树实现存储
参考:http://blog.csdn.net/jinhuoxingkong/article/details/51191106
TreeSet使用二叉树原理,对新add()的对象安装指定的顺序排序(升序、降序),每增加一个对象都会进行排序,将对象插入二叉树指定的位置
Integer和String都是按默认的TreeSet排序,自定义的对象,必须实现Comparable接口,重写compareTo(),指定比较规则
在重写compare()方法时,要返回相应的值才能使TreeSet按照一定规则来排序,升序是:比较此对象与指定对象的顺序。如果该对象小于、等于或大于指定对象,则分别返回负整数、零或正整数。
如果想把自定义类的对象存入TreeSet进行排序, 那么必须实现Comparable接口
* 在类上implement Comparable
* 重写compareTo()方法
* 在方法内定义比较算法, 根据大小关系, 返回正数负数或零
TreeSet实现排序的2种比较方式:
1.自定义类类 实现 Comparable接口,重写其 compareTo()方法 ---- 类排序
2.给TreeSet定义一个实现Comparator接口的比较器,重写其 compare()方法 ---- 比较器排序
* a.自然顺序(Comparable)
* TreeSet类的add()方法中会把存入的对象提升为Comparable类型
* 调用对象的compareTo()方法和集合中的对象比较
* 根据compareTo()方法返回的结果进行存储
* b.比较器顺序(Comparator)
* 创建TreeSet的时候可以制定 一个Comparator
* 如果传入了Comparator的子类对象, 那么TreeSet就会按照比较器中的顺序排序
* add()方法内部会自动调用Comparator接口中compare()方法排序
* 调用的对象是compare方法的第一个参数,集合中的对象是compare方法的第二个参数
* c.两种方式的区别
* TreeSet构造函数什么都不传, 默认按照类中Comparable的顺序(没有就报错ClassCastException)
* TreeSet如果传入Comparator, 就优先按照Comparator
15.关于Map --- HashMap、TreeMap
原理:http://blog.csdn.net/chenssy/article/details/26668941
HashMap
按照key的hashCode实现,无序
TreeMap
基于红黑树实现,映射根据其key的自然顺序进行排序,或根据创建映射时提供的Comparator进行排序,具体取决于使用的构造方法
TreeMap只能依据key来排序,不能根据value排序
如果想对value排序,可以把TreeMap的EntrySet转换成list,然后使用Collections.sort排序 -- 参考:http://blog.csdn.net/liuxiao723846/article/details/50454622
http://blog.csdn.net/xiaoyu714543065/article/details/38519817
eg:value是String或引用类型的值,按照指定规则对value进行排序
public static Map sortTreeMapByValue(Map map){
List
Collections.sort(list, new Comparator
//升序排
@Override
public int compare(Map.Entry o1, Map.Entry o2) {
return o1.getValue().toString().compareTo(o2.getValue().toString());
}
});
for (Map.Entry
System.out.println(e.getKey()+":"+e.getValue());
}
return map;
}
16.关于ThreadLocal
当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本
3个重要方法:
void set(T value)、T get()以及T initialValue()
使用场景:
多线程中,每个线程需要独享这个变量,且每个线程用的变量最初都是一样的,可以通过ThreadLocal处理该变量
原理:
ThreadLocal如何为每个线程维护变量的副本?
ThreadLocal类中有一个Map,用于存储每一个线程的变量副本,Map中key为线程对象,value为线程的变量副本
eg:
public class JavaTest {
// 创建一个Integer型的线程本地变量, 并通过匿名内部类覆盖ThreadLocal的initialValue()方法,指定初始值
public static final ThreadLocal
@Override
protected Integer initialValue() {
return 0;
}
};
public static void main(String[] args) throws InterruptedException {
Thread[] threads = new Thread[5];// 创建5个线程
for (int j = 0; j < 5; j++) {
threads[j] = new Thread(new Runnable() {
@Override
public void run() {
// 获取当前线程的本地变量,然后累加5次
int num = local.get();// 返回当前线程的线程本地变量值,若对应的thread不存在,则会调用initialValue初始化
for (int i = 0; i < 5; i++) {
num++;
}
// 重新设置累加后的本地变量
local.set(num);
System.out.println(Thread.currentThread().getName() + " : "
+ local.get());
}
}, "Thread-" + j);
}
for (Thread thread : threads) {// 启动线程
thread.start();
}
}
}
运行后结果:
Thread-0 : 5
Thread-4 : 5
Thread-2 : 5
Thread-1 : 5
Thread-3 : 5
我们看到,每个线程累加后的结果都是5,各个线程处理自己的本地变量值,线程之间互不影响
17.自定义注解
元注解
Java提供了4种元注解,专门负责注解其他注解使用
@Retention 表示需要在什么级别保存该注释信息(生命周期)
可选参数:
RetentionPolicy.SOURCE: 停留在java源文件,编译器被丢掉
RetentionPolicy.CLASS:停留在class文件中,但会被VM丢弃(默认)
RetentionPolicy.RUNTIME:内存中的字节码,VM将在运行时也保留注解,因此可以通过反射机制读取注解的信息 --- 最常用
@Target 表示该注解用于什么地方
可选参数:
ElementType.CONSTRUCTOR: 构造器声明
ElementType.FIELD: 成员变量、对象、属性(包括enum实例)
ElementType.LOCAL_VARIABLE: 局部变量声明
ElementType.METHOD: 方法声明
ElementType.PACKAGE: 包声明
ElementType.PARAMETER: 参数声明
ElementType.TYPE: 类、接口(包括注解类型)或enum声明
@Documented 将注解包含在JavaDoc中
@Inheried 运行子类型继承父类中的注解
自定义注解:
eg:
自定义注解 --- MyAnnotation
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义注解
* 作用于方法和类
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface MyAnnotation {
//为注解添加属性
String color();
String value() default "我是xxx";//为注解属性提供默认值
int[] array() default {1,2,3};
Gender gender() default Gender.MAN; // 添加一个枚举
// 添加枚举属性
MetaAnnotation metaAnnotation() default @MetaAnnotation(birthday = "我的出身日期为1988-2-18");
}
定义一个枚举类 --- Gender
public enum Gender{
MAN{
public String getName(){
return "男";
}
},
WOMEN{
public String getName(){
return "女";
}
};
}
定义注解类 --- MetaAnnotation
public @interface MetaAnnotation{
String birthday();
}
解析注解:
/**
* 调用注解并赋值
* Created by hetiewei on 2016/10/12.
*/
@MyAnnotation(metaAnnotation = @MetaAnnotation(birthday = "我的出身日期为1991-2-27"),
color = "red", array = {23, 26 })
public class Test {
public static void main(String args[]){
//检查类Test中是否包含@MyAnnotation注解
if (Test.class.isAnnotationPresent(MyAnnotation.class)){
//若存在则获取注解,并解析
MyAnnotation annotation = Test.class.getAnnotation(MyAnnotation.class);
System.out.println(annotation);
//解析注解中的内容
//1.获取注解属性
System.out.println(annotation.color());
System.out.println(annotation.value());
//2.获取属性数组
int[] arrs = annotation.array();
System.out.println(arrs.toString());
//3.获取枚举
Gender gender = annotation.gender();
System.out.println("性别:"+gender);
//4.获取注解属性
MetaAnnotation meta = annotation.metaAnnotation();
System.out.println(meta.birthday());
}
}
}
18.关于枚举 枚举类是一种特殊的类,它一样有自己的Field,方法,可以实现一个或者多个接口,也可以定义自己的构造器
枚举与普通类有如下简单区别:
(1). 枚举类可以实现一个或者多个接口,使用enum定义的枚举类默认继承了java.lang.Enum类,而不是继承Object类。其中java.lang.Enum类实现了java.lang.Serializable和java.lang.Comparable接口。
(2). 使用enum定义,非抽象的枚举类默认会使用final修饰,因此枚举类不能派生子类。
(3). 枚举类的构造器只能使用private访问控制符,如果省略了构造器的访问控制符,则默认使用private修饰;如果强制指定访问控制符,则只能指定private修饰符。
(4). 枚举类的所有实例必须在枚举类的第一行显示列出,否则这个枚举类永远不能产生实例。列出这些实例时,系统会自动添加public static final修饰,无须程序员显示添加。
所有的枚举类都提供了一个values方法,该方法可以很方便的遍历所有的枚举值
(5) 枚举常用方法
name() ,toString() --- 返回此枚举实例名称,优先使用 toString()
ordinal() --- 返回枚举实例的索引位置,第一个枚举值索引为0
public static T valueOf(Class enumType, String name)
--- 返回指定枚举类中指定名称的枚举值,名称必须与在该枚举类中声明枚举值时所用的标识符完全匹配,不允许使用额外的空白字符
eg:
public enum SeasonEnum{
//列出4个枚举实例
SPRING,SUMMER,FALL,WINTER;
}
解析:
1.枚举值之间以英文逗号(,)隔开,枚举值列举结束后以英文分号作为结束
2.枚举值代表了该枚举类的所有可能实例
3.使用枚举值 EnumClass.variable eg: SeasonEnum.SPRING
4.枚举可作为switch条件表达式
5.获取所有枚举值 EnumClass[] enums = EnumClass.values();
枚举类的属性Field、方法和构造器
1.枚举类也是一种类,只是它是一种比较特殊的类,因此它一样可以定义Field,方法
19.几种集合类解析
1.HashMap 底层 数组+链表,计算出hash值后,得到元素在数组中存放的位置索引,
若不同元素hash值相同,即:有相同的存放位置,则在相同位置建立链表,采用头插入法依次保存元素
工作原理:
数组+链表 以 Entry[]数组实现的哈希桶数组,用Key的哈希值取模数组的大小得到数组的下标
如果多个key占有同一个下标(碰撞),则使用链表将相同的key串起来
通过hash方法,通过put和get存储和获取对象,存储对象时,将K/V传给put()方法时,它调用hashCode()计算hash值得到bucket位置,
进一步存储,HashMap会根据当前bucket的占用情况自动调整容量(超过加载因子,容量扩展为2倍)。
获取对象时,通过K,调用hashCode()得到bucket位置,并进一步调用equals()方法确定键值对。
如果发生碰撞时,HashMap通过链表将产生碰撞的元素组织起来,在Java8中,如果一个bucket中碰撞的元素超过某个限制(,默认8个),
则使用红黑树来替换链表,从而提高速度
2.HashSet 底层 是HashMap实现, 优点:利用哈希表提供查询效率, 缺点:元素不能重复
由于HashSet不允许元素重复,故需要判断元素是否相同,
用hash表判断元素是否相同的方法,即需要hashCode和equals两个方法,对于hashSet,先通过hashCode判断元素在哈希表中的位置是否相同,在通过equals方法判断元素内容是否相同
哈希表如何判断元素是否相同?
1> 哈希值是否相同 :判断的其实是对象的hashCode()方法是否相同(看对象是否在哈希表的同一个位置)
2>内容是否相同:用equals方法判断对象是否相同。
规则:若hash值不同,不必判断对象内容,返回false;若hash值相同,有必要判断对象内容,若在相同,返回true,否则false。
3.TreeSet 使用元素的自然顺序,对象集合中元素进行排序,添加的元素需要自己实现Comparable接口,以便默认排序时调用其CompareTo()进行比较
2中自定义排序方式
1.元素的类,实现Comparable接口,实现compareTo()
2.给ThreeSet传递一个实现Comparator接口的参数对象
20.关于线程池
参考:
http://www.codeceo.com/article/java-thread-pool-deep-learn.html
http://www.codeceo.com/article/java-threadpoolexecutor.html
1.核心类:
ThreadPoolExecutor extends AbstractExecutorService implement ExecutorService 提供4个构造器
构造器参数:
corePoolSize: 核心池大小
默认情况,创建线程池后,池中线程数为0,当有任务来时,创建一个线程去执行任务,当线程池中线程数目达到corePoolSize后,会把任务放入缓存队列、
maxPoolSize: 线程池最大线程数
keepAliveTime:表示线程没有任务执行时最多保持多久会终止
默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize
unit: 参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性
TimeUnit.DAYS; //天
TimeUnit.HOURS; //小时
TimeUnit.MINUTES; //分钟
TimeUnit.SECONDS; //秒
TimeUnit.MILLISECONDS; //毫秒
TimeUnit.MICROSECONDS; //微妙
TimeUnit.NANOSECONDS; //纳秒
workQueue: 阻塞队列,用来存储等待执行的任务
可选的阻塞队列:
ArrayBlockingQueue
LinkedBlockingQueue --- 默认,用的最多
SynchronousQueue
PriorityBlockingQueue
threadFactory: 线程工厂,主要用来创建线程
handler: 表示当拒绝处理任务时的策略,
4种取值:
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
2.线程池原理:
1.线程池状态:
ThreadPoolExecutor中定义了一个Volatile变量runState表示当前线程池的状态,使用volatile来保证线程之间的可见性
线程池的4种状态:
volatile int runState;
static final int RUNNING = 0;
static final int SHUTDOWN = 1;
static final int STOP = 2;
static final int TERMINATED = 3;
解析:
1.创建线程池后,初始时,线程池处于RUNNING状态
2.如果调用了shutdown()方法,则线程池处于SHUTDOWN状态,此时线程池不能接受新任务,但会等待所有任务执行完毕
3.如果调用了shutdownNow()方法,线程池处于STOP状态,此时线程池不能接受新任务,并且会尝试终止正在执行的任务
4.当线程池处于SHUTDOWN或STOP状态,并且所有工作线程已经销毁,任务缓存队列已经清空或执行结束后,线程池被设置为TERMINATED状态
2.任务的执行:
线程任务保存在BlockingQueue中,通过execute(Runnable )来调用执行,
21.Java的类加载器
类加载器 --- 一个用来加载类文件的类
Java源码通过javac编译成类文件,然后JVM来执行类文件中的字节码,类加载器负责加载文件系统、网络或其他来源的类文件
JVM中类加载器的树状层次结构
Bootstrap ClassLoader 引导类加载器, 加载Java的核心库(jre/lib/rt.jar),用C++代码实现,不继承子java.lang.ClassLoader
Extension ClassLoader 扩展类加载器, 加载Java的扩展库(jre/ext/*.jar), Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类
System ClassLoader 系统类加载器, 根据Java应用的类路径(classpath)来加载Java类,
Java应用的类都是由它来完成加载的,可通过ClassLoader.getSystemClassLoader()获取系统类加载器
自定义类加载器 通过继承java.lang.ClassLoader类,实现自己的类加载器
Java类加载器的3个机制:
委托机制:
将加载器的请求交给父加载器,如果父类加载器找不到或不能加载这个类,则当前类加载器再加载它
可见性机制:
子类的加载器可以看见所有父类加载器加载的类,而父类加载器看不到子类加载器加载的类
单一性机制:
类仅被加载一次, 由 委托机制 确保子类加载器不会再次加载父类加载器加载过的类
类加载过程 3个步骤:
装载:
链接:(验证、准备、解析)
初始化:
装载:
查找并加载类的二进制数据
链接:
验证:确保被加载类信息符合JVM规范、没有安全方面问题
准备:为类的静态变量分配内存,并将其初始化为默认值
解析:把虚拟机常量池中的符号引用转换为直接引用
初始化:
为类的静态变量赋予正确的初始值
说明:
1.JVM会为每个加载的类维护一个常量池
2.类的初始化步骤:
1.如果这个类没被加载和链接,则先进行加载和链接
2.如果这个类存在父类,如果类未初始化,则先初始化其父类
3.如果类中存在static块,一次执行这些初始化语句
java.lang.ClassLoader类
根据一个指定的类名称,找到或生成其对于的字节代码,然后从这些字节码中定义出一个Java类,即:java.lang.Class类的一个实例
ClassLoader中的方法:
getParent() 返回该类加载器的父类加载器
loadClass(name) 加载名称为name的类,返回结果是java.lang.Class的实例
findClass(name) 查找名称为name的类,返回结果是java.lang.Class的实例
findLoadedClass(name) 查找名称为name的已被加载过的类,返回结果是java.lang.Class的实例
resolveClass(Class c) 链接指定的Java类
Java中的类加载过程
加载(可自定义类加载器) 连接 ( 验证 准备 解析 ) 初始化
加载:
获取二进制字节流 --> 将字节流静态存储结构转换为方法区的运行时数据结构 --> 在堆中生成Class对象
连接:
验证:
文件格式验证: 1.参照Class文件格式规范验证
2.此阶段基于字节流经过此验证后,字节流才会进入方法区,后面的验证都依赖与方法区的验证
元数据验证: Java语法验证,eg:该类是否继承了不该继承的类
字节码验证: 运行时安全性检查
符号引用验证: 确保类中引用的类,字段,方法都是可访问的
准备:
设置类变量初始值 --- static类变量 初始值 , 注意:final比较特别!!!
1.设置类变量 --- static变量
2.设置变量初始值 (注意:非代码中定义的值,8种基本数据类型都有初始值 eg: static int a = 10, 准备阶段会把a初始值赋值为0,初始化时,再赋值为10 )
3.对于final的值,设为代码中的值(eg:final static int a = 10 , 准备阶段直接把 a 赋值为10)
解析:
将符号引用转换为直接引用
1.符号引用: 用符号来表示所引用的目标
2.直接引用: 一个指向内存中目标对象地址的句柄
初始化:
1.根据代码实现初始化类变量及其他资源 (准备阶段,static类变量还是初始值,这里赋值为代码中指定的值)
2.执行子类初始化方法时,先执行父类的初始化方法(static变量赋值,static代码段执行,先父类后子类)
22.Java反射 增加 装饰模式 的适用性
装饰模式:在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能,它是通过创建一个包装对象来包裹真实的对象,比生产子类更加灵活,
使用Java的动态代理实现装饰模式,会具有更强的灵活性和适用性
装饰模式有什么特点呢?
1、装饰对象和真实对象有相同的接口。这样调用者就能以和真实对象相同的方式和装饰对象交互。
2、装饰对象包含一个真实对象的引用(即上面例子中的Ability接口)。
3、装饰对象接受所有来调用者的请求,并把这些请求转发给真实的对象。
4、装饰对象可以在调用者的方法以前或以后增加一些附加功能。这样就确保了在运行时,不用修改给定对象的结构就可以在外部增加附加的功能。
什么样的地方使用装饰模式呢?
1、需要动态扩展一个类的功能,或给一个类添加附加职责。
2、需要动态的给一个对象添加功能,这些功能可以再动态的撤销。
3、需要增加由一些基本功能的排列组合而产生的非常大量的功能,从而使继承关系变的不现实。
4、 当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。另一种情况可能是因为类定义被隐藏,或类定义不能用于生成子类。
23.JVM的运行时栈帧 --- JVM运行程序的过程!!! --- 方法的运行过程 !!!
1.每个方法的执行,在JVM中都是对应的栈帧在JVM栈中的入栈到出栈的过程!!!
2.每个在JVM中运行的程序,都是由许多的帧切换产生的结果
参考:
http://blog.csdn.net/column/details/14217.html
栈帧: --- 线程安全!!!每个线程的栈帧相互独立 ---> 局部变量在多线程环境下线程安全的原因!!!
存放方法的局部变量表、操作数栈、动态链接,方法返回值和一些额外的附加信息
当前栈:
一个方法的调用链可能很长,当调用一个方法时,可能会有很多方法处于执行状态,但对于执行引擎,置于JVM栈顶的栈帧才是有效的,这个栈帧称为 当前栈
当前栈所关联的方法称为当前方法,执行引擎的所有指令都是针对当前栈帧进行操作的
局部变量表:
内容: 存放方法的局部变量
eg:方法参数,方法内定义的局部变量,对象引用,returnAddress类型
在Java程序被编译为class文件时,这个表的容量最大值已经确定
访问:
虚拟机利用索引编号的递增来对局部变量表中定义的变量进行一次访问(从0开始),而对于实例方法(非static方法),其局部变量表的第0个索引是this,
这是可以在实例方法中使用this.name ......的原因
动态连接:
参考:http://blog.csdn.net/eric_sunah/article/details/8014865
方法的调用过程:
在虚拟机运行时,运行时常量池会保存大量符号引用,这些符号引用可以看做每个方法的间接引用,
如果代表栈帧A的方法要调用代表栈帧B的方法,则这个虚拟机的方法调用指令就会以B方法的符号引用作为参数,
但因为符号引用并不是直接指向代表B方法的内存位置,所有在调用之前还必须要将符号引用转换为直接引用,然后通过直接引用访问到真正的方法!
注意:
静态解析:
如果符号引用在类加载阶段或第一次使用时转化为直接引用,则这种转换成为静态解析
动态连接:
如果在运行期间转换为直接引用,这种转换称为动态连接
栈帧A 常量池 栈帧B
局部变量表 局部变量表
A方法的符号引用
操作数栈 操作数栈
B方法的符号引用
动态连接 动态连接
字符串常量等
返回地址 返回地址
方法返回地址
1.正常退出:根据方法定义决定是否要返回值给上层调用者
2.异常退出:不会传递返回值给上层调用者
注意:
1. 不管那种方式结束,在退出当前方法时,都会跳转到当前方法被调用的位置!!!
如果正常退出,则调用者的PC计数器的值可作为返回地址,
如果异常退出,则需要通过异常处理表来确定
2. 方法的一次调用对应着栈帧在虚拟机中的一次入栈出栈操作!!!
方法退出时做的事情:
恢复上层方法的局部变量表以及操作数栈,如果有返回值,就把返回值压入到调用者栈帧的操作数栈中,
还会把PC计数器的值调整为方法调用入口的下一条指令
24.JVM的内存溢出分析和参数调优
1.JVM调优 http://blog.csdn.net/eric_sunah/article/details/7862114
1.JVM内存参数调优
-Xms 设置初始化堆的内存
-Xmx 设置堆最大使用内存
-Xss 设置每个线程的栈大小
-Xmn 设置年轻代大小
eg:
java -Xmx4096m -Xms4096m -Xmn2g -Xss128k
或
java -Xmx4g -Xms4g -Xmn2g -Xss128k
设置JVM堆最大可以内存为4096M,初始内存为4096M(-Xms设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存)
设置JVM年轻代大小为2G JVM堆内存 = 年轻代大小 + 年老代大小 + 持久代大小
持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,推荐配置为整个堆的3/8
设置每个线程的栈大小为128k
JDK5+ 线程栈默认1M, 相同物理内存下,减小该值能生成更多线程,但操作系统对一个进程内的线程数有限制,最好不超过5000
如果方法递归太深,则可能耗尽线程栈,报出 StackOverflow !!! 线程栈内存溢出 <--- 方法调用太深
eg:设置堆内存中的内存分配比例
java -Xmx4g -Xms4g -Xmn2g -Xss128k -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MaxPermSize=64m -XX:MaxTenuringThreshold=0
-Xmn2g 设置年轻代大小为2G
-XX:NewRatio=4:设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)。设置为4,则年轻代与年老代所占比值为1:4,年轻代占整个堆的1/5
-XX:SurvivorRatio=4 设置年轻代中from和to区的比例,Eden区(from)与Survivor区(to)的大小比值为4:1:1,即 一个 Survivor 占 年轻代的1/6
特别注意:
上面的比值 4 <=等价=> 1:4
-XX:MaxPermSize=64m 设置持久代大小为64M
-XX:MaxTenuringThreshold=0:设置垃圾最大年龄
小结:
1,整个堆包括年轻代,老年代和持久代。其中年轻代又包括一个Eden区和两个Survivor区。
2,年轻代:
-XX:NewSize (for 1.3/1.4) ,
-XX:MaxNewSize (for 1.3/1.4) ,
-Xmn
2,持久代:
-XX:PermSize
-XX:MaxPermSize
3,年轻代和老年代的比例:
-XX:NewRatio(年轻代和老年代的比值,年轻代多,除去持久代)
当设置了-XX:+UseConcMarkSweepGC后,会使-XX:NewRatio=4失效,此时需要使用-Xmn设置年轻代大小
4,Eden与Survivor的比例
-XX:SurvivorRatio(Eden区与两个Survivor区的比值,Eden区多)
2.GC参数设置
并行收集器 --- 吞吐量优先,适合后台处理
eg:
-XX:+UseParallelGC -XX:ParallelGCThreads=20 -XX:+UseParallelOldGC
解析:
-XX:+UseParallelGC:选择垃圾收集器为并行收集器。此配置仅对年轻代有效。即上述配置下,年轻代使用并发收集,而年老代仍旧使用串行收集
-XX:ParallelGCThreads=20:配置并行收集器的线程数,即:同时多少个线程一起进行垃圾回收。此值最好配置与处理器数目相等
-XX:+UseParallelOldGC:配置年老代垃圾收集方式为并行收集,JDK6.0支持对年老代并行收集
并发收集器 --- 响应时间优先,保证系统响应时间,减少垃圾收集时的停顿时间,适合应用服务器和典型领域等
eg:
java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:ParallelGCThreads=20 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC
-XX:+UseConcMarkSweepGC:设置年老代为并发收集。测试中配置这个以后,-XX:NewRatio=4的配置失效了,原因不明。所以,此时年轻代大小最好用-Xmn设置。
-XX:+UseParNewGC:设置年轻代为并行收集。可与CMS收集同时使用。JDK5.0以上,JVM会根据系统配置自行设置,所以无需再设置此值。
3.常见配置汇总
堆设置
-Xms:初始堆大小
-Xmx:最大堆大小
-XX:NewSize=n:设置年轻代大小
-XX:NewRatio=n:设置年轻代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4
-XX:SurvivorRatio=n:年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5
-XX:MaxPermSize=n:设置持久代大小
收集器设置
-XX:+UseSerialGC:设置串行收集器
-XX:+UseParallelGC:设置并行收集器
-XX:+UseParalledlOldGC:设置并行年老代收集器
-XX:+UseConcMarkSweepGC:设置并发收集器
垃圾回收统计信息
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-Xloggc:filename
并行收集器设置
-XX:ParallelGCThreads=n:设置并行收集器收集时使用的CPU数。并行收集线程数。
-XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间
-XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)
并发收集器设置
-XX:+CMSIncrementalMode:设置为增量模式。适用于单CPU情况。
-XX:ParallelGCThreads=n:设置并发收集器年轻代收集方式为并行收集时,使用的CPU数。并行收集线程数。
4.调优总结
年轻代大小选择
响应时间优先的应用:尽可能设大,直到接近系统的最低响应时间限制(根据实际情况选择)。在此种情况下,年轻代收集发生的频率也是最小的。同时,减少到达年老代的对象。
吞吐量优先的应用:尽可能的设置大,可能到达Gbit的程度。因为对响应时间没有要求,垃圾收集可以并行进行,一般适合8CPU以上的应用。
年老代大小选择
响应时间优先的应用:年老代使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率和会话持续时间等一些参数。
如果堆设置小了,可以会造成内存碎片、高回收频率以及应用暂停而使用传统的标记清除方式;
如果堆大了,则需要较长的收集时间。最优化的方案,一般需要参考以下数据获得:
并发垃圾收集信息
持久代并发收集次数
传统GC信息
花在年轻代和年老代回收上的时间比例
减少年轻代和年老代花费的时间,一般会提高应用的效率
吞吐量优先的应用:一般吞吐量优先的应用都有一个很大的年轻代和一个较小的年老代。原因是,这样可以尽可能回收掉大部分短期对象,
减少中期的对象,而年老代尽存放长期存活对象。
较小堆引起的碎片问题
因为年老代的并发收集器使用标记、清除算法,所以不会对堆进行压缩。当收集器回收时,他会把相邻的空间进行合并,这样可以分配给较大的对象。
但是,当堆空间较小时,运行一段时间以后,就会出现“碎片”,如果并发收集器找不到足够的空间,那么并发收集器将会停止,
然后使用传统的标记、清除方式进行回收。如果出现“碎片”,可能需要进行如下配置:
-XX:+UseCMSCompactAtFullCollection:使用并发收集器时,开启对年老代的压缩。
-XX:CMSFullGCsBeforeCompaction=0:上面配置开启的情况下,这里设置多少次Full GC后,对年老代进行压缩
2.JVM内存溢出分析 --- OutOfMemoryError 不属于Exception,继承自Throwable
1.堆内存溢出 --- 不断创建对象,并且不释放,导致GC无法回收,堆内存移除 Java heap space
eg: 制造堆内存溢出的程序
1.降低修改虚拟机堆内存大小 -Xms20m -Xmx20m
2.不断创建强引用对象,方式GC回收
public static void main(String[] args) {
headOutOfMemory();
}
/*
* -verbose:gc -XX:+PrintGCDetails -verbose:gc
* -XX:+HeapDumpOnOutOfMemoryError
*
* -Xms20m -Xms20m
*
*/
static void headOutOfMemory() {
long count = 0;
try {
List