架构师修炼之道

知识日新月异,唯有保持同步迭代,才能适应美好的未来!

0%

在实际项目开发过程中我们经常会遇到把A类的属性复制到B类中的这种场景,比如实体类DO对象从数据库查询出来,在给到调用方之前很可能需要转成DTO传输对象,那这个时候就需要将DO对象的字段值复制到DTO对象中,如果字段比较少可能你手写就行了,但如果要复制的字段比较多,超过50个,并且这种场景在项目中出现频繁,该怎么办?仍然还是去手写,然后一点一点复杂吗?如果这样做那效率必然是低下的,而且没有一点儿技术含金量,花大把时间写这种样本代码对自己成长帮助不大。接下来介绍的是一款让你更优雅的实现Bean之间属性映射的框架,并且在同类框架中性能是最好的。

介绍

MapStruct是一个代码生成器,它基于约定优于配置的方法极大地简化了Java bean类型之间映射的实现。

通过上面的介绍我们应该能够理解到这么几点,首先它是一个代码生成器,就是用来帮开发者自动生成代码的工具,只需要通过简单的代码就可以实现原来手工编写的样板代码,因为它采用约定大于配置的设计思想,所以开发者只需要掌握简单的代码编写就可以了。

也就是说人家框架帮你自动生成了原先手工编写的代码,但实际上那些手工编写的代码还是存在的,只不过你没有编写,框架帮你自动生成了而已。这其实也回到框架的本质,事情还是那些事,就看你来做,还是它来做,它如果多做,你就少做,甚至可以不做。这里提到的指的是各种框架,它的本质就是帮开发者做了一些事情。

阅读全文 »

OutOfMemoryError

java.lang.OutOfMemoryError

Java heap space

Java应用程序仅允许使用有限的内存,此限制是在应用程序启动期间指定的。Java内存被分为两个不同的区域,这些区域称为Heap(堆空间)和Permgen(永久代):

这些区域的大小是在Java虚拟机(JVM)启动期间设置的,可以通过指定JVM参数-Xmx和-XX:MaxPermSize进行自定义。如果未明确设置大小,将使用特定于平台的默认值。

应用程序尝试添加更多的数据放入堆空间区域,但没有足够的空间供它,可能会有大量的物理内存可用,但当JVM达到堆大小限制时,都会引发Java堆空间错误。

是什么原因造成的?

因为你尝试将XXL大的对象放入S大小的Java堆空间中。也就是说,该应用程序需要更多的Java堆空间才能正常运行。

  • 用量/数据量激增。该应用程序旨在处理一定数量的用户或一定数量的数据。当用户数量或数据量突然达到峰值并超过预期阈值时,在峰值之前正常运行的操作将停止运行并触发java.lang.OutOfMemoryError: Java heap space。
  • 内存泄漏。一种特殊类型的编程错误将导致你的应用程序不断消耗更多的内存。每次使用应用程序的泄漏功能时,都会将某些对象留在Java堆空间中。随着时间的流逝,泄漏的对象会消耗所有可用的Java堆空间。
    阅读全文 »

什么是垃圾回收

跟踪所有仍在使用的对象并将其余对象标记为垃圾的这一过程就叫做垃圾回收。

手动内存管理

在开始介绍现代Garbage Collection之前,快速回顾一下以前不得不手动和显式分配和释放数据存储空间的日子。如果你忘记释放它,则将无法重用它,但这块内存已经被声明了只是没有被使用,这种情况称为内存泄漏

下面是一个用C语言编写的,使用手动内存管理的简单示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int send_request() {
size_t n = read_size();
int *elements = malloc(n * sizeof(int));

if(read_elements(n, elements) < n) {
// elements未释放
// 因为代码运行到此处就返回了,elements还来不及释放
return -1;
}

// …
// 如果运行到此处才会被释放
free(elements)
return 0;
}

如你所见,忘记释放内存是很容易的。内存泄漏曾经是比今天更常见的问题,你只能通过修复代码来真正打败“它们”,那有没有更优雅的解决方案呢?答案是肯定的,更好的方法是采取自动回收未使用的内存的策略,从而完全消除人为错误的可能性。这种自动化的过程称为垃圾收集(或简称GC)。

自动内存管理

在上面的C++代码中必须明确地知道何时需要进行内存管理,把这个工作给程序员,就意味着肯定有系统性风险,即人为忽略。如果把内存管理交给程序自己处理呢?这将非常方便,因为开发人员不再需要考虑自己清理。在程序运行时将自动了解不再使用某些内存并将其释放。换句话说,它会自动** 收集垃圾**。第一个垃圾收集器是在1959年为Lisp创建的,此后技术才有所发展。

引用计数(Reference Counting)

上面用C++的共享指针示例可以适用于所有对象,并且许多语言(例如Perl,Python或PHP)都采用这种方法。下面用图片说明:

绿云(GC ROOTS)表示程序员指向的对象仍在使用中。从技术上讲,这些可能是当前正在执行的方法中的局部变量或静态变量之类的东西。

蓝色圆圈是内存中的活动对象,其中的数字表示其引用计数。灰色圆圈表示没有被哪个仍在显式使用的对象中引用。因此灰色表示的是垃圾,可以由垃圾收集器清理。

这一切看起来真的很好,不是吗?但是这个策略有一个巨大的缺点。如果原先的引用不存在了,但可能它们之间仍然有相互引用,这种循环引用的问题,会导致它们的引用计数永远不为零。下面是一个例子:

红色圆圈表示的对象实际上是应用程序不使用的垃圾,可是由于引用计数的限制,仍然会存在内存泄漏问题。

有一些方法可以解决此问题,例如使用特殊的“弱”引用或应用单独的循环收集算法。上述语言(Perl、Python和PHP)都以某种方式处理循环,不过本方主要介绍JVM采取的方法。

阅读全文 »

对于Java开发人员来说,了解Java内存模型是必不可少的知识。

JVM内存模型

在生产环境上一般会配置JVM参数以充分利用硬件资源,不管是在云主机或容器里面,也就是在启动应用时添加下面的某些配置:

  • -XmsSetting —初始堆大小
  • -XmxSetting —最大堆大小
  • -XX:NewSizeSetting —新一代堆大小
  • -XX:MaxNewSizeSetting —最大新一代堆大小
  • -XX:MaxPermGenSetting —永久生成的最大大小
  • -XX:SurvivorRatioSetting —新的堆大小比率(例如,如果Young Gen大小为10m并且内存开关为–XX:SurvivorRatio=2,则将为Eden空间保留5m,为两个Survivor空间分别保留2.5m,默认值= 8)
  • -XX:NewRatio —提供新旧大小的比率(默认值= 2)

就像任何其他软件一样,JVM占用主机OS内存上的可用空间。

在JVM内部存在单独的内存空间(堆、非堆和缓存),用来存储运行时数据和编译后的代码。

阅读全文 »

JVM架构

JVM只是一个规范,在市面上有不同的公司进行了实现,这包含收费的以及免费的。下面这张图是JVM的整体架构图,涵盖了java代码执行的全过程,下面分别就这些过程进行说明。

类加载器子系统

如果与php相比较,当php文件执行完后Zend引擎被立即销毁,可java却不同,JVM会长驻在内存并一直工作。它在执行期间使用Class Loader子系统将类文件带到RAM,这被称为Java动态类加载。在它首次运行时(而非编译时)将加载、链接和初始化类文件(.class)。

加载

将编译的类(.class文件)加载到内存中是Class Loader的主要任务。通常类加载过程从加载主类(即带有 static main() 方法声明的类)开始的,所有后续的类加载都是根据已运行类中的类引用完成的,如以下情况所述:

  • 当字节码静态引用一个类时(例如 System.out
  • 当字节码创建一个类对象时(例如 Person person = new Person("John")

在JVM中有三个类加载器(与继承属性相关),它们工作时遵循4个主要原则。

阅读全文 »