nemotan 2016-04-12T12:21:07+00:00 kejinlu@gmail.com hadoop-2.6.0为分布式安装指南 2016-02-24T00:00:00+00:00 nemotan http://nemotan.github.io//2016/02/hadoop-2.6.0伪分布式安装指南 [toc]

hadoop-2.6.0伪分布安装指南(官网参考)

安装准备

系统信息

ubutun14.04 64位操作系统

virtual box安装

 1、linux安装服务 sudo apt-get install openssh-server
 2、sudo /etc/init.d/ssh start
 3、安装增强才能挂载:http://lxf20001978.blog.163.com/blog/static/27110722201041763931331/
 sudo  aptitude install build-essential linux-headers-$(uname -r) -y
 sudo  mount /dev/cdrom /mnt/
 执行:sudo  /mnt/VBoxLinuxAdditions-x86.run
 sudo umount /mnt/

 4、linux开机挂载virtual box共享文件夹:sudo mount -t vboxsf share /mnt/share

下载hadoop-2.6.0

去官网上下载hadoop-2.6.0jar包,默认是64位系统的。这里直接解压到用户文件夹下~/下

安装jdk1.7

下载linux版本的jdk,解压到用户目录下。

修改环境变量

export JAVA_HOME=/home/nemo/jdk1.7
export HADOOP_HOME=/home/nemo/hadoop-2.6.0
export PATH=$PATH:$JAVA_HOME/bin:$HADOOP_HOME/sbin:$HADOOP_HOME/bin:

修改host域名以供下面配置文件使用:/etc/hosts文件

192.168.3.226   master

修改hadoop配置文件

修改:~/hadoop-2.6.0/etc/hadoop/hadoop-env.sh

#修改jdk路径
export JAVA_HOME=/home/nemo/jdk1.7

修改:~/hadoop-2.6.0/etc/hadoop/core-site.sh

<configuration>
 <property>
        <name>fs.defaultFS</name>
        <value>hdfs://master:9000</value>
 </property>
 <property>
        <name>hadoop.tmp.dir</name>
        <value>/home/nemo/hadoop-2.6.0/tmp</value>
        <description>Abase for other temporary directories.</description>
 </property>
</configuration>

修改:~/hadoop-2.6.0/etc/hadoop/hdfs-site.sh

<configuration>
  <property>
        <name>dfs.replication</name>
        <value>1</value>
  </property>
</configuration>

修改:~/hadoop-2.6.0/etc/hadoop/mapred-site.sh

<configuration>
 <property>
        <name>mapreduce.framework.name</name>
        <value>yarn</value>
 </property>
 <property>
        <name>mapreduce.app-submission.cross-platform</name>
        <value>true</value>
 </property>
        <property>
                <name>mapreduce.map.memory.mb</name>
                <value>384</value>
        </property>
        <property>
                <name>mapreduce.reduce.memory.mb</name>
                <value>384</value>
        </property>
</configuration>

修改:~/hadoop-2.6.0/etc/hadoop/yarn-site.sh

<configuration>
 <property>
        <name>yarn.nodemanager.aux-services</name>
        <value>mapreduce_shuffle</value>
 </property>
<property>
    <name>yarn.nodemanager.resource.memory-mb</name>
    <value>6144</value>
    <discription>每个节点可用内存,单位MB</discription>
</property>

<property> <name>yarn.scheduler.minimum-allocation-mb</name> <value>512</value> <discription>单个任务可申请最少内存,默认1024MB</discription> </property>

<property> <name>yarn.scheduler.maximum-allocation-mb</name> <value>8192</value> <discription>单个任务可申请最大内存,默认8192MB</discription> </property> </configuration>

启动

由于配置了环境变量,那么直接输入:

start-dfs.sh :启动hadoop的hdfs
start-yarn.sh :启动yarn

格式化:

hadoop namenode -format

验证:

输入jps,查看java进程,有下面5个进程说明启动成功
4385 DataNode
4813 ResourceManager
4603 SecondaryNameNode
4936 NodeManager
4168 NameNode

浏览器验证:

http://master:50070/  :查看hdfs的信息
http://master:8088/cluster :查看job信息

hdfs shell常用命令:命令查询

hdfs dfs -ls :查看
]]>
布隆过滤算法的使用场景 2015-12-23T00:00:00+00:00 nemotan http://nemotan.github.io//2015/12/布隆过滤算法的使用场景 [toc]

案例描述

问题:假设要你写一个网络蜘蛛( web crawler)。由于网络间的链接错综复杂,蜘蛛在网络间爬行很可能会形成“环”。为了避免形成“环”,就需要知道蜘蛛已经访问过那些 URL。给一个 URL,怎样知道蜘蛛是否已经访问过呢?稍微想想,就会有如下几种方案:

方案

  1. 将访问过的 URL保存到数据库。

  2. 用HashSet将访问过的URL保存起来。那只需接近O(1)的代价就可以查到一个URL是否被访问过了。

  3. URL经过MD5或SHA-1等单向哈希后再保存到HashSet或数据库。

  4. Bit-Map方法。建立一个BitSet,将每个URL经过一个哈希函数映射到某一位。

方法1~3都是将访问过的URL完整保存,方法4则只标记URL的一个映射位。

缺点

  1. 方法1的缺点:数据量变得非常庞大后关系型数据库查询的效率会变得很低。而且每来一个URL就启动一次数据库查询是不是太小题大做了?

  2. 方法2的缺点:太消耗内存。随着URL的增多,占用的内存会越来越多。就算只有1亿个URL,每个URL只算50个字符,就需要5GB内存。

  3. 方法3:由于字符串经过MD5处理后的信息摘要长度只有128Bit,SHA-1处理后也只有160Bit,因此方法3比方法2节省了好几倍的内存。

  4. 方法4消耗内存是相对较少的,但缺点是单一哈希函数发生冲突的概率太高。还记得数据结构课上学过的Hash表冲突的各种解决方法么?若要降低冲突发生的概率到1%,就要将BitSet的长度设置为URL个数的100倍。

实质上上面的算法都忽略了一个重要的隐含条件:允许小概率的出错,不一定要100%准确!也就是说少量url实际上没有没网络蜘蛛访问,而将它们错判为已访问的代价是很小的——大不了少抓几个网页呗。

Bloom Filter的算法

定义

其实上面方法4的思想已经很接近Bloom Filter了。 方法四的致命缺点是冲突概率高为了降低冲突的概念,Bloom Filter使用了多个哈希函数,而不是一个。

定义:创建一个m位BitSet,先将所有位初始化为0,然后选择k个不同的哈希函数。第i个哈希函数对字符串str哈希的结果记为h(i,str),且h(i,str)的范围是0到m-1 。

加入字符串操作

下面是每个字符串处理的过程,首先是将字符串str“记录”到BitSet中的过程:

对于字符串str,分别计算h(1,str),h(2,str)…… h(k,str)。
然后将BitSet的第h(1,str)、h(2,str)…… h(k,str)位设为1。

  这样就将字符串str映射到BitSet中的k个二进制位了。

检查字符串操作

下面是检查字符串str是否被BitSet记录过的过程:

  对于字符串str,分别计算h(1,str),h(2,str)…… h(k,str)。然后检查BitSet的第h(1,str)、h(2,str)…… h(k,str)位是否为1,若其中任何一位不为1则可以判定str一定没有被记录过。若全部位都是1,则“认为”字符串str存在。

  若一个字符串对应的Bit不全为1,则可以肯定该字符串一定没有被Bloom Filter记录过。(这是显然的,因为字符串被记录过,其对应的二进制位肯定全部被设为1了)

  但是若一个字符串对应的Bit全为1,实际上是不能100%的肯定该字符串被Bloom Filter记录过的。(因为有可能该字符串的所有位都刚好是被其他字符串所对应)这种将该字符串划分错的情况,称为false positive 。

删除字符串操作

  字符串加入了就被不能删除了,因为删除会影响到其他字符串。实在需要删除字符串的可以使用Counting bloomfilter(CBF),这是一种基本Bloom Filter的变体,CBF将基本Bloom Filter每一个Bit改为一个计数器,这样就可以实现删除字符串的功能了。

  Bloom Filter跟单哈希函数Bit-Map不同之处在于:Bloom Filter使用了k个哈希函数,每个字符串跟k个bit对应。从而降低了冲突的概率。

Bloom Filter参数选择

哈希函数选择

  哈希函数的选择对性能的影响应该是很大的,一个好的哈希函数要能近似等概率的将字符串映射到各个Bit。选择k个不同的哈希函数比较麻烦,一种简单的方法是选择一个哈希函数,然后送入k个不同的参数。

Bit数组大小选择

  哈希函数个数k、位数组大小m、加入的字符串数量n的关系可以参考参考文献1。该文献证明了对于给定的m、n,当 k = ln(2)* m/n 时出错的概率是最小的。

  同时该文献还给出特定的k,m,n的出错概率。哈希函数个数k取10,位数组大小m设为字符串个数n的20倍时,false positive发生的概率是0.0000889 ,这个概率基本能满足网络爬虫的需求了。

参考:布隆过滤算法详解 Bloom filter 过滤(布隆过滤算法)原理

]]>
哈希表以及java中HashMap源码分析 2015-12-23T00:00:00+00:00 nemotan http://nemotan.github.io//2015/12/哈希表以及java中HashMap源码分析 [toc]

哈希表定义

Hash ,一般翻译做“ 散列” ,也有直接音译为“ 哈希” 的,就是把任意长度的输入(又叫做预映射, pre-image ),通过散列算法,变换成固定长度的输出,该输出就是散列值。

这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,而不可能从散列值来唯一的确定输入值。 简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。

HASH 主要用于信息安全领域中加密算法,它把一些不同长度的信息转化成杂乱的128 位的编码, 这些编码值叫做 HASH 值. 也可以说, hash 就是找到一种数据内容和数据存放地址之间的映射关系 例如字符串 hello 的哈希算法

char value = "hello"; int key = (((((((27 (int)'h'+27) (int)'e') + 27)   (int)'l') + 27)  (int)'l' +27)  27 ) + (int)'o' ; 

HashMap实现原理

特点

数组的特点:

寻址容易,插入和删除困难;

链表的特点:

寻址困难,插入和删除容易。

那么我们能不能综合两者的特性,做出一种寻址容易,插入删除也容易 的数据结构?答案是肯定的,这就是我们要提起的哈希表,哈希表有多种不同的实现方法,我接下来解释的是最常用的一种方法—— 拉链法,我们可以理解为 “ 链表 的数组” ,如图:

实现原理

1、首先 HashMap里面实现一个静态内部类 Entry 其重要的属性有 key , value, next,从属性 key,value我们就能很明显的看出来 Entry就是 HashMap键值对实现的一个基础 bean,我们上面说到 HashMap的基础就是一个线性数组,这个数组就是 Entry[],Map里面的内容都保存在 Entry[]里面。

2、既然是线性数组,为什么能随机存取?这里 HashMap用了一个小算法,大致是这样实现:

存储时:
int hash = key.hashCode();--> 这个hashCode方法这里不详述,只要理解每个keyhash是一个固定的int
int index = hash % Entry[].length;
Entry[index] = value;

取值时: int hash = key.hashCode(); int index = hash % Entry[].length; return Entry[index]

3、疑问:如果两个key通过hash % Entry[].length得到的index相同,会不会有覆盖的危险?

这里 HashMap里面用到链式数据结构的一个概念.上面我们提到过 Entry 类里面有一个 next属性,作用是指向下一个 Entry。打个比方, 第一个键值对A进来,通过计算其 keyhash得到的 index=0, 记做: Entry[0] = A.一会后又进来一个键值对B,通过计算其 index也等于0,现在怎么办? HashMap会这样做 :B.next = A,Entry[0] = B,如果又进来C,index也等于0,那么C.next = B,Entry[0] = C;这样我们发现index=0的地方其实存取了A,B,C三个键值对,他们通过next这个属性链接在一起。所以疑问不用担心。

当然HashMap里面也包含一些优化方面的实现,这里也啰嗦一下。 比如:Entry[]的长度一定后,随着map里面数据的越来越长,这样同一个index的链就会很长,会不会影响性能?HashMap里面设置一个因素(也称为因子),随着map的size越来越大,Entry[]会以一定的规则加长长度。

hash冲突解决办法

开放定址法(线性探测再散列,二次探测再散列,伪随机探测再散列)

这种方法也称再散列法,其基本思想是:当关键字key的哈希地址p=H(key)出现冲突时,以p为基础,产生另一个哈希地址p1,如果p1仍然冲突,再以p为基础,产生另一个哈希地址p2,…,直到找出一个不冲突的哈希地址pi ,将相应元素存入其中。这种方法有一个通用的再散列函数形式:

Hi=(H(key)+di)% m   i=1,2,…,n

其中H(key)为哈希函数,m 为表长,di称为增量序列。增量序列的取值方式不同,相应的再散列方式也不同。主要有以下三种:

线性探测再散列

dii=1,2,3,…,m-1

这种方法的特点是:冲突发生时,顺序查看表中下一单元,直到找出一个空单元或查遍全表。

二次探测再散列

di=12,-12,22,-22,…,k2,-k2    ( k<=m/2 )

这种方法的特点是:冲突发生时,在表的左右进行跳跃式探测,比较灵活。 伪随机探测再散列

di=伪随机数序列。

再哈希法

这种方法是同时构造多个不同的哈希函数:

Hi=RH1(key)  i=1,2,…,k

当哈希地址Hi=RH1(key)发生冲突时,再计算Hi=RH2(key)……,直到冲突不再产生。这种方法不易产生聚集,但增加了计算时间。

链地址法

这种方法的基本思想是将所有哈希地址为i的元素构成一个称为同义词链的单链表,并将单链表的头指针存在哈希表的第i个单元中,因而查找、插入和删除主要在同义词链中进行。链地址法适用于经常进行插入和删除的情况。

例如,已知一组关键字(32,40,36,53,16,46,71,27,42,24,49,64),哈希表长度为13,哈希函数为:H(key)= key % 13,则用链地址法处理冲突的结果如图所示:

建立一 公共溢出区

这种方法的基本思想是:将哈希表分为 基本表和溢出表两部分,凡是和基本表发生冲突的元素,一律填入溢出表

]]>
ConcurrentHashMap源码分析 2015-12-23T00:00:00+00:00 nemotan http://nemotan.github.io//2015/12/ConcurrentHashMap源码分析 [toc]

HashTable和ConcurrentMap比较

HashTable 使用的是 synchronized是针对整张 Hash表的,即每次锁住整张表让线程独占, ConcurrentHashMap允许多个修改操作并发进行,其关键在于使用了锁分离技术。它使用了多个锁来控制对hash表的不同部分进行的修改。

ConcurrentHashMap内部使用段 (Segment)来表示这些不同的部分,每个段其实就是一个小的 hash table,它们有自己的锁。只要多个修改操作发生在不同的段上,它们就可以并发进行。

size()containsValue()等一些操作全表的方法,它们可能需要锁定整个表而而不仅仅是某个段,这需要按顺序锁定所有段,操作完毕后,又按顺序释放所有段的锁。这里“按顺序”是很重要的,否则极有可能出现死锁,在 ConcurrentHashMap内部,段数组是 final的,并且其成员变量实际上也是 final的,但是,仅仅是将数组声明为 final的并不保证数组成员也是 final的,这需要实现上的保证。这可以确保不会出现死锁,因为获得锁的顺序是固定的。

数据结构

ConcurrentHashMapHashtable主要区别就是围绕着锁的粒度以及如何锁,可以简单理解成把一个大的 HashTable分解成多个,形成了锁分离。如图:

另外附上HashMap和HashTable的数据结构图: 当然HashTable中和HashMap的区别知识增加了synchronized锁,锁定了整个表

使用场景

1、多线程共享数据场景 2、当设计数据表的事务时(事务某种意义上也是同步机制的体现),可以把一个表看成一个需要同步的数组,如果操作的表数据太多时就可以考虑事务分离了(这也是为什么要避免大表的出现),比如把数据进行字段拆分,水平分表等.

部分源码

ConncurrentHashMap的segment

ConcurrentHashMap 中主要实体类就是三个: ConcurrentHashMap(整个 Hash表),Segment(桶),HashEntry(节点),对应上面的图可以看出之间的关系

/*
* The segments, each of which is a specialized hash table
/
final Segment<K,V>[] segments;

HashEntry

ConcurrentHashMap完全允许多个读操作并发进行,读操作并不需要加锁。如果使用传统的技术,如 HashMap中的实现,如果允许可以在hash链的中间添加或删除元素,读操作不加锁将得到不一致的数据。 ConcurrentHashMap实现技术是保证 HashEntry几乎是不可变的。 HashEntry代表每个 hash链中的一个节点,其结构如下所示:

 static final class HashEntry<K,V> {
final K key;
final int hash;
volatile V value;
final HashEntry<K,V> next;

}

可以看到除了 value不是 final的,其它值都是 final的,这意味着不能从 hash链的中间或尾部添加或删除节点,因为这需要修改 next 引用值,所有的节点的修改只能从头部开始。对于 put操作,可以一律添加到 Hash链的头部。但是对于 remove操作,可能需要从中间删除一个节点,这就需要将要删除节点的前面所有节点整个复制一遍,最后一个节点指向要删除结点的下一个结点。这在讲解删除操作时还会详述。为了确保读操作能够看到最新的值, 将value设置成volatile,这避免了加锁。

定位段

如下是定位段的方法:

1. final Segment<K,V> segmentFor(int hash) {
2. return segments[(hash >>> segmentShift) & segmentMask];
3. }

segments相关属性

关于 Hash表的基础数据结构,这里不想做过多的探讨。 Hash表的一个很重要方面就是如何解决 hash冲突, ConcurrentHashMapHashMap使用相同的方式,都是将 hash值相同的节点放在一个 hash链中。与 HashMap不同的是, ConcurrentHashMap使用多个子 Hash表,也就是段( Segment)。下面是 ConcurrentHashMap的数据成员:

1. public class ConcurrentHashMap<K, V> extends AbstractMap<K, V>
2. implements ConcurrentMap<K, V>, Serializable {
3. / 4. * Mask value for indexing into segments. The upper bits of a 5. * key's hash code are used to choose the segment. 6. */
7. final int segmentMask;
8.
9. /
10. * Shift value for indexing within segments. 11. /
12. final int segmentShift;
13.
14. /** 15. * The segments, each of which is a specialized hash table 16.
/
17. final Segment<K,V>[] segments;
18. }

segment源码

所有的成员都是 final的,其中 segmentMask和segmentShift主要是为了定位段,参见上面的 segmentFor方法。 每个 Segment相当于一个子 Hash表,它的数据成员如下

1.     static final class Segment<K,V> extends ReentrantLock implements Serializable {
2. private static final long serialVersionUID = 2249069246763182397L;
3. / 4. * The number of elements in this segment's region. 5. */
6. transient volatile int count;
7.
8. /
9. * Number of updates that alter the size of the table. This is 10. * used during bulk-read methods to make sure they see a 11. * consistent snapshot: If modCounts change during a traversal 12. * of segments computing size or checking containsValue, then 13. * we might have an inconsistent view of state so (usually) 14. * must retry. 15. /
16. transient int modCount;
17.
18. /** 19. * The table is rehashed when its size exceeds this threshold. 20. * (The value of this field is always <tt>(int)(capacity * 21. * loadFactor)</tt>.) 22.
/
23. transient int threshold;
24.
25. / 26. * The per-segment table. 27. */
28. transient volatile HashEntry<K,V>[] table;
29.
30. /
31. * The load factor for the hash table. Even though this value 32. * is same for all segments, it is replicated to avoid needing 33. * links to outer object. 34. * @serial 35. */
36. final float loadFactor;
37. }

remove(key)源码

1. public V remove(Object key) {
2. hash = hash(key.hashCode());
3. return segmentFor(hash).remove(key, hash, null);
4. }
整个操作是先定位到段,然后委托给段的remove操作。当多个删除操作并发进行时,只要它们所在的段不相同,它们就可以同时进行。下面是Segmentremove方法实现: 1. V remove(Object key, int hash, Object value) {
2. lock();
3. try {
4. int c = count - 1;
5. HashEntry<K,V>[] tab = table;
6. int index = hash & (tab.length - 1);
7. HashEntry<K,V> first = tab[index];
8. HashEntry<K,V> e = first;
9. while (e != null && (e.hash != hash || !key.equals(e.key)))
10. e = e.next;
11.
12. V oldValue = null;
13. if (e != null) {
14. V v = e.value;
15. if (value == null || value.equals(v)) {
16. oldValue = v;
17. // All entries following removed node can stay 18. // in list, but all preceding ones need to be 19. // cloned. 20. ++modCount;
21. HashEntry<K,V> newFirst = e.next;
22. for (HashEntry<K,V> p = first; p != e; p = p.next)
23.
newFirst = new HashEntry<K,V>(p.key, p.hash,
24. newFirst, p.value);
25. tab[index] = newFirst;
26. count = c; // write-volatile 27. }
28. }
29. return oldValue;
30. } finally {
31. unlock();
32. }
33. }

注意:移除一个节点不是简单的链表进行移除,因为在HashEntry中,next属性是final不可变的,因此删除操作实际上是克隆一条链。 如图,删除元素之前:

删除元素之后:

1、当要删除的结点存在时,删除的最后一步操作要将count的值减一。这必须是最后一步操作,否则读取操作可能看不到之前对段所做的结构性修改 2、remove执行的开始就将table赋给一个局部变量tab,这是因为table是 volatile变量,读写volatile变量的开销很大。编译器也不能对volatile变量的读写做任何优化,直接多次访问非volatile实例变量没有多大影响,编译器会做相应优化。

put操作源码

1. V put(K key, int hash, V value, boolean onlyIfAbsent) {
2. lock();
3. try {
4. int c = count;
5. if (c++ > threshold) // ensure capacity 6. rehash();
7. HashEntry<K,V>[] tab = table;
8. int index = hash & (tab.length - 1);
9. HashEntry<K,V> first = tab[index];
10. HashEntry<K,V> e = first;
11. while (e != null && (e.hash != hash || !key.equals(e.key)))
12. e = e.next;
13.
14. V oldValue;
15. if (e != null) {
16. oldValue = e.value;
17. if (!onlyIfAbsent)
18. e.value = value;
19. }
20. else {
21. oldValue = null;
22. ++modCount;
23. tab[index] = new HashEntry<K,V>(key, hash, first, value);
24. count = c; // write-volatile 25. }
26. return oldValue;
27. } finally {
28. unlock();
29. }
30. }

get源码

1. V get(Object key, int hash) {
2. if (count != 0) { // read-volatile 当前桶的数据个数是否为0 3. HashEntry<K,V> e = getFirst(hash); 得到头节点 4. while (e != null) {
5. if (e.hash == hash && key.equals(e.key)) {
6. V v = e.value;
7. if (v != null)
8. return v;
9. return readValueUnderLock(e); // recheck 10. }
11. e = e.next;
12. }
13. }
14. return null;
15. }

另外这篇博文详细讲解了每个方法的原理: http://www.ibm.com/developerworks/cn/java/java-lo-concurrenthashmap/

]]>
jvm【四】垃圾收集算法 2015-12-15T00:00:00+00:00 nemotan http://nemotan.github.io//2015/12/jvm【四】垃圾收集算法 [toc]

跟踪收集器

跟踪收集器采用的为集中式的管理方式,全局记录对象之间的引用状态,执行时从一些列GC Roots的对象做为起点,从这些节点向下开始进行搜索所有的引用链,当一个对象到GC Roots 没有任何引用链时,则证明此对象是不可用的。

下图中,对象Object6、Object7、Object8虽然互相引用,但他们的GC Roots是不可到达的,所以它们将会被判定为是可回收的对象。

可作为GC Roots 的对象包括: - 虚拟机栈(栈帧中的本地变量表)中的引用对象。 - 方法区中的类静态属性引用的对象 - 方法区中的常量引用的对象 - 本地方法栈中JNI的引用对象。

主要有 复制、标记清除、标记压缩三种实现算法。

1.标记-清除算法

标记清除算法是 最基础的收集算法,其他收集算法都是基于这种思想。标记清除算法分为 “标记”和“清除”两个阶段:首先标记出需要回收的对象,标记完成之后统一清除对象。

缺点: ①.标记和清除过程效率不高 ②.标记清除之后会产生大量不连续的内存碎片。

2.复制算法

它将可用内存容量划分为 大小相等的两块,每次只使用其中的一块。当这一块用完之后,就将还存活的对象复制到另外一块上面, 然后在把已使用过的内存空间一次清理掉。这样使得每次都是对其中的一块进行内存回收,不会产生碎片等情况,只要移动堆订的指针,按顺序分配内存即可,实现简单,运行高效。

主要缺点: 内存缩小为原来的一半。

3.标记-整理算法

标记操作和“标记-清除”算法一致,后续操作不只是直接清理对象,而是在清理无用对象完成后让所有存活的对象都向一端移动,并更新引用其对象的指针。

主要缺点: 在标记-清除的基础上还需进行对象的移动,成本相对较高,好处则是不会产生内存碎片。

引用计数收集器

引用计数收集器采用的是分散式管理方式,通过计数器记录对象是否被引用。当计数器为0时说明此对象不在被使用,可以被回收。

主要缺点: 循环引用的场景下无法实现回收,例如下面的图中,ObjectC和ObjectB相互引用,那么ObjectA即便释放了对ObjectC、ObjectB的引用,也无法回收。sunJDK在实现GC时未采用这种方式。

转自:http://blog.csdn.net/java2000_wl/article/details/8022293

]]>
jvm【五】垃圾收集器 2015-12-15T00:00:00+00:00 nemotan http://nemotan.github.io//2015/12/jvm【五】垃圾收集器 [toc]

HotSpot JVM收集器

下面有7中收集器,分为两块, 上面为新生代收集器,下面是老年代收集器。如果两个收集器之间存在连线,就说明它们可以搭配使用。如下图:


Serial(串行GC)收集器--新生代--复制算法

Serial收集器是最基本、发展历史最悠久的收集器(JDK1.3之前)是 虚拟机新生代收集的唯一选择。

特点是:

  1. 在它进行垃圾收集的时,必须暂停其他所有的工作线程,直到它收集结束。
  2. 举例:你妈打扫房间,必须把你赶出去,不然一边打扫,一边扔垃圾,永远也收集不完。
  3. 使用场景:依然是虚拟机运行在Client模式下的默认新生代收集器(仅仅是新生代使用的内存,桌面应用基本不会太大,停顿时间控制在几十毫秒)。

ParNew(并发GC)收集器--新生代--复制算法

ParNew收集器其实就是 serial收集器的多线程版本,除了使用 多条线程进行垃圾收集之外,其余行为与Serial收集器一样。

特点是:

  1. 是许多运行在Server模式下的虚拟机中首选的新生代收集器。
  2. 目前只有它可以 与CMS收集器配合工作。
  3. 并发收集器,垃圾收集线程和用户线程同时工作,做到了妈妈扫房间,你一边扔垃圾

Parallel Scavenge(并行回收GC)收集器--新生代--复制算法

Parallel Scavenges收集器是新生代收集器,是复制算法的收集器,有事并行的多线程收集器。

吞吐量 = 运行用户代码时间/(运行用户代码时间+垃圾收集时间)

特点:

  1. CMS是减少用户停顿时间,而这个收集器的目的是 达到一个可控制的吞吐量。
  2. 停顿时间少能提升用户体验, 高吞吐量可以高效率的利用CPU时间,适合后台运算而不需要太多交互的任务。
  3. --XX:MaxGPauseMillis:接收一个>0的毫秒数,收集器尽可能保证内存回收时间不超过这个值,但是这个值设置的过小,那么频率就会快一些, 如果10S/次、每次100ms,编程5S一次,每次70ms则,吞吐量反而下降。
  4. --XX:GCTimeRatio:直接设置吞吐量大小,接收0<x<100的整数, 也就是垃圾收集时间占总时间的比率,如果为19,那么GC时间比为:1/(1+19),默认为99即则垃圾时间为1%。
  5. --XX:+UseAdaptiveSizePolicy,可以根据当前系统的运行情况收集监控信息,以提供最合适的停顿时间或者最大的吞吐量。
  6. 自适应调节策略也是Parallel Scavenge收集器与ParNew收集器的重要区别。

Serial Old(串行GC)收集器--老年代--标记-整理算法

Serial Old是Serial收集器的老年代版本,它同样使用一个单线程执行收集,使用 “标记-整理”算法。主要使用在 Client模式下的虚拟机。


Parallel Old(并发GC)收集器--老年代--标记-整理算法

Parallel Old是Parallel Scavenge收集器的老年代版本,使用 多线程和“标记-整理”算法。 适用场景:**Parallel Scavenge(新生代)+Parallel Old(老年代)可以适用高吞吐量优先的应用。


CMS(并发GC)收集器--老年代--标记-清除

CMS(Concurrent Mark Sweep)收集器是 获取最短停顿时间为目标的收集器。 适用于互联网站或者B/S系统的服务端上

分为四个步骤:

1. 初始标记

暂停其他用户线程,标记一下GC Roots能直接关联到得对象,速度很快。

2. 并发标记

进行GC Roots Tracing的过程。

3. 重新标记

暂停其他用户线程,是为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,停顿时间比出事标记长,但远比并发标记时间短。

4. 并发清除

与用户线程一起并发执行。

特点:

并发收集,低停顿。

缺点

  1. CMS对CPU资源分厂敏感。在并发阶段,它虽然不会导致用户线程停顿,但是会因为占用了一部分线程(或者说是CPU资源)导致应用程序变慢,总吞吐量会降低。
  2. 无法处理浮动垃圾,可能出现 ”concurrent mode failure“失败而导致另一次Full GC的产生。由于CMS并发清理阶段,用户线程还在运行着,伴随程序运行自然就还会有新的垃圾不断产生,这一部分垃圾出现在标记过程之后,CMS无法在档次收集中处理掉它们,只好留待下一次GC时再清理掉。这一部分垃圾就称为”浮动垃圾“。 在JDK1.5默认设置下,CMS收集器当老年代使用了68%的空间后就会被激活。可用参数,-XX:+UseCMSInitiationOccupancyFraction进行设置出发百分比,JDK1.6中启动阈值提升到了92%。
  3. 由于使用了"标记-清除"算法实现的收集器,会产生大量的空间碎片,会给分配大对象带来麻烦,会出现老年代剩余空间大,但是无法找到足够到大的连续空间来分配当前对象。会出发一次Full GC。-XX:+UseCMSCompactAtFullCollection开关(默认开启),Full Gc带来的问题肯定就是停顿时间变长。

G1收集器--标记-整理

G1(Garbage First)收集器是JDK1.7提供的一个新收集器,G1收集器基于“标记-整理”算法实现,也就是说不会产生内存碎片。还有一个特点之前的收集器进行收集的范围都是整个新生代或老年代,而G1将整个Java堆(包括新生代,老年代)。

特点

  1. 并行与并发:能充分利用多CPU,在缩短Stop-The_World停顿时间,部分其他收集器原本需要停顿Java线程执行的GC动作,G1仍然可以通过并发的方式让Java程序继续执行。
  2. 分代收集:
  3. 空间整合:标记整理算法,不会产生内存空间碎片。
  4. 可预测的停顿:

收集器参数大全

-XX:+<option> 启用选项
-XX:-<option> 不启用选项
-XX:<option>=<number>
-XX:<option>=<string>

参数描述

-XX:+UseSerialGC

Jvm运行在Client模式下的默认值,打开此开关后,使用Serial + Serial Old的收集器组合进行内存回收
-XX:+UseParNewGC打开此开关后,使用ParNew + Serial Old的收集器进行垃圾回收
-XX:+UseConcMarkSweepGC使用ParNew + CMS +  Serial Old的收集器组合进行内存回收,Serial Old作为CMS出现“Concurrent Mode Failure”失败后的后备收集器使用。
-XX:+UseParallelGCJvm运行在Server模式下的默认值,打开此开关后,使用Parallel Scavenge +  Serial Old的收集器组合进行回收
-XX:+UseParallelOldGC使用Parallel Scavenge +  Parallel Old的收集器组合进行回收
-XX:SurvivorRatio新生代中Eden区域与Survivor区域的容量比值,默认为8,代表Eden:Subrvivor = 8:1
-XX:PretenureSizeThreshold直接晋升到老年代对象的大小,设置这个参数后,大于这个参数的对象将直接在老年代分配
-XX:MaxTenuringThreshold晋升到老年代的对象年龄,每次Minor GC之后,年龄就加1,当超过这个参数的值时进入老年代
-XX:UseAdaptiveSizePolicy动态调整java堆中各个区域的大小以及进入老年代的年龄
-XX:+HandlePromotionFailure是否允许新生代收集担保,进行一次minor gc后, 另一块Survivor空间不足时,将直接会在老年代中保留
-XX:ParallelGCThreads设置并行GC进行内存回收的线程数
-XX:GCTimeRatioGC时间占总时间的比列,默认值为99,即允许1%的GC时间,仅在使用Parallel Scavenge 收集器时有效
-XX:MaxGCPauseMillis设置GC的最大停顿时间,在Parallel Scavenge 收集器下有效
-XX:CMSInitiatingOccupancyFraction设置CMS收集器在老年代空间被使用多少后出发垃圾收集,默认值为68%,仅在CMS收集器时有效,-XX:CMSInitiatingOccupancyFraction=70
-XX:+UseCMSCompactAtFullCollection
由于CMS收集器会产生碎片,此参数设置在垃圾收集器后是否需要一次内存碎片整理过程,仅在CMS收集器时有效
-XX:+CMSFullGCBeforeCompaction
设置CMS收集器在进行若干次垃圾收集后再进行一次内存碎片整理过程,通常与UseCMSCompactAtFullCollection参数一起使用
-XX:+UseFastAccessorMethods
原始类型优化
-XX:+DisableExplicitGC
是否关闭手动System.gc
-XX:+CMSParallelRemarkEnabled
降低标记停顿
-XX:LargePageSizeInBytes
内存页的大小不可设置过大,会影响Perm的大小,-XX:LargePageSizeInBytes=128m

Client、Server模式默认GC

 新生代GC方式老年代和持久GC方式

Client

Serial 串行GCSerial Old 串行GC
ServerParallel Scavenge  并行回收GCParallel Old 并行GC

Sun/oracle JDK GC组合方式

 新生代GC方式老年代和持久GC方式

-XX:+UseSerialGC

Serial 串行GCSerial Old 串行GC
-XX:+UseParallelGCParallel Scavenge  并行回收GCSerial Old  并行GC
-XX:+UseConcMarkSweepGCParNew 并行GCCMS 并发GC
当出现“Concurrent Mode Failure”时
采用Serial Old 串行GC
-XX:+UseParNewGCParNew 并行GCSerial Old 串行GC
-XX:+UseParallelOldGCParallel Scavenge  并行回收GCParallel Old 并行GC
-XX:+UseConcMarkSweepGC
-XX:+UseParNewGC
Serial 串行GCCMS 并发GC 
当出现“Concurrent Mode Failure”时
采用Serial Old 串行GC

摘自:《深入理解Java虚拟机 JVM高级特性与最佳实践》 参考:http://blog.csdn.net/java2000_wl/article/details/8030172

]]>
jvm【二】jvm内存模型&jvm常用参数 2015-12-15T00:00:00+00:00 nemotan http://nemotan.github.io//2015/12/jvm【二】jvm内存模型&jvm常用参数 转自:(原文) [toc]

JVM规定

《The Java Virtual Machine Specification》中将JVM内存结构(又称运行时数据区Runtime Data Area)分为六部分(参看第三章)

  1. The pc Register;
  2. Java Virtual Machine Stacks;
  3. Heap;
  4. Method Area;
  5. Runtime Constant Pool;
  6. Native Method Stacks;

以上数据区的具体描述可参考规范。需要注意的是,以上只是一个规范说明,并没有规定虚拟机如何实现这些数据区。Sun JDK实现将内存空间划分为方法区、堆、本地方法栈、JVM方法栈、PC寄存器五部分。

如图:

内存空间详解

PC寄存器和JVM方法栈

每个线程都会拥有以及创建一个属于自己的 PC寄存器和JVM方法栈,PC寄存器占用的有可能为CPU寄存器或者OS内存,而JVM栈占用的为OC内存。

每运行一个方法,便会将方法的信息压入JVM方法栈中,同时将当前执行方法放入PC寄存器中(需要注意的是,如果当前方法为Native方法,PC寄存器的值为空)。 可以想到,如果方法栈太深,如递归方法,便会报StackOverflowError,同样如果占用空间太多,也会报OutOfMemoryError。 需要修改JVM参数设置:-Xss××k,在××中填入数字。

本地方法栈

同JVM方法栈一样,本地方法栈存放的是native方法的调用的状态。在Sun JDK的实现中,本地方法栈和JVM方法栈是同一个。

方法区

方法区存放了要加载 类的信息(名称、修饰符等)、类的静态变量、类中定义为fianl类型的常量、类中的Field信息、类中的方法信息, 你用Class对象的方法,如getName()、getFields()等来获取信息时,这些数据都来自方法区。需要注意的是, Runtime Constant Pool(常量池)也存放在方法区中。

方法区是被同一个JVM所有线程所共享的,在Sun JDK中这块区域对应Permanet Generation(持久代),默认最小值为16MB,最大值为64MB,可通过-XX:PermSize及-XX:MaxPermSize来指定。当方法区无法满足分配请求时,会报OutOfMemoryError。

堆用于存放 对象实例以及数组值,可以认为所有通过 ==new==来创建的 对象的内存均在此分配。一般所说的 ==GC==,大部分都是对堆进行的。

堆在32位操作系统上最大为2GB,在64位的则没有限制,大小通过-Xms和-Xmx来控制。-Xms为JVM启动时申请的最小堆内存,默认为物理内存的1/64但小于1GB;-Xmx为JVM可申请的最大堆内存,默认为物理内存的1/4但小于1GB,默认当空余堆内存小于40%的时候,JVM会将堆增大到-Xmx指定大小,可通过-XX:MinHeapFreeRatio=来指定比例,空余堆大于70%时,会将堆大小降到-Xms指定大小,这个参数可用-XX:MaxHeapFreeRatio=来指定。但对于运行系统来说,会避免频繁调整堆大小,会将-Xms和-Xmx的值设为一样。

为了让内存回收更加高效,Sun JDK从1.2开始对堆采取了分代管理的方法,如下图:

新生代

大多数的新建对象都是从新生代中分配内存,新生代由Eden(伊甸园) Space和两块相同的Survivor Space(S0,S1或者From,To)构成。

可通过-Xmn参数来指定新生代大小,-XX:SurvivorRatio来调整Eden与S Space的大小。

旧生代

用于存放新生代经过多次垃圾回收仍然存活的对象,像Cache。同时新建的对象也有可能在旧生代上直接分配内存,一般来说是比较的对象,即:单一大对象以及大数组,-XX:PretenureSizeThreshold = 1024 (byte, default = 0)可用来代表单一对象超过多大即不在新生代分配。

旧生代所占内存大小为-Xmx-(-Xmn)。

JVM常用参数

参数

配置 解释
-Xss××k 方法栈深度
-XX:PermSize 方法区内存最小值
-XX:MaxPermSize 方法区内存最大值
-Xms JVM启动分配最小堆内存
-Xmx JVM启动分配最大堆内存
-XX:MinHeapFreeRatio= 堆内存需扩展时,剩余内存最小比例,默认40%
-XX:MaxHeapFreeRatio= 堆内存需收缩时,剩余内存最大比例,默认70%
-Xmn 堆新生代内存大小
-XX:NewRatio= 如参数为4,则新生代与旧生代比例为1:4
-XX:SurvivorRatio= S0/S1占新生代内存的比例
-XX:PretenureSizeThreshold= 需要内存超过参数的对象,直接在旧生代分配
-XX:MaxTenuringThreshold= 设置垃圾最大年龄。如果为0,新生代对象不经过S区,直接进行旧生代,值较大的话,会增加新生代对象再GC的概率。

实例:根据GC日志猜测jvm参数

Heap def new generation total 6464K, used 115K [0x34e80000, 0x35580000, 0x35580000) eden space 5760K, 2% used [0x34e80000, 0x34e9cd38, 0x35420000) from space 704K, 0% used [0x354d0000, 0x354d0000, 0x35580000) to space 704K, 0% used [0x35420000, 0x35420000, 0x354d0000) tenured generation total 18124K, used 8277K [0x35580000, 0x36733000, 0x37680000) the space 18124K, 45% used [0x35580000, 0x35d95758, 0x35d95800, 0x36733000) compacting perm gen total 16384K, used 16383K [0x37680000, 0x38680000, 0x38680000) the space 16384K, 99% used [0x37680000, 0x3867ffc0, 0x38680000, 0x38680000) ro space 10240K, 44% used [0x38680000, 0x38af73f0, 0x38af7400, 0x39080000) rw space 12288K, 52% used [0x39080000, 0x396cdd28, 0x396cde00, 0x39c80000)

分析过程

-XX:PermSize = 16384K = 16M
-XX:MaxPermSize = (0x38680000-0x37680000/1024/1024 = 16M
-Xmx = (0x37680000-0x34e80000)/1024/1024 = 40M
-Xms = 3.7+17.7 = 21.5m
新生代=(6464k+704k)/1024=7M 3.7M
老年代=33M  17.7M
-XX:NewRatio= 33/7 约等于 5
-XX:SurvivorRatio=8(因为:s0 大小为 704k,新生代大小为:
6464+704=7168,704/7168=1/10)
-XX:+PrintGCDetails

因此得出的结果是: -XX:PermSize=16m -XX:MaxPermSize=16m -Xms22m -Xmx40m -XX:NewRatio=5 -XX:SurvivorRatio=8 -XX:+PrintGCDetails

小结

总的来说,所有语言的内存结构都大同小异,均分为堆、栈、区,堆放动态分配(alloc)的对象,栈存放临时变量、方法过程等,方法区则存放编译时确定的方法签名、常量池等。

学习博客:jvm精选blog学习 参考书籍:深入理解Java虚拟机 JVM高级特性与最佳实践

]]>
jvm【三】jvm中对象的创建与访问 2015-12-15T00:00:00+00:00 nemotan http://nemotan.github.io//2015/12/jvm【三】jvm中对象的创建与访问 [toc]

对象创建

1.new关键字

虚拟机遇到new 关键字的时候, 首先去常量池中寻找有没有这个类的符号引用,并且检查该引用的类是否已经被 加载,解析,和初始化(执行类构造器)过,如果没有则会先执行该类的加载过程, 在通过检查后, 虚拟机为该新生对象分配内存。

2.分配内存

为对象分配内存有俩种方式:

一种分配方式是“指针碰撞",在内存规整的时候, 已使用的内存在一侧,未使用的内存在一侧时,中间为指示器指针,这个时候的内存分配就是 把指示器指针向未使用的区域移动至创建的对象大小相等的距离。

另一种分配方式是“空闲列表”,当内存不规整时,虚拟机必须 在不连续的内存空间寻找一块适合对象大小的内存区域并使用一个列表去维护创建的每一个区域,并更新列表上的记录。

选择那种分配方式是由堆内存是否规整决定,又由所采用的gc是否带有压缩整理功能决定。

面临并发时时,有可能存在,虚拟机给对象A分配内存时指针还未来得及改变,这个时候同时又有B对象使用指针来分配内存解决这个问题的两种式:

一种是对分配内存空间的操作进行同步处理 ,虚拟机采用的CAS和失败重试的方式保证更新操作的原子性,另一种是把内存分配的动作按照线程划分在不同的空间进行,即每一个线程都在java堆中预先分配一小块内存。又称本地线程分配缓冲(Thread Local Allocation Buffer,简称TLAB)。 TLAB用完时分配新的TLAB 时需要同步锁定操作。虚拟机设置使用TLAB,可以通过-XX:+/UseTLAB参数设定。

3.初始化对象内存空间

内存分配完成之后,虚拟机对该对象分到的内存空间初始化为零值(除了对象头),如果使用了TLAB ,这一工作也可以提前至TLAB分配时进行。 初始化零值这一步也是为什么对象刚创建就可以使用的原因。

4.对象设置

虚拟机对对象进行设置,比如对象是那个类的实例,对象的哈希值,gc分带年龄等,这些信息都存在对象的头之中。之后就是执行方法,到此类创建结束。

内存布局

对象在内存中分三块区域, 对象头,实例数据,对齐填充。

java对象头部分俩个部分:一部分是用来存对象本身的运行时数据,比如:哈希code, gc分带年龄,锁状态标志,线程持有的锁,偏向线程ID,偏向时间戳等

另一部分是类型的指针,指向类元数据,虚拟机通过这个指针确定它属于那个类的实例,查找对象的元数据信息,并不一定需要经过对象本身。

实例数据部分是对象真正存储的有效信息,也是代码中所定义的类型的字段内容,无论是父类还是子类的都需要记录。

对齐填充不是必然存在的,它只是起占位符的作用,HotSpot VM的自动内存管理系统要求对象的起始地址必须是8字节的整数倍,如果不是则需要通过对齐填充来补全。

对象访问

对象访问会涉及到Java栈、Java堆、方法区这三个内存区域。 如下面这句代码:

Object objectRef = new Object();  

假设这句代码出现在方法体中,"Object objectRef” 这部分将会反映到Java栈的本地变量中,作为一个reference类型数据出现。而“new Object()”这部分将会反映到Java堆中,形成一块存储Object类型所有实例数据值的结构化内存,根据具体类型以及虚拟机实现的对象内存布局的不同,这块内存的长度是不固定。另外,在java堆中还必须包括能查找到此对象类型数据(如对象类型、父类、实现的接口、方法等)的地址信息,这些数据类型存储在方法区中。

reference类型在java虚拟机规范里面只规定了一个指向对象的引用地址,并没有定义这个引用应该通过那种方式去定位,访问到java堆中的对象位置,因此不同的虚拟机实现的访问方式可能不同,主流的方式有两种:使用句柄和直接指针。

句柄访问方式

句柄访问方式:java堆中将划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据和类型数据各自的具体地址信息。

指针访问方式

指针访问方式:reference变量中直接存储的就是对象的地址,而java堆对象一部分存储了对象实例数据,另外一部分存储了对象类型数据。

这两种访问对象的方式各有优势,使用句柄访问方式最大好处就是reference中存储的是稳定的句柄地址,在对象移动时只需要改变句柄中的实例数据指针,而reference不需要改变。使用指针访问方式最大好处就是速度快,它节省了一次指针定位的时间开销,就虚拟机而言,它使用的是第二种方式(直接指针访问)。

转自1:http://my.oschina.net/u/272065/blog/506903?p={"output"=>nil, "content"=>"[toc]\n\n## 对象创建\n### 1.new关键字\n虚拟机遇到new 关键字的时候, 首先去常量池中寻找有没有这个类的符号引用,并且检查该引用的类是否已经被 加载,解析,和初始化(执行类构造器)过,如果没有则会先执行该类的加载过程, 在通过检查后, 虚拟机为该新生对象分配内存。\n### 2.分配内存\n为对象分配内存有俩种方式: \n\n一种分配方式是“指针碰撞\",在内存规整的时候, 已使用的内存在一侧,未使用的内存在一侧时,中间为指示器指针,这个时候的内存分配就是 把指示器指针向未使用的区域移动至创建的对象大小相等的距离。\n\n另一种分配方式是“空闲列表”,当内存不规整时,虚拟机必须 在不连续的内存空间寻找一块适合对象大小的内存区域并使用一个列表去维护创建的每一个区域,并更新列表上的记录。\n\n>选择那种分配方式是由堆内存是否规整决定,又由所采用的gc是否带有压缩整理功能决定。\n\n当 面临并发时时,有可能存在,虚拟机给对象A分配内存时指针还未来得及改变,这个时候同时又有B对象使用指针来分配内存解决这个问题的两种式:\n\n一种是对分配内存空间的操作进行同步处理 ,虚拟机采用的[CAS](http://www.blogjava.net/xylz/archive/2010/07/04/325206.html)和失败重试的方式保证更新操作的原子性,另一种是把内存分配的动作按照线程划分在不同的空间进行,即每一个线程都在java堆中预先分配一小块内存。又称本地线程分配缓冲(Thread Local Allocation Buffer,简称TLAB)。 TLAB用完时分配新的TLAB 时需要同步锁定操作。虚拟机设置使用TLAB,可以通过-XX:+/UseTLAB参数设定。\n\n### 3.初始化对象内存空间\n内存分配完成之后,虚拟机对该对象分到的内存空间初始化为零值(除了对象头),如果使用了TLAB ,这一工作也可以提前至TLAB分配时进行。 初始化零值这一步也是为什么对象刚创建就可以使用的原因。\n\n### 4.对象设置\n\n虚拟机对对象进行设置,比如对象是那个类的实例,对象的哈希值,gc分带年龄等,这些信息都存在对象的头之中。之后就是执行方法,到此类创建结束。\n\n## 内存布局\n对象在内存中分三块区域, 对象头,实例数据,对齐填充。\n\njava对象头部分俩个部分:一部分是用来存对象本身的运行时数据,比如:哈希code, gc分带年龄,锁状态标志,线程持有的锁,偏向线程ID,偏向时间戳等\n\n另一部分是类型的指针,指向类元数据,虚拟机通过这个指针确定它属于那个类的实例,查找对象的元数据信息,并不一定需要经过对象本身。\n\n实例数据部分是对象真正存储的有效信息,也是代码中所定义的类型的字段内容,无论是父类还是子类的都需要记录。\n\n对齐填充不是必然存在的,它只是起占位符的作用,HotSpot VM的自动内存管理系统要求对象的起始地址必须是8字节的整数倍,如果不是则需要通过对齐填充来补全。\n## 对象访问\n对象访问会涉及到Java栈、Java堆、方法区这三个内存区域。\n如下面这句代码:\n\n{% highlight java %}\nObject objectRef = new Object(); \n{% endhighlight %}\n假设这句代码出现在方法体中,\"Object objectRef” 这部分将会反映到Java栈的本地变量中,作为一个reference类型数据出现。而“new Object()”这部分将会反映到Java堆中,形成一块存储Object类型所有实例数据值的结构化内存,根据具体类型以及虚拟机实现的对象内存布局的不同,这块内存的长度是不固定。另外,在java堆中还必须包括能查找到此对象类型数据(如对象类型、父类、实现的接口、方法等)的地址信息,这些数据类型存储在方法区中。\n \nreference类型在java虚拟机规范里面只规定了一个指向对象的引用地址,并没有定义这个引用应该通过那种方式去定位,访问到java堆中的对象位置,因此不同的虚拟机实现的访问方式可能不同,主流的方式有两种:使用句柄和直接指针。\n\n### 句柄访问方式\n句柄访问方式:java堆中将划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据和类型数据各自的具体地址信息。\n\n\n### 指针访问方式\n指针访问方式:reference变量中直接存储的就是对象的地址,而java堆对象一部分存储了对象实例数据,另外一部分存储了对象类型数据。\n\n\n这两种访问对象的方式各有优势,使用句柄访问方式最大好处就是reference中存储的是稳定的句柄地址,在对象移动时只需要改变句柄中的实例数据指针,而reference不需要改变。使用指针访问方式最大好处就是速度快,它节省了一次指针定位的时间开销,就虚拟机而言,它使用的是第二种方式(直接指针访问)。\n\n转自1:[http://my.oschina.net/u/272065/blog/506903?p={{page}}](http://my.oschina.net/u/272065/blog/506903?p={{page}})\n转自2:[http://blog.csdn.net/java2000_wl/article/details/8015105](http://blog.csdn.net/java2000_wl/article/details/8015105)\n\n", "relative_path"=>"posts/2015/2015-12-15-jvm【三】jvm中对象的创建与访问.md", "path"=>"posts/2015/2015-12-15-jvm【三】jvm中对象的创建与访问.md", "url"=>"/2015/12/jvm-%E4%B8%89-jvm%E4%B8%AD%E5%AF%B9%E8%B1%A1%E7%9A%84%E5%88%9B%E5%BB%BA%E4%B8%8E%E8%AE%BF%E9%97%AE/", "collection"=>"posts", "next"=>#<Jekyll::Document posts/2015/2015-12-15-jvm【二】jvm内存模型&jvm常用参数.md collection=posts>, "previous"=>#<Jekyll::Document posts/2015/2015-12-14-java中io包源码详解.md collection=posts>, "id"=>"/2015/12/jvm【三】jvm中对象的创建与访问", "draft"=>false, "categories"=>["jvm"], "date"=>2015-12-15 00:00:00 +0000, "layout"=>"post", "title"=>"jvm【三】jvm中对象的创建与访问", "tags"=>["jvm"], "slug"=>"jvm【三】jvm中对象的创建与访问", "ext"=>".md", "excerpt"=>"

[toc]

\n"}过,如果没有则会先执行该类的加载过程, 在通过检查后, 虚拟机为该新生对象分配内存。\n### 2.分配内存\n为对象分配内存有俩种方式: \n\n一种分配方式是“指针碰撞\",在内存规整的时候, 已使用的内存在一侧,未使用的内存在一侧时,中间为指示器指针,这个时候的内存分配就是 把指示器指针向未使用的区域移动至创建的对象大小相等的距离。\n\n另一种分配方式是“空闲列表”,当内存不规整时,虚拟机必须 在不连续的内存空间寻找一块适合对象大小的内存区域并使用一个列表去维护创建的每一个区域,并更新列表上的记录。\n\n>选择那种分配方式是由堆内存是否规整决定,又由所采用的gc是否带有压缩整理功能决定。\n\n当 面临并发时时,有可能存在,虚拟机给对象A分配内存时指针还未来得及改变,这个时候同时又有B对象使用指针来分配内存解决这个问题的两种式:\n\n一种是对分配内存空间的操作进行同步处理 ,虚拟机采用的CAS和失败重试的方式保证更新操作的原子性,另一种是把内存分配的动作按照线程划分在不同的空间进行,即每一个线程都在java堆中预先分配一小块内存。又称本地线程分配缓冲(Thread Local Allocation Buffer,简称TLAB)。 TLAB用完时分配新的TLAB 时需要同步锁定操作。虚拟机设置使用TLAB,可以通过-XX:+/UseTLAB参数设定。\n\n### 3.初始化对象内存空间\n内存分配完成之后,虚拟机对该对象分到的内存空间初始化为零值(除了对象头),如果使用了TLAB ,这一工作也可以提前至TLAB分配时进行。 初始化零值这一步也是为什么对象刚创建就可以使用的原因。\n\n### 4.对象设置\n\n虚拟机对对象进行设置,比如对象是那个类的实例,对象的哈希值,gc分带年龄等,这些信息都存在对象的头之中。之后就是执行方法,到此类创建结束。\n\n## 内存布局\n对象在内存中分三块区域, 对象头,实例数据,对齐填充。\n\njava对象头部分俩个部分:一部分是用来存对象本身的运行时数据,比如:哈希code, gc分带年龄,锁状态标志,线程持有的锁,偏向线程ID,偏向时间戳等\n\n另一部分是类型的指针,指向类元数据,虚拟机通过这个指针确定它属于那个类的实例,查找对象的元数据信息,并不一定需要经过对象本身。\n\n实例数据部分是对象真正存储的有效信息,也是代码中所定义的类型的字段内容,无论是父类还是子类的都需要记录。\n\n对齐填充不是必然存在的,它只是起占位符的作用,HotSpot VM的自动内存管理系统要求对象的起始地址必须是8字节的整数倍,如果不是则需要通过对齐填充来补全。\n## 对象访问\n对象访问会涉及到Java栈、Java堆、方法区这三个内存区域。\n如下面这句代码:\n\n{% highlight java %}\nObject objectRef = new Object(); \n{% endhighlight %}\n假设这句代码出现在方法体中,\"Object objectRef” 这部分将会反映到Java栈的本地变量中,作为一个reference类型数据出现。而“new Object()”这部分将会反映到Java堆中,形成一块存储Object类型所有实例数据值的结构化内存,根据具体类型以及虚拟机实现的对象内存布局的不同,这块内存的长度是不固定。另外,在java堆中还必须包括能查找到此对象类型数据(如对象类型、父类、实现的接口、方法等)的地址信息,这些数据类型存储在方法区中。\n \nreference类型在java虚拟机规范里面只规定了一个指向对象的引用地址,并没有定义这个引用应该通过那种方式去定位,访问到java堆中的对象位置,因此不同的虚拟机实现的访问方式可能不同,主流的方式有两种:使用句柄和直接指针。\n\n### 句柄访问方式\n句柄访问方式:java堆中将划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据和类型数据各自的具体地址信息。\n\n\n### 指针访问方式\n指针访问方式:**reference变量中直接存储的就是对象的地址,而java堆对象一部分存储了对象实例数据,另外一部分存储了对象类型数据。\n\n\n这两种访问对象的方式各有优势,使用句柄访问方式最大好处就是reference中存储的是稳定的句柄地址,在对象移动时只需要改变句柄中的实例数据指针,而reference不需要改变。使用指针访问方式最大好处就是速度快,它节省了一次指针定位的时间开销,就虚拟机而言,它使用的是第二种方式(直接指针访问)。\n\n转自1:http://my.oschina.net/u/272065/blog/506903?p={{page}}\n转自2:http://blog.csdn.net/java2000_wl/article/details/8015105\n\n", "relative_path"=>"posts/2015/2015-12-15-jvm【三】jvm中对象的创建与访问.md", "path"=>"posts/2015/2015-12-15-jvm【三】jvm中对象的创建与访问.md", "url"=>"/2015/12/jvm-%E4%B8%89-jvm%E4%B8%AD%E5%AF%B9%E8%B1%A1%E7%9A%84%E5%88%9B%E5%BB%BA%E4%B8%8E%E8%AE%BF%E9%97%AE/", "collection"=>"posts", "next"=>#<Jekyll::Document posts/2015/2015-12-15-jvm【二】jvm内存模型&jvm常用参数.md collection=posts>, "previous"=>#<Jekyll::Document posts/2015/2015-12-14-java中io包源码详解.md collection=posts>, "id"=>"/2015/12/jvm【三】jvm中对象的创建与访问", "draft"=>false, "categories"=>["jvm"], "date"=>2015-12-15 00:00:00 +0000, "layout"=>"post", "title"=>"jvm【三】jvm中对象的创建与访问", "tags"=>["jvm"], "slug"=>"jvm【三】jvm中对象的创建与访问", "ext"=>".md", "excerpt"=>"

[toc]

\n"}) 转自2:http://blog.csdn.net/java2000_wl/article/details/8015105

]]>
java中io包源码详解 2015-12-14T00:00:00+00:00 nemotan http://nemotan.github.io//2015/12/java中io包源码详解 [toc]

简介

流的概念和作用

流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。即数据在两设备间的传输称为流,流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。

流的分类

根据处理数据类型的不同分为: 字符流和字节流 根据数据流向不同分为: 输入流和输出流

字符流和字节流

字符流的由来: 因为数据编码的不同,而有了对字符进行高效操作的流对象。 本质其实就是基于字节流读取时,去查了指定的码表。字节流和字符流的区别:

  1. 读写单位不同: 字节流字节(8bit)为单位,字符流以 字符为单位,根据码表映射字符,一次可能读多个字节。

  2. 处理对象不同:字节流能处理 所有类型的数据(如图片、avi等),而字符流 能处理 字符类型的数据。

  3. 字节流在操作的时候本身是不会用到缓冲区的,是文件本身的直接操作的;而字符流在操作的时候下后是会用到缓冲区的,是通过缓冲区来操作文件,我们将在下面验证这一点。

结论: 优先选用字节流。首先因为硬盘上的所有文件都是以字节的形式进行传输或者保存的,包括图片等内容。但是字符只是在内存中才会形成的,所以在开发中,字节流使用广泛。

输入流和输出流

对输入流只能进行读操作,对输出流只能进行写操作,程序中需要根据待传输数据的不同特性而使用不同的流。

包结构

InputStream主要方法

//读取一个字节(8位),如果没有了返回-1
public abstract int read() throws IOException;

//读取一个数组这么多的字节,如果没有了返回-1 public int read(byte b[]) throws IOException { return read(b, 0, b.length); }

//从偏移量off开始读取len个字节到byte[]数组中,返回读到的所有字节 public int read(byte b[], int off, int len) throws IOException {...}

//掉过n个字节,返回跳过的字节综合 public long skip(long n) throws IOException {...}

//预计还可以读取的字节数 public int available() throws IOException {...}

// 调用mark方法会记下当前调用mark方法的时刻,InputStream被读到的位置,调用reset方法就会回到该位置 public synchronized void mark(int readlimit) {}

//将此流重新定位到对此输入流最后调用 mark 方法时的位置 public synchronized void reset() throws IOException {

//是否支持mark方法,InputStream默认不支持 public boolean markSupported() { return false; }

mark和reset方法举例

方法说明:调用mark方法会记下当前调用mark方法的时刻,InputStream被读到的位置。 调用reset方法就会回到该位置。

String content = "BoyceZhang!";
InputStream inputStream = new ByteArrayInputStream(content.getBytes());

// 判断该输入流是否支持mark操作 if (!inputStream.markSupported()) { System.out.println("mark/reset not supported!"); } int ch;
boolean marked = false;
while ((ch = inputStream.read()) != -1) {

//读取一个字符输出一个字符 System.out.print((char)ch);
//读到 'e'的时候标记一下 if (((char)ch == 'e')& !marked) {
inputStream.mark(content.length()); //先不要理会mark的参数 marked = true;
}

//读到'!'的时候重新回到标记位置开始读 if ((char)ch == '!' && marked) {
inputStream.reset();
marked = false; }
}

//程序最终输出:BoyceZhang!Zhang!

涉及的设计模式

装饰者模式

简介

InputStream的子类FilterInputStream是InputStream类的一个装饰者,譬如BufferedInputStream扩展了InputStream,该装饰者新增了缓存处理。

源码详解

 //在BufferedInputStream的父类FilterInputStream中关联了一个InputStream进行装饰。
 protected volatile InputStream in;

/* * Creates a <code>FilterInputStream</code> * by assigning the argument <code>in</code> * to the field <code>this.in</code> so as * to remember it for later use. * * @param in the underlying input stream, or <code>null</code> if * this instance is to be created without an underlying stream. / protected FilterInputStream(InputStream in) { this.in = in; }

//读取字节 public synchronized int read() throws IOException { if (pos >= count) {//buffer中的当前位置>buffer中读取了的位置 //需要进行填充buffer fill(); if (pos >= count) return -1; } return getBufIfOpen()[pos++] & 0xff; }

private void fill() throws IOException { byte[] buffer = getBufIfOpen(); if (markpos < 0) pos = 0; / no mark: throw away the buffer / else if (pos >= buffer.length) / no room left in buffer / if (markpos > 0) { / can throw away early part of the buffer / int sz = pos - markpos; System.arraycopy(buffer, markpos, buffer, 0, sz); pos = sz; markpos = 0; } else if (buffer.length >= marklimit) { markpos = -1; / buffer got too big, invalidate mark / pos = 0; / drop buffer contents / } else { / grow buffer / //每次填充2倍 int nsz = pos * 2; if (nsz > marklimit) nsz = marklimit; byte nbuf[] = new byte[nsz]; System.arraycopy(buffer, 0, nbuf, 0, pos); if (!bufUpdater.compareAndSet(this, buffer, nbuf)) { // Can't replace buf if there was an async close. // Note: This would need to be changed if fill() // is ever made accessible to multiple threads. // But for now, the only way CAS can fail is via close. // assert buf == null; throw new IOException("Stream closed"); } buffer = nbuf; } count = pos; int n = getInIfOpen().read(buffer, pos, buffer.length - pos); if (n > 0) count = n + pos; }

适配器模式

简介

ByteArrayInputStream是一个适配器类,ByteArrayInputStream继承了InputStream的接口,而封装了一个byte数组。换言之,它将一个byte数组的接口适配成InputStream流处理器的接口。

]]>
java中concurrent包详解 2015-12-14T00:00:00+00:00 nemotan http://nemotan.github.io//2015/12/java中concurrent包详解 [toc]

java中concurrent包详解

请见:http://blog.csdn.net/defonds/article/details/44021605

]]>