• MemoryAnalyzer介绍及使用

    Eclipse Memory Analyzer  Tools(MAT)是一个功能丰富且轻量的 Java 堆内存分析工具,可以用来辅助发现内存泄漏减少内存占用。 使用 Memory Analyzer 来分析生产环境的 Java 堆转储文件,可以从数以百万计的对象中快速计算出对象的 Retained Size,查看是谁在阻止垃圾回收,并自动生成一个 Leak Suspect(内存泄露可疑点) 报表。Memory Analyzer 有两种使用方式: 一种是下载独立版本的 MAT, 一种是使用嵌入到 Eclipse 中的 MAT插件。 我这里是用的 eclipse 插件。 如果平时用的是其他 IDE, 可以尝试使用独立版 MAT。 前段时间整理了一下Memory Analyzer相关的内容,包括文档及几个适用案例,目录大致如下: Memory Analyzer使用文档(保存在CSDN); 关于Heap Dump; 使用MAT找出重复引用的jar或类; 使用MAT分析内存泄漏; 找出被空集合占用的内存; 使用MAT分析Java集合的使用。

    [阅读更多...]
  • IntelliJ Idea 复制粘贴的问题

    分析 尝试从外部复制内容向Idea工作空间内粘贴文件时,有一定的几率会发生复制粘贴失败的问题:复制了新的内容,粘贴的却还是早些时候复制的旧的内容。 我使用的IDEA是最新版(2016.1.3),操作系统是Win7/Win10。 这个问题是因为Idea的历史粘贴板已经满了,无法向历史粘贴板中写入新的内容导致的。这应该是Idea的一个BUG。 解决思路有两个: 增加历史粘贴板的深度; 将历史粘贴板中的部分内容删除,腾出空间来。 增加历史粘贴板的深度 要做这个调整需要修改设置了。选择File > Settings > Editor > General,下拉右侧的滚动条,找到 Maximum number of contents to keep in clipboard,修改这个对应的值。默认是5,修改成50好了。 下次要找这个设置项可以直接在设置的搜索框中搜索“max clipboard”。 删除历史粘贴板中的内容 打开历史粘贴板比较简单:使用Ctrl+Shift+V组合键即可。但是要删除历史粘贴板中的内容时却不知道该如何下手。 查了些资料,也是使用一个快捷键:选中要删除的目标,使用Shift+Delete组合键即可完成删除。 在idea的默认KeyMap中,Shift+Delete执行的是cut(剪切)操作。而delete line的快捷键是Ctrl+Y。也都试验过,Ctrl+Y不起作用,只有Shift+Delete起作用。 #########

    [阅读更多...]
  • Java实现邮件发送过程中遇到的几个问题

    发送邮件的代码是我从以前的一个应用上直接拷贝过来的。以前使用的腾讯的邮件服务,程序执行起来没有任何问题。后来修改为微软office365邮件服务后,却遇到了两个问题。 问题一,tls加密设置 异常信息如下: 这个解决起来比较容易。找了些资料,添加如下配置即可: 问题二,提示协议为null: 异常信息如下: 这个问题是在将应用部署到生产环境后才遇到的。经检查后发现调用的jar包不是我在maven中指定的版本。后来确认是应用使用的jar包和容器(就是jetty)使用的jar包冲突了。容器使用的jar版本较旧,不过默认优先加载容器的jar。这样问题解决思路有两个: 依赖容器的jar重新写代码; 更新容器的jar。 第二个选择多少有些危险,就采用第一个选项好了,只需要修改一行即可: 这个问题在javax.mail 1.4版本中会出现。之后较高的版本会默认采用SMTP协议发送邮件。 修改后的程序: ##

    [阅读更多...]
  • 关于Heap Dump

    Heap Dump是什么? Heap Dump也叫堆转储文件,是一个Java进程在某个时间点上的内存快照。Heap Dump是有着多种类型的。不过总体上heap dump在触发快照的时候都保存了java对象和类的信息。通常在写heap dump文件前会触发一次FullGC,所以heap dump文件中保存的是FullGC后留下的对象信息。 我们可以通过Heap Dump做哪些事情? 一般在Heap Dump文件中可以获取到(这仍然取决于heap dump文件的类型)如下信息: 对象信息:类、成员变量、直接量以及引用值; 类信息:类加载器、名称、超类、静态成员; Garbage Collections Roots:JVM可达的对象; 线程栈以及本地变量:获取快照时的线程栈信息,以及局部变量的详细信息。 也就是说我们可以对上面这些内容进行分析。通常可以基于Heap Dump分析如下类型的问题: 找出内存泄漏的原因; 找出重复引用的jar或类; 分析集合的使用; 分析类加载器。 总而言之我们对Heap Dump的分析就是对应用的内存使用进行分析,从而更加合理地使用内存。 怎样获取Heap Dump? 获取heap dump有多种方式,可以通过参数配置在特定的条件下触发堆转储,也可以通过工具来获取。 1. 通过OutOfMemoryError获取heap dump 通过设置如下的JVM参数,可以在发生OutOfMemoryError后获取到一份HPROF二进制Heap Dump文件: 生成的文件会直接写入到工作目录。 这个方案适用于如下版本的虚拟机:Sun JVM (1.4.2_12 or higher and 1.5.0_07 or higher), HP-UX JVM (1.4.2_11 or higher) and SAP JVM (since 1.5.0)。 2. 主动触发Heap Dump 也可以为虚拟机设置下面的参数,这样就可以在需要的时候按下CTRL+BREAK组合键随时获取一份heap dump文件。 3. 使用HPROF agent 使用agent可以在程序执行结束时或者收到SIGQUIT信号时生成dump文件。使用agent也是要配置虚拟机参数的: 这种方式我没有试过,具体如何做的不是很清楚。 4. 使用jmap 这个算是用的最多的方式了吧。也很简单,执行下面的命令就可以了: 5. 使用jconsole 启动一个应用后,打开<JAVA_HOME>/bin/jconsole.exe,在jconsole中选择正在运行的应用: (在这个例子中选择的是eclipse,虽然进程名称为空,但是通过重启eclipse可以确认)。 建立连接后,选择页签MBean,执行com.sun.management. HotSpotDiagnostic下的操作dumpHeap。第一个参数p0是要获取的dump文件的完整路径名,记得文件要以.hprof作为扩展名(要在Memory AnalysisPerspective下打开扩展名必须是这个)。如果我们只想获取live的对象,第二个参数p1需要保持为true。 建议将导出的dump文件保存到一个独立的文件夹,在接下来的分析中会通过这个文件创建很多图表文件。 6. 使用Memory Analyzer 如果要获取dump文件的Java进程和Memory Analyzer在同一台机器上,可以直接使用Memory Analyzer获取dump文件。采用这种方式获取dump文件后会直接将之解析并在Memory Analyzer中打开(这个方式我不太喜欢,因为会在用户目录下生成很多零碎的文件)。 获取heap dump是受虚拟机支持的一种特定的功能。Memory Analyzer提供了一些被称为Heap Dump Provider的概念:比如支持Sun虚拟机(需要用到Sun JDK的jmap功能)的Provider或支持IBM虚拟机(也需要一个IBM JDK)的Provider。此外,Memory Analyzer也提供了对用户自定义的Heap Dump Provider插件的扩展支持。 要使用Memory Analyzer获取dump文件需要先打开Memory AnalysisPerspective,而后点击File -> Acquire Heap Dump…菜单项。之后会打开如下窗口: 在上图中,根据具体的运行环境,预装的Heap Dump Provider按照默认设置展示了当前正在运行的java进程列表。一些Heap Dump Provider也允许(或者说需要)设置一些额外的参数(比如heap dump的类型),这个可以点击Configure按钮来设置。选择要dump的线程,点击Next按钮就可以生成dump文件了。 有的时候进程列表可能为空,这时有必要调整下Heap Dump Provider的设置了。点击Configure按钮,选择合适的provider并点击,然后找到要做调整的参数并做设置就好了。 此外,IBM的虚拟机还有自己的一套方法,这里就不写出了。 就这样! #######

    [阅读更多...]
  • Lambda表达式2 – 语法说明

    语法说明 一个lambda表达式由如下几个部分组成: 1. 在圆括号中以逗号分隔的形参列表。在CheckPerson.test方法中包含一个参数p,代表了一个Person类的实例。注意:lambda表达式中的参数的类型是可以省略的;此外,如果只有一个参数的话连括号也是可以省略的。比如上一节曾提到的代码: 2. 箭头符号:->。用来分隔参数和函数体。 3. 函数体。由一个表达式或代码块组成。在上一节的例子中使用了这样的表达式: 如果使用的是表达式,java运行时会计算并返回表达式的值。另外,还可以选择在代码块中使用return语句: 不过return语句并不是表达式。在lambda表达式中需要将语句用花括号括起来,然而却没有必要在只是调用一个返回值为空的方法时也用花括号括起来,所以如下的写法也是正确的: lambda表达式和方法的声明看起来有很多类似的地方。所以也可以把lambda表达式视为匿名方法,也就是没有定义名字的方法。 以上提到的lambda表达式都是只使用了一个参数作为形参的表达式。下面的实例类,Caulator,演示了如何使用多个参数作为形参: 代码中operateBinary方法使用了两个整型参数执行算数操作。这里的算数操作本身就是IntegerMath接口的一个实例。在上面的程序中使用lambda表达式定义了两个算数操作:addition和subtraction。执行程序会打印如下内容: 访问外部类的局部变量 类似于局部类或匿名类,lambda表达式也可以访问外部类的局部变量。不同的是,使用lambda表达式时无需考虑覆盖之类的问题。lambda表达式只是一个词法上的概念,这意味着它不需要从超类中继承任何名称,也不会引入新的作用域。也就是说,在lambda表达式中的声明和在它的外部环境中的声明意义是一样的。在下面的例子中对此作了演示: 这段代码会输出如下内容: 如果使用示例中lambda表达式myConsumer中的参数y替换为x,编译器就会报错: 编译器报错信息是:“variable x is already defined in method methodInFirstLevel(int)”,就是说在方法methodInFirstLevel中已经定义了变量x。报错是因为lambda表达式不会引入新的作用域。也因此呢,可以在lambda表达式中直接访问外部类的域字段、方法以及形参。在这个例子中,lambda表达式myConsumer直接访问了方法methodInFirstLevel的形参x。而访问外部类的成员时也是直接使用this关键字。在这个例子中this.x指的就是FirstLevel.x。 然而,和局部类或匿名类一样,lambda表达式也只能访问局部变量或外部被声明为final(或等同于final)的成员。比如,我们将示例代码methodInFirstLevel方法中“x=99”前面的注释去掉: 因为在这段语句中修改了参数x的值,使得methodInFirstLevel的参数x不可以再被视为final式的。因此java编译器就会在lambda表达式访问局部变量x的地方报出类似“local variables referenced from a lambda expression must be final or effectively final”这样的错误。 目标类型 该如何判断lambda表达式的类型呢。再来看一下筛选适龄服兵役人员的代码: 这段代码在两处用到过: public static void printPersons(List<Person> roster, CheckPerson tester) —— 方案三 public void printPersonsWithPredicate(List<Person> roster, Predicate<Person> tester) —— 方案六 在调用printPersons方法时,这个方法期望一个CheckPerson 类型的参数,此时上面的那个表达式就是一个CheckPerson 类型的表达式。在调用printPersonsWithPredicate方法时,期望一个Predicate<Person>类型的参数,此时同样的表达式就是Predicate<Person>类型的。像这样子的,由方法期望的类型来决定的类型就叫做目标类型(其实我觉得scala中的类型推断用在这里更合适)。java编译器就是通过目标类型的上下文语境或者发现lambda表达式时的位置来判断lambda表达式的类型的。这也就意味着只能在Java编译器可以推断出类型的位置使用Lambda表达式: 变量声明; 赋值; 返回语句; 数组初始化; 方法或者构造器参数; lambda表达式方法体; 条件表达式(?:); 抛出异常时。 目标类型和方法参数 对于方法参数,Java编译器还需要依赖两个语言特性来决定目标类型:重载解析和类型参数推断。 看一下下面的这两个函数式接口( java.lang.Runnable and java.util.concurrent.Callable<V>): Runnable.run()方法没有返回值,而Callable.call()方法有。 假设我们像下面这样重载了invoke方法: 那么在下面的语句中将会调用哪个方法呢: 调用的是invoke(Callable<T>),因为这个方法有返回值,而invoke(Runnable<T>)没有返回值。在这种情况下lambda表达式(() -> “done”)的类型是Callable<T>。 序列化 如果一个lambda表达式的目标类型还有它调用的参数的类型都是可序列化的,那么lambda表达式也是可序列化的。然而就像内部类一样,强烈不建议对lambda表达式进行序列化。 #########

    [阅读更多...]
  • Lambda表达式1 – 一个简单用例

    我对java中lambda表达式的看法是相当纠结的: 一个我这么想:lambda表达式降低了java程序的阅读体验。java程序一直不以表现力出众,正相反使Java流行的一个因素正是它的安全和保守——即使是初学者只要注意些也能写出健壮且容易维护的代码来。lambda表达式对开发人员的要求相对来说高了一层,因此也增加了一些维护难度。 另一个我这么想:作为一个码代码的,有必要学习并接受语言的新特性。如果只是因为它的阅读体验差就放弃它在表现力方面的长处,那么即使是三目表达式也有人觉得理解起来困难呢。语言也是在发展的,跟不上的就自愿被丢下好了。 我不愿意被丢下。不过非让我做出选择的话,我的决定还是比较保守的:没必要一定在java语言中使用lambda——它让目前Java圈子中的很多人不习惯,会造成人力成本的上升。如果非常喜欢的话,可以考虑使用scala。 不管怎样,我还是开始试着掌握Lambda了,毕竟工作中维护的部分代码使用了Lambda(相信我,我会逐步把它去掉的)。学习的教程是在Oracla – Java官网的相关教程。 —————————— 假设目前正在创建一个社交网络应用。其中的一个特性是管理员可以对符合指定条件的会员执行某些操作,如发送消息。下面的表格详细描述了这个用例: Field 描述 名称 要执行的操作 主要参与者 管理员 前提条件 管理员登录系统 后置条件 只对符合指定标准的会员执行操作 主成功场景 1. 管理员对要执行操作的目标会员设置过滤标准;2. 管理员选择要执行的操作;3. 管理员点击提交按钮;4. 系统找到符合指定标准的会员;5. 系统对符合指定标准的会员执行预先选择的操作。 扩展 在选择执行操作前或者点击提交按钮前,管理员可以选择是否预览符合过滤标准的会员信息。 发生频率 一天中会发生许多次。 使用下面的Person类来表示社交网络中的会员信息: 假设所有的会员都保存在一个List<Person>实例中。 这一节我们从一个非常简单的方法开始,然后尝试使用局部类和匿名类进行实现,到最后会逐步深入体验Lambda表达式的强大和高效。可以在这里找到完整的代码。 方案一:一个个地创建查找符合指定标准的会员的方法 这是实现前面提到的案例最简单粗糙的方案了:就是创建几个方法、每个方法校验一项标准(比如年龄或是性别)。下面的一段代码校验了年龄大于一个指定值的情况: 这是一种很脆弱的方案,极有可能因为一点更新就导致应用无法运行。假如我们为Person类添加了新的成员变量或者更改了标准中衡量年龄的算法,就需要重写大量的代码来适应这种变化。再者,这里的限制也太过僵化了,比方说我们要打印年龄小于某个指定值的成员又该怎么做呢?再添加一个新方法printPersonsYoungerThan?这显然是一个笨方法。 方案二:创建更通用的方法 下面的方法较之printPersonsOlderThan适应性更好一些;这个方法打印了在指定年龄段内的会员信息: 此时又有新的想法了:如果我们要打印指定性别的会员信息,或者同时符合指定性别又在指定年龄段内的会员信息该怎么办呢?如果我们调整了Person类,添加了诸如友好度和地理位置这样的属性又该怎么办呢。尽管这样写方法要比printPersonsYoungerThan通用性更强一些,但是如果为每一种可能的查询都写一个方法也会导致代码的脆弱。倒不如把标准检查这一块代码给独立到一个新的类中。 方案三:在一个局部类中实现标准检查 下面的方法打印了符合检索标准的会员信息: 在程序里使用了一个CheckPerso对象tester对List参数roster中的每个实例进行校验。如果tester.test()返回true,就会执行printPerson()方法。为了设置检索标准,需要实现CheckPerson接口。 下面的这个类实现了CheckPerson并提供了test方法的具体实现。这个类中的test方法过滤了满足在美国服兵役条件的会员信息:即性别为男、且年龄在18~25岁之间。 要使用这个类,需要创建一个实例并触发printPersons方法: 现在的代码看起来不那么脆弱了——我们不需要因为Person类结构的变化而重写代码。不过这里仍有额外的代码:一个新定义的接口、为应用中每个搜索标准定义了一个内部类。 因为CheckPersonEligibleForSelectiveService实现了一个接口,所以可以使用匿名类,而不需要再为每种标准分别定义一个内部类。 方案四:使用匿名类实现标准检查 下面调用的printPersons方法中的一个参数是匿名类。这个匿名类的作用和方案三中的CheckPersonEligibleForSelectiveService类一样:都是过滤性别为男且年龄在18和25岁之间的会员。 这个方案减少了编码量,因为不再需要为每个要执行的检索方案创建新类。不过这样做仍让人有些不舒服:尽管CheckPerson接口只有一个方法,实现的匿名类仍是有些冗长笨重。此时可以使用Lambda表达式替换匿名类,下面会说下如何使用Lambda表达式替换匿名类。 方案五:使用Lambda表达式实现标准检查 CheckPerson接口是一个函数式接口。所谓的函数式接口就是指任何只包含一个抽象方法的接口。(一个函数式接口中也可以有多个default方法或静态方法)。既然函数式接口中只有一个抽象方法,那么在实现这个函数式接口的方法的时候可以省略掉方法的方法名。为了实现这个想法,可以使用Lambda表达式替换匿名类表达式。在下面重写的printPersons方法中,相关的代码做了高亮处理: 在这里还可以使用一个标准的函数接口来替换CheckPerson接口,从而进一步简化代码。 方案六:在Lambda表达式中使用标准函数式接口 再来看一下CheckPerson接口: 这是一个非常简单的接口。因为只有一个抽象方法,所以它也是一个函数式接口。这个抽象方法只接受一个参数并返回一个boolean值。这个抽象接口太过简单了,以至于我们会考虑是否有必要在应用中定义一个这样的接口。此时可以考虑使用JDK定义的标准函数式接口,可以在java.util.function包下找到这些接口。 在这个例子中,我们就可以使用Predicate<T>接口来替换CheckPerson。在这个接口中有一个boolean test(T t)方法: Predicate<T>接口是一个泛型接口。泛型类(或者是泛型接口)使用一对尖括号(<>)指定了一个或多个类型参数。在这个接口中只有一个类型参数。在使用具体类声明或实例化一个泛型类时,就会获得一个参数化类。比如说参数化类Predicate<Person>就是这样的: 在这个参数化类中有一个与CheckPerson.boolean test(Person p)方法的参数和返回值都一致的方法。因此就可以同如下方法所演示的一样使用Predicate<T>接口来替换CheckPerson接口: 然后使用下面的代码就可以像方案三中一样筛选适龄服兵役的会员了: 有没有注意到,这里使用Predicate<Person>作为参数类型时并没有显式指定参数类型。这里并不是唯一适用lambda表达式的地方,下面的方案会介绍更多lambda表达式的用法。 方案七:在整个应用中使用lambda表达式 再来看一下printPersonsWithPredicate 方法,考虑是否可以在这里使用lambda表达式: 在这个方法中使用Predicate实例tester检查了roster中的每个Person实例。如果Person实例符合tester中定义的检查标准,将会触发Person实例的printPerson方法。 除了触发printPerson方法,满足tester标准的Person实例还可以执行其他的方法。可以考虑使用lambda表达式指定要执行的方法(私以为这个特性很好,解决了java中方法不能作为对象传递的问题)。现在需要一个类似printPerson方法的lambda表达式——一个只需要一个参数且返回为void的lambda表达式。记住一点:要使用lambda表达式,需要先实现一个函数式接口。在这个例子中需要一个函数式接口,其中只包含一个抽象方法,这个抽象方法有个类型为Person的参数,且返回为void。可以看一下JDK提供的标准函数式接口Consumer<T>,它有一个抽象方法void accept(T t)正好满足这个要求。在下面的代码中使用一个Consumer<T>的实例调用accept方法替换了p.printPerson(): 对应这里,可以使用如下代码筛选适龄服兵役的会员: 如果我们想做的事情不只是打印会员信息,而是更多的事情,比如验证会员身份、获取会员联系方式等等。此时,我们需要一个有返回值方法的函数式接口。JDK的标准函数式接口Function<T,R>就有一个这样的方法R apply(T t)。下面的方法从参数mapper中获取数据,并在这些数据上执行参数block指定的行为: 下面的代码获取了roster中适龄服兵役的所有会员的邮箱信息并打印出来: 方案八:多使用泛型 再来回顾一下processPersonsWithFunction方法。下面是这个方法的泛型版本,新方法在参数类型上要求更为宽容: 要打印适龄服兵役的会员信息可以像下面这样调用processElements方法: 在方法的调用过程中执行了如下行为: 从一个集合中获取对象信息,在这个例子里是从集合实例roster中获取Person对象信息。 过滤能够匹配Predicate实例tester的对象。在这个例子里,Predicate对象是一个lambda表达式,它指定了过滤适龄服兵役的条件。 将过滤后的对象交给一个Function对象mapper处理,mapper会为这个对象匹配一个值。在这个例子中Function对象mapper是一个lambda表达式,它返回了每个会员的邮箱地址。 由Consumer对象block为mapper匹配的值指定一个行为。在这个例子里,Consumer对象是一个lambda表达式,它的作用是打印一个字符串,也就是Function实例mapper返回的会员邮件地址。 方案九:使用将lambda表达式作为参数的聚集操作 下面的代码中使用了聚集操作来打印roster集合中适龄服兵役会员的邮件地址: 分析下如上代码的执行过程,整理如下表: 行为 聚集操作 获取对象 Stream<E> stream() 过滤匹配Predicate实例指定标准的对象 Stream<T> filter(Predicate<? super T> predicate) 通过一个Function实例获取对象匹配的值 <R> Stream<R> map(Function<? super T,? extends R> mapper) 执行Consumer实例指定的行为 void forEach(Consumer<? super T> action) 表中的filter、map和forEach操作都是聚集操作。聚集操作处理的元素来自Stream,而非是直接从集合中获取(就是因为这示例程序中调用的第一个方法是stream())。Stream是一个数据序列。和集合不同,Stream并没有用特定的结构存储数据。相反的,Stream从一个特定的源获取数据,比如从集合获取数据,通过一个pipeline。pipeline是一个Stream操作序列,在这个例子中就是filter-map-forEach。此外,聚集操作通常采用lambda表达式作为参数,这也给了我们许多自定义的空间。 ########

    [阅读更多...]
  • 瞻前顾后 – 2016

    很早就想对过去一年的工作做个总结,不过懒病发作总不想动手。直到今天科三考试失利,反思了好久才整理好了心情(所以有些时候十分需要当头一盆冷水)。从驾校回来后写了这篇短文来对过去的一年做个总结,并为今年做一些规划。 先说工作吧。过去一年我们主要是在做一款车联网产品,我负责软硬件对接的部分。对我来说,我参与的这个产品是失败了:在我主要参与的一年里,产品正式上线的deadline一拖再拖,拖到后来热情消退,最终走人了事。在离职前后一直在想为什么会走到这一步。现在想想,我们那个产品组的构成很不错:我们软件组的经理是我见过的最负责的经理;硬件组经理在行业里浸润多年,很好地把握了产品的方向;共事的同事在各自的领域也都熟练且专业;公司经理是技术出身,沟通不存在问题。可惜最终的结果让人有些失望:产品始终不能推进市场,在新年开始以后,公司已经开始计划做外包项目收拢资金。至于产品为什么会失败,一开始只是认为对市场反应慢缺少危机感,后来才意识到这只是一个方面,关键仍在开发过程的控制上:阶段性目标不明确,时间线不明确。在产品开发早期,我们也过过一段拼命加班的日子,后来产品大致成形了,推进速度就慢了下来。当时确实是遇到了一些问题——主要是在测试上。因为资源支持不足,测试持续的时间很长(其实资源不该成为问题的,我们的金主就是一家特大的汽车贸易集团)。所谓的测试阶段,就是在公司和同事的几辆车上试用产品,大家在这几辆车的日常使用中一点点发现问题,然后再一点点地调整。时间飞快地过去,从15年10月份开始到16年3月份,我们也适应了慢下来的节奏。如果一开始就明确了产品合格标准以及测试方案,规划好测试调整的时间线,尽力解决资源的问题,完成一轮阶段性目标后再进行迭代,相信起码不会像后来那么被动。至于其他…我也就只能说这些。 再说说关于个人的。在生涯规划方面只有一句话:走了好多弯路,相信辛苦不会白费。其他的方面大致也可以:买了跑步机,每周基本上可以坚持有四五个早上跑1.5到3公里;陪老婆出去转了一圈;在老婆的监督下每天早睡早起;养成了每天读书学习的习惯…. 但是现在这段时间也有了一些不好的习惯:比如拖延症再次频繁发作(写这篇文就用了两天多);每天学习工作前总要刷会儿平板手机;对于技术的执着心有些下降;开始有些浮躁。失利后浮躁的状态稍稍有些冷却,科三虽然没过,但是车考已经不是重心了,需要重新收拾下心情回到正轨上。 在2016年有了新的工作,也开始学车。今年计划的学习方向是计算广告学、算法、java基础、scala,还有英语。因为打算倒空重来,所以干脆删掉了之前全部的文(也没有彻底删掉了,只是备份在其他地方)。在这个博客上以后只会记录一些经过用心整理的相对有价值的的内容,学习的内容会记录在博客园提供的空间上。记录我学习的地址是robin’s note,一个以前使用的博客。 想做的事情很多,学习的事情只能一步步的来,记录下来每一步,除了习惯养成也能起到警醒的作用。同时也希望大家能够监督共勉。 就这样!生活很好,奋斗更好!

    [阅读更多...]
  • 使用Memory Analyzer分析Java集合

    上一节里说了如何发现JVM内存中的空集合对象,这对减少JVM内存的浪费有些帮助。这一节要说的内容更有意思一些,可以让我们更深入地了解Java集合的使用。 先说明一点:这里仅仅是通过内存分析来得到一些结论。分析出来的结论通过JDK源码也许可以很轻易地得出,但是请关注分析过程和分析思路,毕竟源码不常有。 在上一节分析空集合对象的过程中难免会产生一些疑问:每个集合的长度有多大?这些集合实例由哪些对象持有?还有一些衍生出的问题:集合空间(capacity)的使用率(或者说填充率)有多高;集合类的hash函数是否足够好;当将元素填充进HashMap实例时是否会发生很多hash冲突?这些都是非常有意义的问题,有助于我们解决具体的任务。在设计开发阶段,这些问题未必都能得到解答,但是在运行测试阶段,借助MAT我们也许可以得到答案。 方案说明 这次会使用MAT中的Query Browser对dump文件进行切面式的分析。Query Browser我们之前已经多次使用,它提供了一系列非常有用的功能,就是这个:。这次主要是用到Java Collections功能组。说一下思路,目前我们有两种方案: 方案一:查询dump中某一个集合类型的全部对象(比如HashMap),然后对结果集进行分组。比如按size进行分组,然后查看size为1的集合对象的持有者。如果您的兴趣在于dump中的全部对象,那么这个方案就值得参考。 方案二:首先缩小要分析的对象的范围,而后使用Java Collection功能组进行分析。举个例子,首先筛选出类名符合“com.zhyea.projects.appname.*”的对象集合,而后在这些对象的histogram视图中再筛选出全部HashMap对象,而后执行查询查看“com.zhyea.projects.appname.*”对象集合中的HashMap对象的size分布信息。如果您的目标是服务器上的某个应用或者是应用中的某个模块,就可以试一下这个方案。 集合对象size 采用哪种结构存储数据以及分配多少初始化空间等内容是在开发阶段决定的,但是用来存储数据的集合对象通常是在运行阶段创建的。因此集合的size以及其持有者通常不是很明确。分析现实场景中得到的dump文件是验证之前采用的方案是否正确的一种方式。通过分析dump文件用户可以知道为集合对象分配的初始空间是否太小或者太大。太小了就会导致集合对象在运行时需要不断的重新调整大小,太大了则会导致不必要的内存浪费。 我取了一份公司测试环境中的dump文件,接下来将使用这份文件演示一下分析的过程。 接下来要做的事就是过滤并显示“com.joyxsys.projects.*”包下类的所有实例。通过执行这个操作我们可以只关注要查看的类,而不受dump中大量的其他对象的干扰。 这个需要用到Query Browser中的Show Retained Set功能: 点击菜单项后,在弹出窗口中输入参数信息: 然后就可以从现有的结果集中进一步过滤出符合“.*HashMap*”的对象: 过滤后的结果: 之后执行右键菜单中的“Collections Grouped By Size”菜单项: 执行菜单项后得到的结果是一个表格。表格的第一列是HashMap对象的大小,第二列是这样大小的HashMap对象的数量,再就是对应组中对象的Shallow Size和近似Retained Size: 如果对其中的某组数据感兴趣(比如长度为0的这组数据)就可以使用“Immediate dominators”这个菜单项来找出是谁持有这个组中的对象: 就如之前的经验,在这个面板中我们会看到一些类: 通过分析我们可以看到,在我的应用中共有104个HashMap对象,其中size为0的对象有93个。DelegatingClassLoader持有了90个size为0的HashMap对象。 集合填充率 “Collection Fill Ratio(集合填充率)”查询和刚才执行的查询在某种程度上很类似。不同之处在于Fill Ratio查询只对会为元素预先分配空间的集合类型有效,比如HashMap、ArrayList等等。Fill Ratio查询得到的信息是预分配空间的使用率,这个值通常在0~1之间,计算公式是 Fill Ratio = size / capacity。 对“com.joyxsys.projects.*”包下的HashMap实例执行Collection Fill Ratio: 查询结果如下图: 可以看到在104个HashMap实例中,93个是空的(和上面得到的结果一致),还有9个Fill Ratio不到20%。所有实例中Fill Ratio最高也不过40%。当然本身数据的总量就不够大,因此也说明不了多少问题。 通常可以把上面提到的两种方式结合起来分析问题。比如可以先执行”Collections Fill Ratio”,而后在得到的分组结果基础上执行”Collections Grouped By Size”操作。 Hash效率 现在开始讨论下一个问题——hashMap和Hashtable中的hash冲突。一个不甚高明的hash()方法实现会严重影响哈希表的查找速度。最为极端的一种情况就是所有元素返回的hash值都是一样的,这样每次查找就相当于遍历一个LinkedList。 哈希函数是否会导致太多哈希冲突也是一个在开发阶段不容易得到验证的问题,但同时也是通过分析dump文件容易得到结果的一个典型案例。尽管这问题和性能相关更多,但还是在dump中留下了相当多的痕迹,我们可以借助”Map Collision Ratio”来解决这个问题。通过”Map Collision Ratio”可以对HashMap(或Hashtable)的实例按哈希冲突的概率进行分组。哈希冲突的概率是向hash表中插入Entry时发生哈希冲突的概率。 仍然是分析刚才的dump文件,不过这次直接查看全部的HashMap实例,打开Histogram视图,过滤HashMap实例,之后再执行“Map Collision Ratio”查询: 执行查询后会得到一个表格,其中第一列是Collision Ratio,第二列是相应的对象的数目。 在这里可以很清楚地看到只有一个实例的Collision Ratio介于60%到80%之间。现在可以看一下这个实例,以及发生冲突的所有的key。首先要尝试获取相关的实例: 执行结果: 在这个里面是看不出什么来的,因为key的类型是String。关于String的hash函数是否够好我想不需多说。但是为什么还会出现这样的情况呢,我能想到的一个解释就是太巧了,碰巧大部分元素都是相同的hash函数。这样的一个HashMap实例会呈现出什么样的特点呢:就是会出现一个非常长的链表,hash表中几乎80%的元素都会在这个链表中。这个可以通过“Grouped By Size”和“List Objects”这两个工具来分析一下。 查看HashMap内容 最后介绍一个可以方便查看Map结构内容的工具:“Hash Entries”。在做性能分析工作时这个工具将会经常用到。 通过“List Objects”查看HashMap实例内容通常都不是一件容易的事情,关键是太容易受到干扰了,一大堆内容同时出现,尤其是Hash冲突比较严重的时候,需要不停的展开折叠内容。 看看上面这张图体验一下。 这时可以使用“Hash Entries”对一个或多个hashMap实例进行查询,查询结果依然是一个表格: 这时候是不是看起来舒服多了。现在可以使用右键菜单继续对目标实例进行分析了。 分析数组 在“Java Collections”功能组还提供了两个数组分析的工具(功能和我们前面介绍的类似,所以不会再重复说明了): Arrays Grouped By Size:对直接类型数组和对象数组都有效; Array Fill Ratio:对直接类型数组无效,统计数组中值不为null的元素的比例。 参考文档 Analyzing Java Collections Usage with MAT:http://scn.sap.com/people/krum.tsvetkov/blog/2007/11/05/analyzing-java-collections-usage-with-memory-analyzer

    [阅读更多...]
  • 找出被空集合占用的内存

    在平时的内存分析中发现有很大一部分集合对象在实例化后就从没有被使用过。集合类是我们平时使用最多的一种类,部分集合类在实例化时就会得到一部分空间(比如ArrayList、HashMap等)。这些空的集合实例虽然价值不大,但也有可能会浪费很多的内存空间。接下来演示一下如何去发现并解决这个问题。 案例 看一下下面这个MyValueStorage类的代码,在这个类里定义了三个ArrayList型的成员变量,并做了初始化操作。这个三个成员变量中standardValues将会被经常用到,specialValues偶尔会被用到,erroneousValues只有在极少数的情况下(比如发生异常)才会被使用到。 一个空的ArrayList默认初始化capacity是10,在32位操作系统上会占用80byte的内存空间,在64位操作系统上则会占用144byte。假使这个类在系统中被广泛的使用,在内存中有接近500 000个实例。那么在32位的系统中将会为specialValues和erroneousValues保留80 M的空间(在64位系统上是144MB)。一个应对的思路就是延迟初始化,即直到使用这些对象的时候才将之实例化,否则一直为null。当然我们需要做一些额外的工作,比如添加几个“if”语句以避免空指针异常的出现。 换个角度来考虑这个问题:如果对象的实例不多的话,那么为之进行优化的工作就是没有必要的。所以在着手优化之前,需要先弄清楚优化工作是否可以获取到明显的收益,在我们这个例子里就是是否可以显著地节省内存。 怎样找出未使用的集合实例 要找出未使用的集合实例可以遵循如下的步骤: 正常运行实例一段时间,根据线程ID获取heap dump文件; 使用Memory Analyzer中的OQL(Object Query Language)工具来查找大小是0且修改次数也是0的集合对象。即在我们获取dump文件之前,这些集合对象一直是空的且从未被修改过。 点击工具栏上的“OQL”按钮启动OQL工具: 顾名思义,OQL类似于SQL语句。这里我们使用如下的语句检查是否有空的且从未被使用过的 ArrayList, HashMap和Hashtable实例: 点击工具栏的红色叹号按钮执行查询。注意一次执行一行。 要想了解更多关于OQL的细节可以参考官方文档。此外,这篇文也不错,先将就着看,有时间整理一下。 计算空集合占用的内存 OQL查询得到的结果是一个符合查询要求的对象列表。想知道这些对象一共占用了多少内存可以开启histogram(直方图)视图。 然后计算所有对象占用的内存总量(使用工具栏上的“Calculate Retained Size”按钮,也可以在右键菜单中找到相关项),在下图实例中是266.4KB,还不算大。 这些空集合在哪儿 在确定了空集合的大小以后,如果发现有必要去做一些优化,那么接下来的事情就是找出谁制造了这些空集合。一个最快捷的方式就是使用右键菜单中的“Immediate Dominators”项。 结果如下图: 看到了这个结果以后相信会比较容易定位到需要优化的位置。对于我这个应用来说是然并卵——大部分都在框架上,幸好占用的空间不大,还不需要优化。 就这样!! 参考文档 Memory For Nothing:http://scn.sap.com/people/krum.tsvetkov/blog/2007/08/02/memory-for-nothing Analyzing a Heap Dump Using OQL:http://visualvm.java.net/oqlhelp.html ########

    [阅读更多...]
  • 使用Memory Analyzer分析内存泄漏

    概述 检测内存泄漏通常采用的方式是检查内存中某些对象的数量是否存在单调递增的现象。这可以通过“在线”实时监控分析的方式或者比较不同时段的内存快照来实现。然而实时监控的方案通常并不可行,尤其是在生产环境上,得考虑到因此导致的性能消耗;并且内存泄漏的产生通常也是非常偶然的,只有在某些特定条件下才会发生。这篇文章将介绍一些使用MAT发现内存泄漏的技巧。 准备工作 首先一定要有足够的数据,这里指的是heap dump文件。可以对JVM进行配置,以实现一发生OutOfMemoryError就自动生成Heap Dump文件。 第二步就是让内存泄漏问题清楚地暴露出来从而容易被捕捉到。这里有一个技巧:试着调整一下应用运行时的最大堆内存,调整到比应用正常运行所需的内存大一些就可以了(建议是一次Full GC后剩余内存的两倍大小)。即使不知道应用运行时到底需要多大的内存,加大堆内存也不是一个坏主意(有时可能真的就是内存不够用了,而非是发生了内存泄漏)。这里不讨论分配给一个应用太大的堆内存是好还是坏——这里只是将调整内存作为故障排除的一个临时方案。调整内存后我们会得到什么呢:如果确实存在内存泄漏,和内存泄漏有关的对象的大小估计会占到堆内存的一半(如果当时设置的是两倍),此时再找导致内存泄漏的原因应该就比较容易了。 案例一 现在假设我们已经做好了配置,然后某一天发生了MMO错误并生成了一个相当大的heap dump文件。接下来该怎么做呢?说实话,接下来要做的事情非常简单。 首先使用MAT打开这个heap dump文件。如果文件非常大的话,第一次打开可能会需要等一段时间,之后再打开这个文件就会非常快了,因为首次打开的时候已经完成了对文件的解析。现在开始尝试找出到底是谁蚕食了我们的内存。点击工具栏上的按钮进入Dominator tree视图: 这里会看到首页的对象图以一个树的形式展现出来。这个树里展示了对象、依赖、它们之间的引用关系以及其他。这里不会详细介绍这个树背后的全部细节,只是说一下两个重要的指标: 在树的最顶端(依Retained Heap Size排序)可以看到内存中最大的对象; 最大的对象就是占用内存最多的对象,它在树中的子节点都是被该对象直接或间接引用的对象(这意味着当这个对象被回收的时候它的子节点对象也会被回收)。 一般发生内存泄漏的时候,都会直接锁定那个最大的对象。接下来一步步接近真相:展开最大对象的子树,试着找到retained size最接近最大对象的子节点(通常是一个数组或者集合)。就是这么简单,我们找到了内存泄漏的元凶。如果还想继续探索下去的话,可以尝试探索更深的子节点。 看一下展开后的结果: 下一件事就是找到内存泄漏的对象到GCRoots的引用链。选中内存泄漏对象,右键菜单中选择“Paths from the GC roots -> without weak and soft references”即可: 在“Paths from the GC Roots”可以看到我们选中的对象到GCRoot的路径,最顶端是我们选中的目标,最下方是GCRoots。选的样本不好,估计到GCRoot还得展开好久: 换了一个样本,这下清晰多了: 案例二 如果所有的问题都能像上面的案例那样容易解决就太好了。有时候仅仅看一次dominator tree是远远不够的。看看下图的案例:这里也提供了足够多的内存让内存泄漏对象去生长,也打开一个覆盖了所有对象的dominator tree,其中就包括内存泄漏对象。但是能看到内存泄漏对象么?在案例一中,所有小的内存泄漏对象都被一个巨大的根对象引用,但是有时候这些相对较小的内存泄漏对象就直接在dominator tree的顶级节点上。尽管这些小的内存泄漏对象数量很多,但是每个对象的Retained Size都比较小,因此不会排在前面。 此时点击工具栏中的“Group by class”按钮会有很大的帮助: 点击按钮后,我们可以看到同一组对象Size的总和,此时再找内存泄漏的原因是不是会更容易一些: 其他 附上测试程序: 模拟时使用的虚拟机参数: 参考文档 Finding Memory Leaks with SAP Memory Analyzer Shallow heap & Retained heap

    [阅读更多...]