ubuntu eclipse mars.2Bug修复

ubuntu eclipse mars.2Bug的bug具体表现为图标异常,菜单无法正常点击切换等等,问题是因为GTK方面,将eclipse的启动方式eclipse.desktop改为一下代码

   [Desktop Entry]
   Name=eclipse
   Comment=develop java app
   Exec=env SWT_GTK3=0 /home/jiang/software/eclipse/eclipse
   Icon=/home/jiang/software/eclipse/icon.xpm
   Terminal=false
   Type=Application
   Categories=devtool

网站登录系统设计

一、前言

一个网站的登录系统可以算作是一个网站的大门,如何让这个大门足够的安全,并且又不用太复杂是一个很考验经验的工作。作为一个没有经验的新手,简单记下一点自己在做网站登录系统设计时做法。

二、前提

首先假设我们设计的网站是一个单服务器的应用,并且他有多种登录方式(邮箱,手机,用户名),以及多种登录途径(web,app)。

三、正文

1.会话状态

因为是单服务器的应用,所以不用担心多台服务器之间的会话状态转移的问题,所以为了简单,我们采用的是cookie方式,这样既不用在用户关闭浏览器后再次访问就要重新登录,也可以通过设定cookie的失效时间,来让浏览器自动在一定时间后让用户重新登录,保护用户设备丢失带来的风险。

2.登录方式

考虑到需要支持邮箱,手机或者是用户名的登录,也就是用户随意输入其中的一种以及密码都要可以登录,所以要对用户名有一定的限制,首先用户名不能重复,并且用户不能是手机或者邮箱,因此要对用户名做一些简单的限制——用户名不能为纯数字,也不能包含@符号。

3.密码存储

密码的存储是一定要足够安全的。因此对密码的存储一定要够安全。我采用的是密码+salt的方式,使用sha-256加密。

4.身份验证

身份验证上,我在cookie中填入用户id和token连接而成的字符串(称为id_token),例如24|dashdiuasdhgiuagsduigaiusdg这种形式,|符号前面是Id,后面是token,token是登录时随机生成的,在登录时会讲登录记录插入到数据库中,这条记录包括用户id,登录名(用户名,邮箱或手机中的任一种)、登录途径(web还是app)、登录时生成的token、登录时间等等,然后之后用户提交请求时都会验证cookie,将cookie中的id和token截取出来,与最新的一条该用户id的登录成功的记录的token对比,如果一致,则表示身份是对的。这种做法可以很好的防止伪造cookie。对于app来说,将这个cookie保存下来就可以一直作为登录凭证。如果遇到手机丢失,想使手机上的登录失效,重新用新手机登录一次即可。

5.多端登录的冲突解决

在4.身份验证中说到,登录的身份验证是根据最新的该用户的登录记录来判断的,所以如果用户在app上登录了,再在网站上登录则会使app的登录失效,为了解决这个问题,可以在登录时附带上登录方式的字段,那么就可以解决web端和app端的冲突问题。

linux下使用crontab进行网站内容和数据库定期备份

crontab有几个常用的命令

crontab -l 列举crontab的任务

crontab -e 编辑crontab的任务

crontab -r 删除crontab的任务

crontab -h crontab的帮助

crontab -i 删除crontab前进行提示

crontab -e进行编辑任务时会使用nano进行编辑,编辑命令如下示例。

分 时 每月的第几天 每周的周几 命令

前5个参数可以使用通配符 *

m h dom mon dow command

设置每两分钟扫描一次,清理过期记录

*/2 * * * * /usr/bin/php /var/www/poker/scan.php >> /$

设置每周二23:29分备份一次

59 23 * * 2 /bin/bash /home/back.sh
59 23 * * 2 /bin/bash /home/back.sh就是我用来定期备份的指令,主要就是配合一个shell脚本

#!/bin/bash
cd /home
mv backup/* oldbackup/
echo “old backup file has been moved to oldbackup folder”;
cd /home/backup
Now=date "+%Y-%m-%d-%H-%M-%S"
File_emlog=”backup-emlog-“${Now}”.sql”
mysqldump -u me -ppassword emlog > $File_emlog
File_java=backup-java-$Now.sql
mysqldump -u me -ppassword java > $File_java
File_plan=backup-plan-$Now.sql
mysqldump -u me -ppassword plan > $File_plan
File_sms=backup-sms-$Now.sql
mysqldump -u me -ppassword sms > $File_sms
File_tu=backup-tu-$Now.sql
mysqldump -u me -ppassword tu > $File_tu
File_wordpress=backup-wordpress-$Now.sql
mysqldump -u me -ppassword wordpress > $File_wordpress

echo “your databases backup successfully completed”;
#数据文件到这里备份完毕

#下面备份网站的文件
tar -czf /home/backup/back_www.tar.gz /var/www

JavaEE post Base64的图片丢失数据解决

项目中有个提交Base64编码的图片到服务器,服务器端转换成图片的二进制流存放到数据库中。在实际使用中发现,保存到数据库中图片文件有损坏。表现为前一部分二进制数据相同,到了后面就开始不同了。

一开始的步骤为:

st=>start: 准备Base64数据
op1=>operation: Post准备好的Base64数据
op2=>operation: 将数据并转换为byte[]
op3=>operation: 存储到数据库
e=>end

st->op1->op2->op3->end

为了找出问题,将post这个步骤省去,并将数据直接存成图片以便观察

st=>start: 准备Base64数据
op2=>operation: 将数据并转换为byte[]
op3=>operation: 存储成图片
e=>end

st->op2->op3->end

这一步保存的文件的大小是9.0k

st=>start: 准备Base64数据
op1=>operation: Post准备好的Base64数据
op2=>operation: 将数据并转换为byte[]
op3=>operation: 存储成图片
e=>end

st->op1->op2->op3->end

这一步保存的文件的大小是8.9k

上面两步对比可以说明数据在传输的时候出现了丢失的情况,于是我猜测是不是tomcat对post方式做了限制,发现不是。查询资料发现,Base64位数据传输的时候中间的+号容易发生丢失。解决方法是将post过来的数据先UrlEncode一下即可

发生这个问题的没有找到明确的可靠的解释,其中一片文章解释为javascript将+当做字符串连接符号处理导致丢失。

Java虚拟机学习记录(九)——类文件结构(上)

##一、前言
在Java开发中,可以通过javac将.java的源代码编译为.class的类文件。之前一直以为,只有java语言可以编译为.class。但是在前些天的学习中,了解到不仅仅是Java语言可以编译成.class文件然后运行在Java虚拟机上,Clojure、Groovy、JRuby、Jython、Scala等语言都可以运行在Java虚拟机上。觉得这真是太神奇了。今天这一章的内容刚好可以解释这些。

##二、Java虚拟机的无关系特点
一般都说Java是平台无关的,因为Java是运行在Java虚拟机上的,而Java虚拟机是开发成各个平台通用的。那么同时,Java虚拟机其实也是语言无关的,也就是说,Java虚拟机并不要求特定的语言。只要该语言可以被编译生成符合标准的class(类)文件,那么就是可以运行在Java虚拟机上了。那么,类文件的结构是什么样的呢?

##三、类文件的基本知识
##1.基本单位
类文件是以8位字节为基础单位的二进制流,没有任何分割符。当遇到需要占用8位以上的数据时,则会按高位在前的方式分割成若干个8位字节进行存储。
###2.存储数据的数据格式:无符号数和表。
无符号数属于基本数据类型。以u1,u2,u4,u8来分别代表1个字节,2个字节,4个字节,8个字节的无符号数,无符号数可以用来描述数字、索引引用、数量值或者安装UTF-8编码构成的字符串值。
表是由多个无符号数或者其他表作为数据项构成的复合数据类型,所有表都习惯性以_info结尾。

##三、类文件的结构
###1.魔数和版本号
class文件的前四个字节称为魔数(Magic Number),用来描述文件的格式,class文件的魔数是0xCAFEBABE。第五六个字节描述次版本号(Minor Version),第七八个字节描述主版本号。
###2.常量池
紧接着主版本号之后是常量池的入口。由于常量池的常量数量是变化的,所以在常量池的入口有一个u2类型(第9,10位)的数据,代表常量池容量计数值。不过这个计数是从1开始的,所以如果这个值是22,则代表有21个常量。
常量池中主要有两种类型:字面量(Literal)和符号引用(Symbolic References)。

a.字面量: 接近java语言的常量的概念,如文本字符串,声明为final的常量值等
b.符号引用:1.类和接口的全限定名(Fully Qualified Name) 2.字段的名称和描述符(Descriptor) 3.方法的名称和描述符

java代码在编译时不会像c/c++一样进行”连接”,这样在编译生成的class文件中不会保存各个方法、字段的最终内存布局信息,而是在运行的时候进行动态连接。也就是从常量池中获得方法、字段对应的符号引用,再在类创建或运行时解析翻译到具体的内存地址之中。

常量池中每一个常量都是一个表,在JDK1.7前共有11中常量,在JDK1.7中为了更好的支持动态语言调用,又额外增加了3种(CONSTANT_MethodHandle_info、CONSTANT_MethodType_info和CONSTANT_InvokeDynamic_info)。

这14个表的共同之处在于,表开始的第一项是一个u1类型的标志位(tag),代表当前这个常量属于哪种常量类型。

类型 标志 描述
CONSTANT_Utf8_info 1 UTF-8编码的字符串
CONSTANT_Integer_info 3 整型字面量
CONSTANT_Float_info 4 浮点型字面量
CONSTANT_Long_info 5 长整型字面量
CONSTANT_Double_info 6 双精度浮点型字面量
CONSTANT_Class_info 7 类或接口的符号引用
CONSTANT_String_info 8 字符串类型字面量
CONSTANT_Fieldref_info 9 字段的符号引用
CONSTANT_Methodref_info 10 类中方法的符号引用
CONSTANT_InterfaceMathodref_info 11 接口中方法的符号引用
CONSTANT_NameAndType_info 12 字段或方法的部分符号引用
CONSTANT_MethodHandle_info 15 表示方法句柄
CONSTANT_MethodType_info 16 表示方法类型
CONSTANT_InvokeDynamic_info 18 表示一个动态方法调用点

###3.访问标志
在常量池结束之后,紧接着的是访问标志,用来描述一些类和接口的访问信息,包括这个Class是类还是接口,是否定义为public,是否是abstract如果是类的话,是否是final。

###4.类索引,父类索引与接口索引集合
访问标志之后是类索引,父类索引与接口索引集合。类索引(this_class)和父类索引(super_class)都是一个u2类型的数据,而接口索引集合(interfaces)是一组u2类型的数据集合(对应java语言中的单继承和多接口实现),

###5.字段表集合
再之后是字段表集合。字段表(Field_info)用于描述接口或者类中声明的变量。字段(field)包括类级变量以及实例级变量,但不包括在方法内部声明的局部变量。一个字段的描述包括:字段的作用域(public,private,protected修饰符)、是实例变量还是类变量(static修饰符)、可变性(final)、并发可见性(volatile修饰符,是否强制从主内存读写)、可否被序列化(transient修饰符)、字段数据类型(基本类型,数组,对象)、字段名称。

###6.方法表集合
字段表之后是方法表集合。很显然,对方法的描述和对字段的描述是很像的。volatile和transient不能描述方法,但是syncronized、native、strictfp和abstract是方法独有的。

Java虚拟机学习记录(八) —— 虚拟机性能监控与故障处理工具

一、前言

我觉得Java的强大之处在于它有非常完善的生态环境,从开发工具到分析处理工具。使用JDK中提供的工具可以在遇到程序故障时快速定位故障发生的原因并进行调优。

二、JDK命令行工具

a、jps(JVM Process Status):虚拟机进程状况工具

jps可以用来列出正在运行的虚拟机进程,并显示虚拟机执行主类(Main class,main函数所在的类)名称以及这些进程的本地虚拟机唯一ID(Local Virtual Machine Identifier,LVMID)。

jps命令格式:
jps [option] [hostid]
使用范例:
jiang@jiang-HP-ENVY-Notebook:~$ jps -l
6084 /home/jiang/eclipse//plugins/org.eclipse.equinox.launcher_1.3.200.v20160318-1642.jar
6152 sun.tools.jps.Jps
jps可以通过RMI协议查询开启了RMI服务的远程虚拟机进程状态,hostid为RMI注册表中注册的主机名。

选项 作用
-q 只输出LVMID,省略主类的名称
-m 输出虚拟机进程启动时传递给主类main()函数的参数
-l 输出主类的全名,如果进程执行的是Jar包,输出Jar路径
-v 输出虚拟机进程启动时JVM参数

b、jstat(JVM Statistics Monitoring Tool): 虚拟机统计信息监视工具

jstat是用于监视虚拟机各种运行状态信息的命令行工具。它可以显示本地或者远程虚拟机进程中的类加载、内存、垃圾收集、JIT编译等运行数据。

jstat命令格式为:
jstat [option vmid [interval [s|ms] [count]] ]
如果VMID是本地进程,和LVMID是一样的,如果是远程虚拟机进程,那VMID的格式是:
[protocol:][//]lvmid[@hostname[:port]/servername]
参数inerval和count代表查询间隔和次数,如果省略则表示只查询一次。
使用范例:
jstat -gc 2764 250 20
代表每250ms查询一次进程2764垃圾收集状况,一共查询20次

选项 作用
-class 监视类装载,卸载数量,总空间以及类装载所耗费的时间
-gc 监视Java堆状况,包括Eden区,两个survivor区,老年代,永久代等的容量、已用空间、GC时间合计等信息
-gccapacity 监视内容与-gc基本相同,但输出主要关注Java堆各个区域使用到的最大、最小空间
-gcutil 监视内容与-gc基本相同,但输出主要关注已使用空间占总空间的百分比
-gccause 与-gcutil功能一样,但会额外输出导致上一次GC的原因
-gcnew 监视新生代GC状况
-gcnewcapacity 监视新生代使用到的最大、最小空间
-gcold 监视老年代GC状况
-gcoldcapacity 监视老年代使用到的最大、最小空间
-gcpermcapacity 输出永久代使用到的最大、最小空间
-compiler 输出JIT编译器编译过的方法、耗时等信息
-printcompilation 输出已被JIT编译的方法

Java虚拟机学习记录(七)——内存分配与回收策略

一、前言

Java的内存分配,从全局来看,就是堆上分配(但也可能经过JIT编译后被拆散为标量类型并间接的栈上分配,对象的分配主要在新生代的Eden区上,如果开启了本地线程分配缓冲,将按线程优先在TLAB上分配。少数情况下也可能会直接分配在老年代中(大的对象直接进入老年代)。

二、对象优先在Eden分配

Java堆的新生代中,被分为Eden和两个survivor。大多数情况下,对象在Eden区优先分配,当Eden区没有足够的空间进行分配时,虚拟机将会发起一起Minor GC

新生代GC(Minor GC):指发生在新生代的垃圾回收动作。
老年代GC(Major GC/Full GC):指发生在老年代的GC。

三、大对象直接进入老年代

大对象指的是要占用大量连续内存空间的Java对象,最典型的大对象就是那种很长的字符串以及数组。大对象的内存分配堆虚拟机来说是一件很难的事,因为往往会因为找不到这样的连续的内存空间而不得不提前触发一次Full GC。因此,在写程序中要尽量避免这样大对象,尤其是生命周期很短的大对象。大对象的分配一般会直接进入老年代。(这里可以认为JVM是很不支持生命周期很短的大对象的创建的)。

四、长期存活的对象将进入老年代

虚拟机为每个对象定义了一个年龄计数器,在一次GC后仍然存活的对象它的年龄就会+1,如果对象在Eden出生并且经过第一次Minor GC仍然存活并且Survivor能够容纳就会被移到Survivor(其实就是标记-复制法)。当它的年龄达到一定的程度(默认为15岁),就会被移入老年代。可以通过-XX:MaxTenuringThreshold来设定这个年龄阈值。

五、动态年龄判定

为了更好地适应不同程序的内存情况,虚拟机并不是永远都要求对象的年龄必须达到了MaxTenuringThreshold才能进入老年代,如果在Survivor区中相同年龄所有对象的大小总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代(节省了一半以上的Survivor内存)。

六、空间分配担保

在新生代的Minor GC是采用的标记——复制法,所以一次Minor GC后会将存活的对象移到另一个空闲的Survivor区中,但是没有人可以保证一次GC后存活的对象是Survivor能够容纳的,那么就需要老年代的进行空间分配担保,以防止在容纳不下的情况下有后备的解决方案。所以过程是:

Minor GC发生之前:检查老年代的连续空间是否能够容纳下新生代的所有对象,如果可以,则可以确保Minor GC是正常的。如果不可以,虚拟机就会检查HandlePromotionFailure设置值是否允许担保失败。如果允许,那么就会检查老年代最大可用连续空间是否大于历次进入老年代对象的平均大小,如果大于,则尝试一次Minor GC;如果小于,或者不允许担保失败,就要进行一次Full GC(所以可以理解为Full GC是为了给Minor GC腾出空间,所以Full GC之后往往跟随着一次Minor GC)。

Java虚拟机学习记录(六)——HotSpot算法实现

一、前言

在JVM运行的过程中,垃圾回收是性能提升的重中之重,垃圾回收的前提是准确判定哪些对象是可以回收的,在前面的学习中说到,Java的大多数虚拟机都是通过可达性分析算法来判定对象能否回收。那么如何去找这些根节点(GC ROOTS)的位置也是一个要解决的问题。

二、HotSpot枚举根节点(GC Roots)

根节点主要在全局性的引用(常量和类的静态属性)和执行上下文中(例如栈帧的本地变量表中),因为这些区域的占用内存往往很大,不可能去逐个检索,因为这个操作太耗时了。

同样的,可达性分析的时间严格要求还体现在GC停顿上,GC停顿就是指在可达性分析期间所有的Java执行线程得全部停下来,等分析完成之后再开始重新运行,如果不停顿,就可能导致分析期间,引用关系还在不断的改变。很显然,这个停顿时间必须短不然用户体验极差。Sun把这个停顿叫做Stop the world。

HotSpot采用一种叫OopMap的数据结构来记录所有的执行上下文和全局引用位置,这样就可以直接得到所有的GC Roots的地址了。

但是在Java程序运行过程中,有很多指令会导致对象的引用关系发生变化,如果每个变化都要写入到OopMap中,那样就需要维护一个很大的OopMap数据结构,会占用大量的空间。所以在Jvm中有一个安全点(SafePoint)的概念。HotSpot只在安全点处记录了这些信息,然后开始GC。安全点的选定不能太少造成GC等待时间过长,也不能频繁GC增加运行负荷。

如何让所有线程跑到安全点时停止下来,有两种方案可以选择,抢先式中断(Preemptive Suspension)主动式中断(Voluntary Suspension)

其中抢先式中断不需要线程的执行代码主动配合,在GC发生时,首先让所有线程中断,如果发现线程中断的地方不在安全点上,就恢复线程,让它跑到安全点上。现在几乎没有虚拟机实现采用抢先式中断来暂停线程从而响应GC事件。

主动式中断的思想是当GC需要中断线程的时候,不直接对线程操作,仅仅简单地设置一个标志,各个线程去主动轮询这个标志,发现中断标志为真时就自己中断挂起。轮询标志的地方和安全点是重合的,另外加上创建对象需要分配内存的地方。

但是安全点只能很好的解决运行中的程序,对于”不运行”的程序也就也就无法进入安全点,也就无法进行GC。这里的不运行指的是线程没有分配到CPU时间,典型的例子就是线程处于Sleep状态或者Blocked状态,这时候线程是无法响应JVM的中断请求的,”走”安全的地方去中断挂起,JVM显然也不太可能等待线程重新被分配CPU时间。这种情况需要借助安全区域(Safe Region)来解决。

安全区域指的是在一段代码中,引用关系不会发生变化。在这个区域任意开GC都是安全的。我们也可以把安全区域当成是安全点的扩展。

当线程执行到安全区域中时,首先标识自己进入了安全区域。在这段时间内JVM发起GC就不用管安全区域里的线程了。但是在安全区域内的线程重新获得CPU时间要离开Safe Region时,要检查系统是否已经完成了根节点枚举。完成了才能离开否则就要等待完成。

##三 HotSpot垃圾收集器的实现

a.Serial收集器

这是一款最基本,发展历史最悠久的收集器。这个收集器是一个单线程收集器,并且在收集垃圾时,必须暂停其他所有工作线程。适用于作为Client模式下的虚拟机。

b.ParNew收集器

其实就是Serial收集器的多线程版本。在单CPU的环境中,性能比不上Serial收集器,但是多CPU的时候性能是要好过Serial收集器的。

c.parallel Scavenge收集器

新生代收集器,复制算法,这个收集器与其他收集器的区别在于,Parallel Scavenge收集器的目标是达到一个可控制的吞吐量。吞吐量=运行用户代码所花费的时间/(运行用户代码时间+垃圾收集时间)。而其他收集则是关注减少GC停顿的时间。

d.Serial Old收集器

是Serial收集器的老年代版本,使用标记整理算法。也是给Client模式下的虚拟机使用。在server模式下,还有两大用途:1.在JDK1.5之前和Parallel Scavenge配合使用; 2作为CMS收集器的后备预案,在并发收集发生Concurrent Mode Failure失败时使用。

e.Parallell Old收集器

是Parallel Scavenge收集器的老年代版本,使用多线程和标记-整理算法。配合paraller Scavenge使用。

f.CMS收集器

Concurrent Mark Sweep。以获取最短时间停顿为目标,基于“标记-清除”算法。包括四个步骤:
1.初始标记
2.并发标记
3.重新标记
4.并发清除
他有一下几个缺点:
1.对CPU资源敏感,对性能影响大
2.无法清理浮动垃圾
3.容易产生内存碎片,触发Full GC。

g.G1收集器

是一款面向服务器的垃圾收集器。有以下特点:
1.并行和并发
2.分代收集
3.空间整合
4.可预测的停顿

Java虚拟机学习记录(五)-垃圾收集器

一、前言

众所周知,Java与C的一个显著的区别在于c需要手动的去管理内存,而Java几乎不需要去做这样的处理。原因在于Java的虚拟机有一套自己的内存回收策略。

在Java虚拟机运行过程中,虚拟机栈、程序计数器、本地方法栈随着线程的生命周期的变化而变化,因此这一部分的内存是不需要额外的去回收。但是对于Java堆来说,几乎所有的对象的创建(这里之所以说几乎,是因为随着JIT编译器、对象逃逸分析和栈上分配的技术发展,部分对象不需要在堆中分配内存)都是在堆中进行,如果不作内存回收,很快就会被占满内存,那么怎么样去分析得到那些对象已经不再需要则是问题的关键。所以,要实现垃圾回收,先要判断对象是否已死,然后再对已死对象执行回收算法。下面记录几种常见的在垃圾回收中的算法

二、对象是否已死的判定——引用计数法

这种方法原理很简单,就是说每个对象增加一个引用就给他的计数器加1,当引用失效时(这里的引用失效,我觉得是说当程序的执行离开了对象的作用域,那么这个引用就算是失效了),计数器就减一。当计数器再次变为0时,这个对象就判定它已经可以回收了。

但是这种方法也有一个严重的问题,当ObjectA.instance = ObjectB;ObjectB.instance = ObjectA时,这里两个对象互相引用着导致引用计数一直不为0。因此在JVM的主流实现中,都不采用这种方法。据说python的GC算法就是引用计数加上辅助算法完成的。

三、对象是否已死的判定——可达性分析算法

可达性算法是借助树的数据结构的一个算法,通过判定一个对象是否可以通过树的根到达来确定其是否需要回收。引用一张来自网上的图片图片引用地址

可达性分析

在这里,虽然object 8,object 9,object 10,object 11,object 12都互相持有引用,但是因为从GC Roots中无法到达,所以可以判定为可回收对象。很好的解决了引用计数法的弊端。
在Java中,可作为GC Roots的对象包括:
虚拟机栈(栈帧中的本地变量表)中的引用对象
方法区中类静态属性引用的对象
方法区中常量引用的对象
本地方法栈中JNI引用的对象

四、垃圾回收算法——标记-清除算法

标记-清除算法共有两个阶段。标记和清除

标记是将内存空间扫描一遍,对所有可回收的对象做标记。清除是对内存空间再做一遍扫描,清除可回收的对象。这种方法有两个问题:一是要做两遍扫描,效率不高,二是会产生大量的内存碎片(回收的对象随机分布造成),当分配一个很大的对象时很有可能找不到这样的连续空间而提前触发一次垃圾回收。因此这种算法大多数的JVM的实现都不使用。

五、垃圾回收算法——复制算法

复制算法最大的特点是将内存空间分为大小相同的两部分。当开始垃圾回收时,只需将存活的对象移到另一块没有使用的内存空间,然后将使用的一边全部回收,这样的做法简单高效,但是对内存的浪费实在是太大了。就像是你买了8G的内存条只能使用4G,你肯定是不愿意的。

但是实际上,现在的商业虚拟机都采用这种算法的优化版本来回收新生代。因为在新生代中(Java堆会分为新生代[Young Generation]和年老代[Old Generation])中的对象绝大多数都是可回收的,那么实际上每次做垃回收时,新生代中存活的对象并不多。所以并不需要按照1:1进行空间分割,一般情况下,会将新生代分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden空间和一块Survivor空间,当进行垃圾回收时,将这两块空间中的存活对象移到另一块空闲的Survivor空间。然后将对象全部清除。这样只有10%的内存会被浪费。

经历了几次垃圾回收都依然存活的对象会被放置到年老代中,因此年老代中的对象都是不容易被回收的。

因为没有办法保证每次的垃圾回收过程存活的对象都不超过10%,所以当Survivor空间不够用时,需要依赖其他内存(这里指年老代)进行分配担保(Handle Promotion)。

六、垃圾回收算法——标记-整理法

上面的复制算法很明显不适用于年老代,因为年老代中的对象特点是存活率大。标记-整理算法与标记-清除算法类似,但是它不是直接对对象进行清除,而是将存活的对象向一端移动,然后直接清理掉端边界意外的内存。引用一张来自网络上的图片图片引用地址

此处输入图片的描述

七、扩展一下引用的知识

在四、五的判定对象是否已死中,均涉及到了引用。在上面的表述中,似乎只有引用和死亡两种状态。但是事实上,Java规定了四种引用状态来帮助Java程序获得更好的性能。怎么去理解这个,一般来说,当Java虚拟机的内存足够时,有的对象虽然已经不需要了,但是完全没有必要将它丢弃,只有在进行垃圾回收后内存仍然不足时才将这些对象回收。这样就增加了这些对象的复用。免去了下一次使用这些对象又要重新创建的问题。很多系统的缓存功能都符合这样的应用场景。

在JDK1.2后,Java扩充了引用的概念,分为强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)和虚引用(Phantom Reference)。

a.强引用
Object obj = new Object()这种就是强引用,只要这个引用存在,无论如何JVM都不会回收这些对象

b.软引用
软引用用来描述一些还有用但并非必需的对象。用软引用的对象在系统进行过垃圾回收仍然内存不足时才会进行回收。在JDK1.2之后,提供了SoftReference类来实现软引用。

c.弱引用
弱引用也是用来描述非必需的对象,但是强度比软引用还弱一点。在下一次GC时必定会回收。

d.虚引用
虚引用对对象的生存时间构不成影响,就和没有引用与之关联一样,所以任何时候的GC都会将其回收。对一个对象设置虚引用的唯一目的就是这个对象被回收时会收到一个系统通知。在JDK1.2以后,提供了PhantomReference类来实现虚引用。

用代码来检验一下软引用、弱引用和虚引用。

package test;

import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;

public class TestReference {
    public static boolean run = true;
    public static void main(String[] args){
        WeakReference<String> weakReference = new WeakReference<String>(new String("weak Reference"));
        SoftReference<String> softReference = new SoftReference<String>(new String("soft Reference"));
        final ReferenceQueue<String> queue = new ReferenceQueue<String>();
        new Thread(new Runnable() {

            @Override
            public void run() {
                while(run){
                    Object object = queue.poll();
                    if(object != null){
                        try {
                            Field referent = Reference.class.getDeclaredField("referent");
                            referent.setAccessible(true);
                            Object result = referent.get(object);
                            System.out.println("即将回收"+result.getClass()+(String)result);
                        } catch (NoSuchFieldException | SecurityException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        } catch (IllegalArgumentException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        } catch (IllegalAccessException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                    }
                }

            }
        }).start();
        PhantomReference<String> phantomReference = new PhantomReference<String>(new String("phantom Reference"), queue);

        System.out.println(weakReference.get());
        System.out.println(softReference.get());
        System.out.println(phantomReference.get());
        System.out.println("*****************下面开始GC******************");
        System.gc();        //System.gc只是建议系统进行垃圾回收,并不是立刻执行
        System.out.println(weakReference.get());
        System.out.println(softReference.get());
        System.out.println(phantomReference.get());
    }
}

上面这段代码的输出为

weak Reference
soft Reference
null
*****************下面开始GC******************
null
soft Reference
null
即将回收class java.lang.Stringphantom Reference

可以看到,软引用在gc之后仍然是存在的,而弱引用gc之后变成null了,虚引用一直为null。并且我们通过新开一个线程来检测虚引用被回收的通知。所以正确的使用soft Reference和weak Reference可以实现缓存和防止内存泄露,而虚引用一般用来实现细粒度的内存控制。比如下面代码实现一个缓存。程序在确认原来的对象要被回收之后,才申请内存创建新的缓存。Java幽灵引用的作用

八、总结

可以看出,不论是对java堆中内存空间进行分代,还是对引用进行四种类型的划分,都是为了解决在java程序运行过程中因为存在各种各样的对象,单一的垃圾回收算法没有办法高效的进行。事实上垃圾回收算法再不断的变化,每一种当前存在的垃圾回收算法都有它适应的运行环境。因此在什么时候使用什么样的垃圾回收算法,对于程序的性能来说也是至关重要的。

Java虚拟机学习记录(四)-对象的内存布局和访问定位

一、前言

jvm创建对象的过程分为类加载检查,分配对象空间,初始化类空间,设置对象信息,对象初始化。那么在分配对象空间时是如何分配的,怎么保证能够定位到对象的内存位置。

二、对象的内存布局

对象在内存中的布局可以分为三部分:对象头(Object Header),实例数据(Instance Data)和对齐填充(Padding)。

a.对象头

对象头有两部分,一部分是用于存储对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。
对象头的另外一部分是类型指针,即对象指向他的类元数据的指针(表示这个对象是哪个类实例化出来的)。并不是所有的虚拟机实现都必须在对象数据上保留类元数据的指针。因为查找对象的类元数据信息并不一定要经过对象本身(通过句柄)。另外,如果对象是一个Java数组,那在对象头中还必须要有一块用于记录数组长度的数据,因为虚拟机可以通过普通Java对象的类元数据信息确定Java对象大小,但是数据的类元数据中却无法确定数据的大小。

b.实例数据

存储对象中的各种类型的字段内容以及普通对象的指针(oops,Ordinary Object Pointers)。

c.对象填充

不是必然存在,没有特别意义,作为占位符存在。

三、对象的访问定位

建立对象是为了使用对象,对象的访问是通过栈上的reference数据来操作堆上的具体对象。reference是java虚拟机规范的一个指向对象的引用,但并没有规定如何去具体实现,一般来说,有两种实现方式:句柄和直接指针。

a.句柄

采用句柄方式会在Java堆中开辟出一块句柄池空间,Java栈中的本地变量表中存放着指向句柄池中某一个句柄的reference,然后句柄保存有指向某个实例的指针和指向方法区的对象类元数据。
reference---->句柄------>对象和类元数据,共三次指针定位
这种方式的优点是GC清理垃圾时会移动对象地址,栈中的reference不需要改变只需要改变句柄中指向对象的指针。

b.直接指针访问

reference---->对象实例数据(对象实例数据的对象头包含类元指针)------>类元数据,共两次指针定位
这种方式的优点是速度更快,节省了一次指针定位的时间。Sun HotSpot采用的就是这种对象的访问定位方式。

c.注意

在JDK1.8中,已经完全移除了方法区,类元数据的存储放在了本地内存中,这样就不会再收到方法区大小的限制。